第16章 カスタマイズ

OpenStack はあなたが必要とするすべてのことをしてくれるわけではないかもしれません。この場合、主に2つのやり方のうちのいずれかに従ってください。まず最初に、貢献するには (https://wiki.openstack.org/wiki/How_To_Contribute) を学び、Code Review Workflow (https://wiki.openstack.org/wiki/GerritWorkflow) に従って、あなたの修正を上流の OpenStack プロジェクトへコントリビュートしてください。もし、あなたが必要な機能が既存のプロジェクトと密にインテグレーションする必要がある場合、これが推奨される選択肢です。コミュニティは、いつでも貢献に対して開かれていますし、機能開発ガイドラインに従う新機能を歓迎します。

代替え案としては、もしあなたが必要とする機能が密なインテグレーションを必要としないのであれば、OpenStack をカスタマイズする他の方法があります。もし、あなたの機能が必要とされるプロジェクトが Python Paste フレームワークを使っているのであれば、そのための ミドルウェアを作成し、環境設定を通じて組み込めばよいのです。例えば OpenStack Compute の新しいスケジューラーや、カスタマイズされたダッシュボードを作成するといった、プロジェクトをカスタマイズする特定の方法もあるかもしれません。この章では、OpenStack をカスタマイズする後者の方法にフォーカスします。

OpenStack をこの方法でカスタマイズするためには、開発環境が必要です。開発環境を手軽に動作させる最良の方法は、クラウドの中で DevStack を動かすことです。

 DevStack

ドキュメンテーションはすべて DevStack (http://devstack.org/) のウェブサイトにあります。 どのプロジェクトをカスタマイズしたいかによって、つまり Object Storage (swift) なのか他のプロジェクトなのかによって、DevStack の環境設定が異なります。以下のミドルウェアの例では、Object Storage を有効にしてインストールしなければなりません。

 

インスタンス上で、Folsom の安定版用の DevStack を動作させるためには:

  1. ダッシュボード、または nova のコマンドラインインタフェース(CLI)から、以下のパラメータでインスタンスを起動してください。

    • 名前: devstack

    • イメージ: Ubuntu 12.04 LTS

    • メモリサイズ: 4 GB RAM (おそらく 2 GB でもなんとかなるでしょう)

    • ディスクサイズ: 最低 5 GB

    nova コマンドを使っているのであれば、適切なメモリ量とディスクサイズを得るために nova boot コマンドに --flavor 6 を指定してください。

  2. 利用するイメージで root ユーザしか使えない場合、「stack」ユーザを作成しなければなりません。でなければ、stack.sh スクリプトに「stack」ユーザを作成させる時に、screen に関するパーミッションの問題にぶつかります。利用するイメージに、既に root 以外のユーザがあるのであれば、このステップは省略できます。

    1. ssh root@<IP Address>

    2. adduser --gecos "" stack

    3. プロンプトに対して新しいパスワードを入力します。

    4. adduser stack sudo

    5. grep -q "^#includedir.*/etc/sudoers.d" /etc/sudoers || echo "#includedir /etc/sudoers.d" >> /etc/sudoers

    6. ( umask 226 && echo "stack ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/50_stack_sh )

    7. exit

  3. stack ユーザとしてログインし、DevStack の設定を行います。

    1. ssh stack@<IP address>

    2. プロンプトに対して、stack ユーザに作ったパスワードを入力します。

    3. sudo apt-get -y update

    4. sudo apt-get -y install git

    5. git clone https://github.com/openstack-dev/devstack.git -b stable/folsom devstack/

    6. cd devstack

    7. vim localrc

      • Swift のみ、ミドルウェア例 で使用された, 以下の [1] Swift only localrc の例を参照してください。

      • 他のすべてのプロジェクトについて、 Nova Scheduler Example で使用された, 以下の [2] All other projects localrc の例を参照してください。

    8. ./stack.sh

    9. screen -r stack 

    [注記]注記
    • stack.sh の実行には、しばらく時間がかかります。できれば、この時間を使って OpenStack ファウンデーションに参加してください (http://www.openstack.org/join/)。

    • stack.sh を実行する際、“ERROR: at least one RPC back-end must be enabled” というエラーメッセージが出るかもしれません。これは心配しないでください。swift と keystone はRPC (AMQP) バックエンドを必要としないのです。同様に、ImportErrors はすべて無視できます。

    • Screen は、多くの関連するサービスを同時に見るための便利なプログラムです。GNU screen quick reference. (http://aperiodic.net/screen/quick_reference) を参照してください。

以上で OpenStack の開発環境を準備できましたので、運用環境にダメージを与えることを心配せずに自由にハックできます。Swift のみの環境では ミドルウェア例 に、‘他のすべてのプロジェクトでは Nova Scheduler Example に進んでください。

[1] Swift only localrc

ADMIN_PASSWORD=devstack
MYSQL_PASSWORD=devstack
RABBIT_PASSWORD=devstack
SERVICE_PASSWORD=devstack
SERVICE_TOKEN=devstack 
            
SWIFT_HASH=66a3d6b56c1f479c8b4e70ab5c2000f5
SWIFT_REPLICAS=1 
            
# Uncomment the BRANCHes below to use stable versions
            
            
# unified auth system (manages accounts/tokens)
KEYSTONE_BRANCH=stable/folsom
# object storage
SWIFT_BRANCH=stable/folsom 
            
disable_all_services
enable_service key swift mysql

[2] All other projects localrc

ADMIN_PASSWORD=devstack
MYSQL_PASSWORD=devstack
RABBIT_PASSWORD=devstack
SERVICE_PASSWORD=devstack
SERVICE_TOKEN=devstack 
            
FLAT_INTERFACE=br100
PUBLIC_INTERFACE=eth0
            
VOLUME_BACKING_FILE_SIZE=20480M 
            
# For stable versions, look for branches named stable/[milestone]. 
            
# compute service
NOVA_BRANCH=stable/folsom 
            
# volume service
CINDER_BRANCH=stable/folsom 
            
# image catalog service
GLANCE_BRANCH=stable/folsom 
            
# unified auth system (manages accounts/tokens)
KEYSTONE_BRANCH=stable/folsom 
            
# django powered web control panel for openstack
HORIZON_BRANCH=stable/folsom

 ミドルウェア例

ほとんどの OpenStack プロジェクトは Python Paste(http://pythonpaste.org/) フレームワークに基づいています。A Do-It-Yourself Framework (http://pythonpaste.org/do-it-yourself-framework.html) は、このアーキテクチャの最良の紹介です。このフレームワークを使っているため、コードに一切手をいれずに、プロジェクトの処理パイプラインになんらかのカスタム・コードを入れて機能追加を行うことができます。

このように OpenStack をカスタマイズすることを説明するため、Swift 用に、あるコンテナに対して、そのコンテナのメタデータで指定される一群のIPアドレスのみからアクセスできるようにするミドルウェアを作成してみます。このような例は多くの状況で有用です。例えば、一般アクセス可能なコンテナを持っていますが、実際に行いたいことはホワイトリストに基づいた一群のIPアドレスにのみ制限したい場合です。

[警告]警告

この例は実証目的のみのためにあります。さらなる作りこみと広範なセキュリティテストなしにコンテナのIPホワイトリスト・ソリューションとして使用するべきではありません。

stack.shscreen -r stack で作成したセッションに join すると、Swift インストール用の localrc を使ったのであれば、3つの screen セッションが見えます。

0$ shell*  1$ key  2$ swift

* (アスタリスク)は、どの screen にいるのかを示します。

  • 0$ shell. 何か作業することができる shell セッションです。

  • 1$ key. keystone サービス。

  • 2$ swift. swift プロキシサービス。

 

ミドルウェアを作成して Paste の環境設定を通して組み込むためには:

  1. すべての OpenStack のコードは /opt/stack にあります。shell セッションの screen の中で swift ディレクトリに移動し、あなたのミドルウェアモジュールを編集してください。

    1. cd /opt/stack/swift

    2. vim swift/common/middleware/ip_whitelist.py

  2. 以下のコードをコピーしてください。作業が終わったら、ファイルを保存して閉じてください。

    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
                         
    #    http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    # implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    import socket
     
    from swift.common.utils import get_logger
    from swift.proxy.controllers.base import get_container_info
    from swift.common.swob import Request, Response
     
    class IPWhitelistMiddleware(object):
        """
        IP Whitelist Middleware
    
        Middleware that allows access to a container from only a set of IP
        addresses as determined by the container's metadata items that start
        with the prefix 'allow'. E.G. allow-dev=192.168.0.20
        """
    
        def __init__(self, app, conf, logger=None):
            self.app = app
    
            if logger:
                self.logger = logger
            else:
                self.logger = get_logger(conf, log_route='ip_whitelist')
    
            self.deny_message = conf.get('deny_message', "IP Denied")
            self.local_ip = socket.gethostbyname(socket.gethostname())
    
        def __call__(self, env, start_response):
            """
            WSGI entry point.
            Wraps env in swob.Request object and passes it down.
    
            :param env: WSGI environment dictionary
            :param start_response: WSGI callable
            """
            req = Request(env)
    
            try:
                version, account, container, obj = req.split_path(1, 4, True)
            except ValueError:
                return self.app(env, start_response)
    
            container_info = get_container_info(
                req.environ, self.app, swift_source='IPWhitelistMiddleware')
    
            remote_ip = env['REMOTE_ADDR']
            self.logger.debug(_("Remote IP: %(remote_ip)s"),
                              {'remote_ip': remote_ip})
    
            meta = container_info['meta']
            allow = {k:v for k,v in meta.iteritems() if k.startswith('allow')}
            allow_ips = set(allow.values())
            allow_ips.add(self.local_ip)
            self.logger.debug(_("Allow IPs: %(allow_ips)s"),
                              {'allow_ips': allow_ips})
    
            if remote_ip in allow_ips:
                return self.app(env, start_response)
            else:
                self.logger.debug(
                    _("IP %(remote_ip)s denied access to Account=%(account)s "
                      "Container=%(container)s. Not in %(allow_ips)s"), locals())
                return Response(
                    status=403,
                    body=self.deny_message,
                    request=req)(env, start_response)
    
    
    def filter_factory(global_conf, **local_conf):
        """
        paste.deploy app factory for creating WSGI proxy apps.
        """
        conf = global_conf.copy()
        conf.update(local_conf)
    
        def ip_whitelist(app):
            return IPWhitelistMiddleware(app, conf)
        return ip_whitelist
                     

    envconf には、リクエストについて何をするのか判断するのに使える有用な情報が多数含まれています。どんなプロパティが利用可能なのかを知るには、以下のログ出力文を __init__ メソッドに挿入してください。

    self.logger.debug(_("conf = %(conf)s"), locals())

    そして以下のログ出力分を __call__ メソッドに挿入してください。

    self.logger.debug(_("env = %(env)s"), locals())
  3. このミドルウェアを Swift のパイプラインに組み込むには、設定ファイルを1つ編集する必要があります。

     vim /etc/swift/proxy-server.conf
  4. [filter:ratelimit] セクションを探し、以下の環境定義セクションを貼り付けてください。

    [filter:ip_whitelist]
    paste.filter_factory = swift.common.middleware.ip_whitelist:filter_factory
    # You can override the default log routing for this filter here:
    # set log_name = ratelimit
    # set log_facility = LOG_LOCAL0
    # set log_level = INFO
    # set log_headers = False
    # set log_address = /dev/log
    deny_message = You shall not pass!
  5. [pipeline:main] セクションを探し、このように ip_whitelist リストを追加してください。完了したら、ファイルを保存して閉じてください。

    [pipeline:main]
    pipeline = catch_errors healthcheck cache ratelimit ip_whitelist authtoken keystoneauth proxy-logging proxy-server
  6. Swift にこのミドルウェアを使わせるために、Swift プロキシサービスを再起動します。swift の screen セッションに切り替えてはじめてください。

    1. Ctrl-A の後で 2 を押します。ここで、2 は screen セッションのラベルです。Ctrl-A の後で n を押し、次の screen セッションに切り替えることもできます。

    2. Ctrl-C を押し、サービスを終了させます。

    3. 上矢印キーを押し、最後のコマンドを表示させます。

    4. Enter キーを押し、実行します。

  7. Swift の CLI でミドルウェアのテストをしてください。shell の screen セッションに切り替えてテストを開始し、swift の screen セッションにもどってログ出力をチェックして終了します。

    1. Ctrl-A の後で 0 を押します。

    2. cd ~/devstack

    3. source openrc

    4. swift post middleware-test

    5. Ctrl-A の後で 2 を押します。

  8. ログの中に以下の行があるでしょう。

    proxy-server ... IPWhitelistMiddleware
    proxy-server Remote IP: 203.0.113.68 (txn: ...)
    proxy-server Allow IPs: set(['203.0.113.68']) (txn: ...)

    基本的に、最初の3行はこのミドルウェアが Swift の他のサービスとやりとりする際に、再度認証を行う必要がないことを示しています。最後の2行は、このミドルウェアによって出力されており、リクエストが DevStack インスタンスから送られており、許可されていることを示しています。

  9.  

    DevStack 環境の外の、DevStack 用インスタンスにアクセス可能なリモートマシンからミドルウェアをテストします。

    1. swift --os-auth-url=http://203.0.113.68:5000/v2.0/ --os-region-name=RegionOne --os-username=demo:demo --os-password=devstack list middleware-test

    2. Container GET failed: http://203.0.113.68:8080/v1/AUTH_.../middleware-test?format=json 403 Forbidden   You shall not pass!

  10. 再び Swift のログをチェックすると、以下の行が見つかるでしょう。

    proxy-server Invalid user token - deferring reject downstream
    proxy-server Authorizing from an overriding middleware (i.e: tempurl) (txn: ...)
    proxy-server ... IPWhitelistMiddleware
    proxy-server Remote IP: 198.51.100.12 (txn: ...)
    proxy-server Allow IPs: set(['203.0.113.68']) (txn: ...)
    proxy-server IP 198.51.100.12 denied access to Account=AUTH_... Container=None. Not in set(['203.0.113.68']) (txn: ...)

    ここで、リモートIPアドレスが、許可されたIPアドレスの中になかったため、リクエストが拒否されていることがわかります。

  11. DevStack用インスタンスに戻り、リモートマシンからのリクエストを許可するようなコンテナのメタデータを追加します。

    1. Ctrl-A の後で 0 を押します。

    2. swift post --meta allow-dev:198.51.100.12 middleware-test

  12. ステップ 9 に記載したコマンドをもう一度試すと、今度は成功します。

このような機能試験は、正しいユニットテストと結合テストの代わりになるものではありませんが、作業を開始することはできます。

Python Paste フレームワークを使う他のすべてのプロジェクトで、類似のパターンに従うことができます。単純にミドルウェアモジュールを作成し、環境定義によって組み込んでください。そのミドルウェアはプロジェクトのパイプラインの一部として順番に実行され、必要に応じて他のサービスを呼び出します。プロジェクトのコア・コードは一切修正しません。Paste を使っているプロジェクトを確認するには、/etc/<project> に格納されている、プロジェクトの conf または ini 環境定義ファイルの中で pipeline 変数を探してください。

あなたのミドルウェアが完成したら、オープンソースにし、OpenStack メーリングリストでコミュニティに知らせることを薦めます。コミュニティの人々はあなたのコードを使い、フィードバックし、おそらくコントリビュートするでしょう。もし十分な支持があれば、公式なSwift ミドルウェア (https://github.com/openstack/swift/tree/master/swift/common/middleware) に追加するように提案することもできるでしょう。

 Nova スケジューラーの例

多くの OpenStack のプロジェクトでは、ドライバ・アーキテクチャを使うことによって、特定の機能をカスタマイズすることができます。特定のインターフェースに適合するドライバを書き、環境定義によって組み込むことができます。例えば、簡単に nova に新しいスケジューラーを組み込むことができます。nova の既存のスケジューラーは、フル機能であり、スケジューリング (http://docs.openstack.org/folsom/openstack-compute/admin/content/ch_scheduling.html) によくドキュメントされています。しかし、あなたのユーザのユース・ケースに依存して、既存のスケジューラで要件が満たせないかもしれません。この場合は、新しいスケジューラーを作成する必要があるでしょう。

スケジューラーを作成するには、nova.scheduler.driver.Scheduler クラスを継承しなければなりません。オーバーライド可能な5つのメソッドのうち、以下の「*」で示される2つのメソッドをオーバーライドしなければなりません

  • update_service_capabilities

  • hosts_up

  • schedule_live_migration

  • * schedule_prep_resize

  • * schedule_run_instance

OpenStack のカスタマイズをデモするために、リクエストの送信元IPアドレスとホスト名のプレフィックスに基づいてインスタンスを一部のホストにランダムに配置するようなNova のスケジューラーの例を作成します。この例は、1つのユーザのグループが1つのサブネットにおり、インスタンスをホスト群の中の一部のサブネットで起動したい場合に有用です。

[注記]注記

この例は実証目的のみのためにあります。さらなる作りこみと広範なテストなしにNovaのスケジューラーとして使用するべきではありません。

stack.shscreen -r stack で作成したセッションに join すると、多数の screen セッションが見えます。

0$ shell*  1$ key  2$ g-reg  3$ g-api  4$ n-api  5$ n-cpu  6$ n-crt  7$ n-net  8-$ n-sch ...
  • 0$ shell. 何か作業することができる shell セッションです。

  • 1$ key. keystone サービス。

  • g-*. glance サービス。

  • n-*. nova サービス。

  • n-sch。nova スケジューラーサービス。

 

スケジューラーを作成して、設定を通して組み込むためには:

  1. OpenStack のコードは /opt/stack にあるので、nova ディレクトリに移動してあなたのスケジューラーモジュールを編集します。

    1. cd /opt/stack/nova

    2. vim nova/scheduler/ip_scheduler.py

  2. 以下のコードをコピーしてください。作業が終わったら、ファイルを保存して閉じてください。

    # vim: tabstop=4 shiftwidth=4 softtabstop=4
    # Copyright (c) 2013 OpenStack Foundation
    # All Rights Reserved.
    #
    #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    #    not use this file except in compliance with the License. You may obtain
    #    a copy of the License at
    #
    #   http://www.apache.org/licenses/LICENSE-2.0
    #
    #    Unless required by applicable law or agreed to in writing, software
    #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    #    License for the specific language governing permissions and limitations
    #    under the License.
    
    """
    IP Scheduler implementation
    """
    
    import random
    
    from nova import exception
    from nova.openstack.common import log as logging
    from nova import flags
    from nova.scheduler import driver
    
    FLAGS = flags.FLAGS
    LOG = logging.getLogger(__name__)
    
    
    class IPScheduler(driver.Scheduler):
        """
        Implements Scheduler as a random node selector based on
        IP address and hostname prefix.
        """
    
        def _filter_hosts(self, hosts, hostname_prefix):
            """Filter a list of hosts based on hostname prefix."""
    
            hosts = [host for host in hosts if host.startswith(hostname_prefix)]
            return hosts
    
        def _schedule(self, context, topic, request_spec, filter_properties):
            """
            Picks a host that is up at random based on
            IP address and hostname prefix.
            """
    
            elevated = context.elevated()
            hosts = self.hosts_up(elevated, topic)
    
            if not hosts:
                msg = _("Is the appropriate service running?")
                raise exception.NoValidHost(reason=msg)
    
            remote_ip = context.remote_address
    
            if remote_ip.startswith('10.1'):
                hostname_prefix = 'doc'
            elif remote_ip.startswith('10.2'):
                hostname_prefix = 'ops'
            else:
                hostname_prefix = 'dev'
    
            hosts = self._filter_hosts(hosts, hostname_prefix)
            host = hosts[int(random.random() * len(hosts))]
    
            LOG.debug(_("Request from %(remote_ip)s scheduled to %(host)s")
                      % locals())
    
            return host
    
        def schedule_run_instance(self, context, request_spec,
                                  admin_password, injected_files,
                                  requested_networks, is_first_time,
                                  filter_properties):
            """Attempts to run the instance"""
            instance_uuids = request_spec.get('instance_uuids')
            for num, instance_uuid in enumerate(instance_uuids):
                request_spec['instance_properties']['launch_index'] = num
                try:
                    host = self._schedule(context, 'compute', request_spec,
                                          filter_properties)
                    updated_instance = driver.instance_update_db(context,
                                                                 instance_uuid)
                    self.compute_rpcapi.run_instance(context,
                                                     instance=updated_instance, host=host,
                                                     requested_networks=requested_networks,
                                                     injected_files=injected_files,
                                                     admin_password=admin_password,
                                                     is_first_time=is_first_time,
                                                     request_spec=request_spec,
                                                     filter_properties=filter_properties)
                except Exception as ex:
                    # NOTE(vish): we don't reraise the exception here to make sure
                    # that all instances in the request get set to
                    # error properly
                    driver.handle_schedule_error(context, ex, instance_uuid,
                                             request_spec)
    
        def schedule_prep_resize(self, context, image, request_spec,
                                 filter_properties, instance, instance_type,
                                 reservations):
            """Select a target for resize."""
            host = self._schedule(context, 'compute', request_spec,
                                  filter_properties)
            self.compute_rpcapi.prep_resize(context, image, instance,
                                            instance_type, host, reservations)
    

    contextrequest_specfilter_propertiesには、どこにインスタンスをスケジュールするのか決定するのに使える有用な情報が多数含まれています。どんなプロパティが利用可能なのかを知るには、以下のログ出力文を上記の schedule_run_instance メソッドに挿入してください。

    LOG.debug(_("context = %(context)s") % {'context': context.__dict__})LOG.debug(_("request_spec = %(request_spec)s") % locals())LOG.debug(_("filter_properties = %(filter_properties)s") % locals())
  3. このスケジューラーを Nova に組み込むには、設定ファイルを1つ編集する必要があります。

    LOG$ vim /etc/nova/nova.conf
  4. compute_scheduler_driver 設定を見つけ、このように変更してください。

    LOGcompute_scheduler_driver=nova.scheduler.ip_scheduler.IPScheduler
  5. Nova にこのスケジューラーを使わせるために、Nova スケジューラーサービスを再起動します。 n-sch screen セッションに切り替えてはじめてください。

    1. Ctrl-A の後で 8 を押します。

    2. Ctrl-C を押し、サービスを終了させます。

    3. 上矢印キーを押し、最後のコマンドを表示させます。

    4. Enter キーを押し、実行します。

  6. Nova の CLI でスケジューラーのテストをしてください。shell の screen セッションに切り替えてテストを開始し、n-sch screen セッションにもどってログ出力をチェックして終了します。

    1. Ctrl-A の後で 0 を押します。

    2. cd ~/devstack

    3. source openrc

    4. IMAGE_ID=`nova image-list | egrep cirros | egrep -v "kernel|ramdisk" | awk '{print $2}'`

    5. nova boot --flavor 1 --image $IMAGE_ID scheduler-test

    6. Ctrl-A の後で 8 を押します。

  7. ログの中に以下の行があるでしょう。

    LOG2013-02-27 17:39:31 DEBUG nova.scheduler.ip_scheduler [req-... demo demo] Request from 50.56.172.78 scheduled to
    devstack-nova from (pid=4118) _schedule /opt/stack/nova/nova/scheduler/ip_scheduler.py:73

このような機能試験は、正しいユニットテストと結合テストの代わりになるものではありませんが、作業を開始することはできます。

ドライバ・アーキテクチャを使う他のすべてのプロジェクトで、類似のパターンに従うことができます。単純に、そのドライバ・インタフェースに従うモジュールとクラスを作成し、環境定義によって組み込んでください。あなたのコードはその機能が使われた時に実行され、必要に応じて他のサービスを呼び出します。プロジェクトのコア・コードは一切修正しません。ドライバ・アーキテクチャを使っているプロジェクトを確認するには、/etc/<project> に格納されている、プロジェクトの環境定義ファイルの中で「driver」変数を探してください。

あなたのスケジューラーが完成したら、オープンソースにし、OpenStack メーリングリストでコミュニティに知らせることをお薦めします。もしかしたら他の人も同じ機能を必要としているかもしれません。彼らはあなたのコードを使い、フィードバックし、おそらくコントリビュートするでしょう。もし十分な支持があれば、もしかしたら公式なNova スケジューラー (https://github.com/openstack/nova/tree/master/nova/scheduler) への追加を提案してもよいでしょう。

 ダッシュボード

ダッシュボードは、Python Django (https://www.djangoproject.com/) Webアプリケーションフレームワークに基づいています。カスタマイズのための最良のガイドは既に執筆されており、 Build on Horizon (http://docs.openstack.org/developer/horizon/topics/tutorial.html) にあります。



loading table of contents...