OpenStack Libvirtに基づく仮想化プラットフォームスケジューリング実装------Nova仮想マシン起動ソース実装(3)


友达がこのブログを支持することに感谢して、共に交流を探求することを歓迎して、能力と时间が限られているため、间违ったところは避けられないで、指摘を歓迎します!転載する場合は、作者情報を保持してください.ブログアドレス:http://blog.csdn.net/gaoxingnengjisuan メールアドレス:[email protected]
前回のブログに続いて、私たちは引き続き方法を解析します.create_イメージの第2部と第3部、すなわち駆動構成とファイル注入部のコード.
2.駆動構成部分コード解析
まず方法を見てみましょうcreate_イメージの駆動構成部分を実装するコード:
def _create_image(self, context, instance, libvirt_xml,
                      disk_mapping, suffix='',
                      disk_images=None, network_info=None,
                      block_device_info=None, files=None, admin_pass=None):
        """
                  :
        # context:     ;
        # instance:    ;
        # libvirt_xml:               conf,       conf   xml  ;
        # disk_mapping=disk_info['mapping']:           ;
        # network_info=network_info:              ;
        # block_device_info=block_device_info:          ;
        # files=injected_files:        ;
        # admin_pass=admin_password:admin  ;
        # suffix='';
        # disk_images=None;
        """

        ......

        #     ;
        if configdrive.required_by(instance):
            LOG.info(_('Using config drive'), instance=instance)
            extra_md = {}
            if admin_pass:
                extra_md['admin_pass'] = admin_pass

            # InstanceMetadata:         ;
            #                    ;
            # instance:       ;
            # content=files:    ;
            # extra_md=extra_md:  ;
            inst_md = instance_metadata.InstanceMetadata(instance, content=files, extra_md=extra_md)
            
            # ConfigDriveBuilder:               ;
            #          ,         :
            #                  ;
            #   instance_md       ,     version         EC2        ;
            #                      ;
            
            #                 ,  ISO   vfat       ,   ISO   ;
            with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb:
                #              ;
                configdrive_path = basepath(fname='disk.config')
                LOG.info(_('Creating config drive at %(path)s'),
                         {'path': configdrive_path}, instance=instance)

                #         ,  ISO   vfat       ,   ISO   ;
                try:
                    cdb.make_drive(configdrive_path)
                except exception.ProcessExecutionError, e:
                    with excutils.save_and_reraise_exception():
                        LOG.error(_('Creating config drive failed '
                                  'with error: %s'),
                                  e, instance=instance)

まず文if configdrive.required_を経由するby(instance)は、システムが常にconfig driveを作成することを規定している場合、後続の駆動構成操作を行うと判断した.
次に文inst_を呼び出すmd = instance_metadata.InstanceMetadata(instance,content=files,extra_md=extra_md)はクラスInstanceMetadataの初期化を実現し、仮想マシンインスタンスメタデータ操作クラスのインスタンス化オブジェクトを取得する.
まず、クラスInstanceMetadataの初期化方法を見てみましょう.
class InstanceMetadata():
    """
            ;
    """

    def __init__(self, instance, address=None, content=[], extra_md=None,
                 conductor_api=None):
        #     ;
        self.instance = instance
        #     ;
        self.extra_md = extra_md

        #       “use_local”         conductor_api.LocalAPI   conductor_api.API;
        #            ;
        # LocalAPI :conductor API      ,                 ,     RPC;
        # API :     RPC            ;
        #         nova conductor  , Grizzly  Nova ,nova-conductor  nova-compute        ;
        #    nova-compute         ;
        if conductor_api:
            self.conductor_api = conductor_api
        else:
            self.conductor_api = conductor.API()

        #   admin      ;
        ctxt = context.get_admin_context()

        #  conductor_api.LocalAPI conductor_api.API      ;
        capi = self.conductor_api
        #   host      (zone);
        self.availability_zone = ec2utils.get_availability_zone_by_host(instance['host'], capi)
        #        IP     ;
        self.ip_info = ec2utils.get_ip_info_for_instance(ctxt, instance)
        #           ;
        self.security_groups = capi.security_group_get_by_instance(ctxt, instance)
        #     instance          ;
        self.mappings = _format_instance_mapping(capi, ctxt, instance)

        if instance.get('user_data', None) is not None:
            self.userdata_raw = base64.b64decode(instance['user_data'])
        else:
            self.userdata_raw = None

        self.ec2_ids = capi.get_ec2_ids(ctxt, instance)

        self.address = address

        # expose instance metadata.
        self.launch_metadata = {}
        for item in instance.get('metadata', []):
            self.launch_metadata[item['key']] = item['value']

        self.password = password.extract_password(instance)

        self.uuid = instance.get('uuid')

        self.content = {}
        self.files = []

        # get network info, and the rendered network template
        #   admin      ;
        ctxt = context.get_admin_context()
        #              ;
        #    instance                         ;
        network_info = network.API().get_instance_nw_info(ctxt, instance, conductor_api=capi)

        self.network_config = None
        #                      ;
        cfg = netutils.get_injected_network_template(network_info)

        if cfg:
            key = "%04i" % len(self.content)
            self.content[key] = cfg
            self.network_config = {"name": "network_config",
                'content_path': "/%s/%s" % (CONTENT_DIR, key)}

        for (path, contents) in content:
            key = "%04i" % len(self.content)
            self.files.append({'path': path,
                'content_path': "/%s/%s" % (CONTENT_DIR, key)})
            self.content[key] = contents

クラスの初期化方法はいくつかの変数とパラメータの初期化過程を完成し,その中の比較的重要ないくつかの文を解析した.
まず、次の文を見てみましょう.
if conductor_api:     self.conductor_api = conductor_api else:     self.conductor_api = conductor.API()
入力パラメータでconductor_を知るapiの値はnoneなのでself.conductor_を直接実行しますapi = conductor.API().この文はクラスAPIを初期化し、インスタンス化オブジェクトを取得します.具体的には、コード:
def API(*args, **kwargs):
    """
          “use_local”         conductor_api.LocalAPI   conductor_api.API;
               ;
    LocalAPI :conductor API      ,                 ,     RPC;
    API :     RPC            ;
            nova conductor  , Grizzly  Nova ,nova-conductor  nova-compute        ;
       nova-compute         ;
    """
    use_local = kwargs.pop('use_local', False)
    if oslo.config.cfg.CONF.conductor.use_local or use_local:
        api = conductor_api.LocalAPI
    else:
        api = conductor_api.API
    return api(*args, **kwargs)

コメントに書いてあるように、ここでは構成パラメータ「use_local」に基づいてクラスconductor_を具体的に選択して初期化するapi.LocalAPIまたはconductor_api.APIは、クラスのインスタンスオブジェクトを取得して返します.
ここで、LocalAPIクラスは、RPCではなくローカルデータベースの更新などの操作を処理するconductorAPIのローカルバージョンである.一方,APIクラスはRPCによりデータベースの更新などの操作を行っている.
ここではnova conductorサービスを簡単に説明する必要があります.Grizzly版のNovaでは、nova-conductorはnova-computeの上にある新しいサービス層であり、nova-computeがデータベースに直接アクセスしなくなります.
クラスInstanceMetadataの初期化方法に戻ると、多くの変数とパラメータの初期化賦値過程、特にデータベースクエリに関連する変数賦値は、nova-conductorというサービス層によって実現され、このサービス層はコードの拡張性とセキュリティを実質的に強化していることがわかります.
方法に戻りますcreate_イメージでは、configdrive.configDriveBuilder(instance_md=inst_md)という文を見てみましょう.この魚は、configDriveBuilderという構築構成ドライバのクラスをインスタンス化し、クラスの初期化インスタンスオブジェクトを取得することを実現しています.具体的には、このクラスの初期化方法のコードを見てみましょう.
class ConfigDriveBuilder(object):
    """
           ;
    """

    def __init__(self, instance_md=None):
        """
                         ;
          instance_md       ,     version         EC2        ;
                             ;
        """
        self.imagefile = None
        
        #                   ;
        # config_drive_tempdir:                         ;
        #        tempfile.tempdir,     NONE;
        self.tempdir = tempfile.mkdtemp(dir=CONF.config_drive_tempdir,
                                        prefix='cd_gen_')

        #  instance_md            tempdir ;
        
        #   instance_md       ,     version         EC2        ;
        #                      ;
        if instance_md is not None:
            self.add_instance_metadata(instance_md)

この初期化の過程で、最も重要な文はself.add_です.instance_メソッドaddを呼び出すことによってaddを呼び出すmetadata(instance_md)instance_metadataはinstanceを実現しますmdのメタデータは、駆動構成の一時ディレクトリファイルtempdirに書き込まれます.さらに方法add_を見てみましょうinstance_metadataの実装:
def add_instance_metadata(self, instance_md):
        """
               ,     version       EC2        ;
                             ;
        """
        # metadata_for_config_drive:       ,     version       EC2        ;
        #             json  ,         ,      (path,value)  ;
        
        #  data(     )              ;
        for (path, value) in instance_md.metadata_for_config_drive():
            self._add_file(path, value)
            LOG.debug(_('Added %(filepath)s to config drive'),
                      {'filepath': path})

方法を見てみましょうfor_config_drive:
def metadata_for_config_drive(self):
        """
        Yields (path, value) tuples for metadata elements.
               ,     version       EC2        ;
                    json  ,         ,      (path,value)  ;
        """
        
        # EC2 style metadata
        # EC2        ;
        #         ,         ,          (          );
        #                json  ,         ;
        for version in VERSIONS + ["latest"]:
            if version in CONF.config_drive_skip_versions.split(' '):
                continue

            #         ,   version         ;
            data = self.get_ec2_metadata(version)
            if 'user-data' in data:
                filepath = os.path.join('ec2', version, 'user-data')
                yield (filepath, data['user-data'])
                del data['user-data']

            try:
                del data['public-keys']['0']['_name']
            except KeyError:
                pass

            filepath = os.path.join('ec2', version, 'meta-data.json')
            yield (filepath, json.dumps(data['meta-data']))

        for version in OPENSTACK_VERSIONS + ["latest"]:
            path = 'openstack/%s/%s' % (version, MD_JSON_NAME)
            yield (path, self.lookup(path))

            path = 'openstack/%s/%s' % (version, UD_NAME)
            if self.userdata_raw is not None:
                yield (path, self.lookup(path))

        for (cid, content) in self.content.iteritems():
            yield ('%s/%s/%s' % ("openstack", CONTENT_DIR, cid), content)

以上の2つの方法add_instance_metadataとmetadata_for_config_driveは最終的にインスタンスメタデータの取得を実現し、バージョンバージョンバージョンに基づいて異なるバージョンのEC 2タイプのインスタンスメタデータを更新し、インスタンスメタデータを構成駆動の一時記憶ファイルに書き込む.ここでは,この2つの方法の具体的な実装をコード解析するのではなく,私のコード注釈を直接見ることができる.
これで、文configdrive.configDriveBuilder(instance_md=inst_md)の解析が完了します.
方法に戻りますcreate_イメージでは、駆動構成の実行コードを引き続き見ます.その後最も重要な文はcdb.make_ですdrive(configdrive_path).この文は、構成パラメータの選択に基づいてISO形式またはvfat形式のミラーファイルを生成することを実現し、デフォルトはISO形式です.
方法を具体的に見てみましょうDriveのコード:
def make_drive(self, path):
        """
        path:             ;
                ,  ISO   vfat       ,   ISO   ;
        """
        
        #               ,iso9660   vfat;
        #        iso9660;
        # _make_iso9660:  ISO       ;
        if CONF.config_drive_format == 'iso9660':
            self._make_iso9660(path)
        elif CONF.config_drive_format == 'vfat':
            self._make_vfat(path)
        else:
            raise exception.ConfigDriveUnknownFormat(
                format=CONF.config_drive_format)

構成パラメータconfig_drive_formatは、iso 9660またはvfatの構成ドライバのフォーマットを定義します.パラメータのデフォルト値はiso 9660なので、ここではデフォルト呼び出し方法_make_iso 9660は、ISO形式のミラーファイルを生成する.さらに見てみましょうmake_iso 9660のコード:
def _make_iso9660(self, path):
        """
          ISO       ;
        """
        publisher = "%(product)s %(version)s" % {
            'product': version.product_string(),
            'version': version.version_string_with_package()
            }

        utils.execute(CONF.mkisofs_cmd,
                      '-o', path,
                      '-ldots',
                      '-allow-lowercase',
                      '-allow-multidot',
                      '-l',
                      '-publisher',
                      publisher,
                      '-quiet',
                      '-J',
                      '-r',
                      '-V', 'config-2',
                      self.tempdir,
                      attempts=1,
                      run_as_root=False)

ここではパラメータCONF.mkisofsを構成します.cmdはコマンドgenisoimageを呼び出し、ISO形式のミラーファイルの作成を実現する.
これで、メソッド_create_イメージの第2部では,駆動構成の実装解析が完了する.(しかし理解が不十分)
3.ファイル注入部分コード解析
まず方法を見てみましょうcreate_imageでファイル注入部分を実現するコード:
def _create_image(self, context, instance, libvirt_xml,
                      disk_mapping, suffix='',
                      disk_images=None, network_info=None,
                      block_device_info=None, files=None, admin_pass=None):
        """
                  :
        # context:     ;
        # instance:    ;
        # libvirt_xml:               conf,       conf   xml  ;
        # disk_mapping=disk_info['mapping']:           ;
        # network_info=network_info:              ;
        # block_device_info=block_device_info:          ;
        # files=injected_files:        ;
        # admin_pass=admin_password:admin  ;
        # suffix='';
        # disk_images=None;
        """

        ......

        #     ;
        elif CONF.libvirt_inject_partition != -2:
            #            ;
            target_partition = None
            if not instance['kernel_id']:
                #     kernel_id  ;
                target_partition = CONF.libvirt_inject_partition
                if target_partition == 0:
                    target_partition = None
            #           lxc,         None;
            if CONF.libvirt_type == 'lxc':
                target_partition = None

            #           ssh  ,       'key_data'  ,     'key_data'  ;
            # libvirt_inject_key:           ,    ssh  ;
            #        True; 
            if CONF.libvirt_inject_key and instance['key_data']:
                key = str(instance['key_data'])
            else:
                key = None

            # get_injected_network_template:                     ;
            net = netutils.get_injected_network_template(network_info)

            #            metadata;
            metadata = instance.get('metadata')

            # libvirt_inject_password:           ,         ;
            #        False;
            if not CONF.libvirt_inject_password:
                admin_pass = None

            #   key, net, metadata, admin_pass, files     none,        ;
            if any((key, net, metadata, admin_pass, files)):
                # If we're not using config_drive, inject into root fs
                injection_path = image('disk').path
                img_id = instance['image_ref']

                for inj in ('key', 'net', 'metadata', 'admin_pass', 'files'):
                    if locals()[inj]:
                        LOG.info(_('Injecting %(inj)s into image '
                                   '%(img_id)s'), locals(), instance=instance)
                # inject_data:               ;
                # injection_path:            ;
                # key, net, metadata, admin_pass, files:      ;
                # partition=target_partition:         ;
                # use_cow=CONF.use_cow_images:           cow       ,    True;
                try:
                    disk.inject_data(injection_path,
                                     key, net, metadata, admin_pass, files,
                                     partition=target_partition,
                                     use_cow=CONF.use_cow_images,
                                     mandatory=('files',))
                except Exception as e:
                    with excutils.save_and_reraise_exception():
                        LOG.error(_('Error injecting data into image '
                                    '%(img_id)s (%(e)s)') % locals(),
                                  instance=instance)

実際、ファイル注入部分ではkey(ssh公開鍵)、net(レンダリングされたネットワークテンプレート)、metadata(仮想マシンインスタンスのメタデータ)、admin_pass(ユーザパスワード)やfiles(転送された符号化されたファイル)などは、確立されたディスクミラーに注入される.このプロセスを実現する最も重要な文は、
disk.inject_data(injection_path,                  key, net, metadata, admin_pass, files,                  partition=target_partition,                  use_cow=CONF.use_cow_images,                  mandatory=('files',))
さらに方法inject_を見てみましょうdataの具体的なコード実装:
def inject_data(image, key=None, net=None, metadata=None, admin_password=None,
                files=None, partition=None, use_cow=False, mandatory=()):
    """
                image;
    
    # image:            ;
    # key, net, metadata, admin_pass, files:      ;
    # partition:         ;
    # use_cow:           cow       ,    True;
    """
    LOG.debug(_("Inject data image=%(image)s key=%(key)s net=%(net)s "
                "metadata=%(metadata)s admin_password=ha-ha-not-telling-you "
                "files=%(files)s partition=%(partition)s use_cow=%(use_cow)s")
              % locals())
    fmt = "raw"
    if use_cow:
        fmt = "qcow2"
    try:
        fs = vfs.VFS.instance_for_image(image, fmt, partition)
        fs.setup()
    except Exception as e:
        for inject in mandatory:
            inject_val = locals()[inject]
            if inject_val:
                raise
        LOG.warn(_('Ignoring error injecting data into image '
                   '(%(e)s)') % locals())
        return False

    try:
        return inject_data_into_fs(fs, key, net, metadata, admin_password, files, mandatory)
    finally:
        fs.teardown()

このメソッドではまずメソッドinstance_を呼び出します.for_イメージインプリメンテーションは、マウントに確立されたミラーのためにディスクを準備し、メソッドsetupを呼び出してディスクのマウントを実現します.
再呼び出しメソッドinject_data_into_fsは関連ファイルのファイル注入を実現する.方法を見てみましょうdata_into_fsのコード:
def inject_data_into_fs(fs, key, net, metadata, admin_password, files, mandatory=()):
    """
                    ;
    """
    status = True
    for inject in ('key', 'net', 'metadata', 'admin_password', 'files'):
        inject_val = locals()[inject]
        inject_func = globals()['_inject_%s_into_fs' % inject]
        if inject_val:
            try:
                inject_func(inject_val, fs)
            except Exception as e:
                if inject in mandatory:
                    raise
                LOG.warn(_('Ignoring error injecting %(inject)s into image '
                           '(%(e)s)') % locals())
                status = False
    return status

この方法では、主に異なる文字列のマッチングによって異なる方法を呼び出し、ディスクミラーに異なるファイル情報を注入することを実現する.
例えば、この方法では呼び出し方法を具体的に実現することができる_inject_key_into_fs、_inject_net_into_fs、_inject_metadata_into_fs、_inject_admin_password_into_fsと_inject_files_into_fs,具体的にはkey,net,metadata,admin_を実現するpasswordやfilesなどのファイルの注入過程.
我々は方法でinject_net_into_fsを例に、ディスクミラーにファイルを注入する実装手順を解析します.見てみましょうinject_net_into_fsのコード実装:
def _inject_net_into_fs(net, fs):
    """
    Inject /etc/network/interfaces into the filesystem rooted at fs.
    net is the contents of /etc/network/interfaces.
        /etc/network/interfaces( net)     fs    ;
    """

    LOG.debug(_("Inject key fs=%(fs)s net=%(net)s") %
              locals())
    netdir = os.path.join('etc', 'network')
    fs.make_path(netdir)
    fs.set_ownership(netdir, "root", "root")
    fs.set_permissions(netdir, 0744)

    netfile = os.path.join('etc', 'network', 'interfaces')
    #                ;
    _inject_file_into_fs(fs, netfile, net)

この方法は、ファイル/etc/network/interfaces(すなわち、net)をファイルシステムfsのルートディレクトリに注入することを実現し、この方法では、方法_を具体的に呼び出す.inject_file_into_fsは、指定されたファイル情報をディスクミラーに注入することを実現する.
方法を見てみましょうinject_file_into_fsのコード実装:
def _inject_file_into_fs(fs, path, contents, append=False):
    """
                   ;
    """
    LOG.debug(_("Inject file fs=%(fs)s path=%(path)s append=%(append)s") %
              locals())
    #   contents path       ;
    if append:
        fs.append_file(path, contents)
    #  contents  path       ;
    else:
        fs.replace_file(path, contents)

この方法の実現は比較的理解しやすく,私のコード注釈を直接見ればよい.
これで、メソッド_create_イメージの第3部、すなわちファイル注入が完全に解析された.
よって、メソッド_create_イメージも解析済みです.
次のブログOpenStack Libvirtに基づく仮想化プラットフォームスケジューリング実装------Nova仮想マシン起動ソース実装(4)では,メソッドspawnに戻り,Nova仮想マシン起動ソース実装の解析を継続する