Nova 啓動虛擬機流程解析

目錄

前言

Nova 啓動虛擬機的東西太多,持續更新…node

從請求提及

不管是經過 Dashboard 仍是 CLI 啓動一個虛擬機,發送的是 POST /servers請求,改與該請求的 Body 詳情,能夠瀏覽官方文檔 Create serverpython

nova-api service 階段

Nova API Service 本質是一個 WSGI Application,採用了 Paste + PasteDeploy + Routes + WebOb 框架,簡稱 PPRW。關於架構的實現不屬於本文範疇,因此直接看接收到 POST /servers 請求以後的處理函數(View Method)createweb

NOTE:首先須要說明的是,下文中全部的代碼解析均直接經過註釋的方式呈現完成。數據庫

# File: /opt/stack/nova/nova/api/openstack/compute/servers.py

    @wsgi.response(202)
    @wsgi.expected_errors((400, 403, 409))
    @validation.schema(schema_servers.base_create_v20, '2.0', '2.0')
    @validation.schema(schema_servers.base_create, '2.1', '2.18')
    @validation.schema(schema_servers.base_create_v219, '2.19', '2.31')
    @validation.schema(schema_servers.base_create_v232, '2.32', '2.32')
    @validation.schema(schema_servers.base_create_v233, '2.33', '2.36')
    @validation.schema(schema_servers.base_create_v237, '2.37', '2.41')
    @validation.schema(schema_servers.base_create_v242, '2.42', '2.51')
    @validation.schema(schema_servers.base_create_v252, '2.52', '2.56')
    @validation.schema(schema_servers.base_create_v257, '2.57', '2.62')
    @validation.schema(schema_servers.base_create_v263, '2.63', '2.66')
    @validation.schema(schema_servers.base_create_v267, '2.67')
    def create(self, req, body):
        """Creates a new server for a given user."""
        context = req.environ['nova.context']
        server_dict = body['server']
        
        # 用戶設置的虛擬機密碼
        password = self._get_server_admin_password(server_dict)
        
        name = common.normalize_name(server_dict['name'])
        description = name
        if api_version_request.is_supported(req, min_version='2.19'):
            description = server_dict.get('description')

        # Arguments to be passed to instance create function
        create_kwargs = {}

        # 須要注入到虛擬機的 user_data 數據(Configuration information or scripts to use upon launch.)
        create_kwargs['user_data'] = server_dict.get('user_data')
        
        # NOTE(alex_xu): The v2.1 API compat mode, we strip the spaces for
        # keypair create. But we didn't strip spaces at here for
        # backward-compatible some users already created keypair and name with
        # leading/trailing spaces by legacy v2 API.
      
        # keypair 名稱
        create_kwargs['key_name'] = server_dict.get('key_name')

        # 是否啓用 config_drive(布爾值)
        create_kwargs['config_drive'] = server_dict.get('config_drive')

        # 虛擬機安全組
        security_groups = server_dict.get('security_groups')
        if security_groups is not None:
            create_kwargs['security_groups'] = [
                sg['name'] for sg in security_groups if sg.get('name')]
            create_kwargs['security_groups'] = list(
                set(create_kwargs['security_groups']))

        # 虛擬機調度提示信息,是一種高級的調度因子
        scheduler_hints = {}
        if 'os:scheduler_hints' in body:
            scheduler_hints = body['os:scheduler_hints']
        elif 'OS-SCH-HNT:scheduler_hints' in body:
            scheduler_hints = body['OS-SCH-HNT:scheduler_hints']
        create_kwargs['scheduler_hints'] = scheduler_hints

        # min_count and max_count are optional. If they exist, they may come
        # in as strings. Verify that they are valid integers and > 0.
        # Also, we want to default 'min_count' to 1, and default
        # 'max_count' to be 'min_count'.
        min_count = int(server_dict.get('min_count', 1))
        max_count = int(server_dict.get('max_count', min_count))
        return_id = server_dict.get('return_reservation_id', False)
        if min_count > max_count:
            msg = _('min_count must be <= max_count')
            raise exc.HTTPBadRequest(explanation=msg)
        create_kwargs['min_count'] = min_count
        create_kwargs['max_count'] = max_count
        create_kwargs['return_reservation_id'] = return_id

        # 指定可用域
        availability_zone = server_dict.pop("availability_zone", None)

        # 虛擬機 tags 信息
        if api_version_request.is_supported(req, min_version='2.52'):
            create_kwargs['tags'] = server_dict.get('tags')

        helpers.translate_attributes(helpers.CREATE,
                                     server_dict, create_kwargs)

        target = {
            'project_id': context.project_id,
            'user_id': context.user_id,
            'availability_zone': availability_zone}
        # 驗證 target 是否支持 create 操做
        context.can(server_policies.SERVERS % 'create', target)

        # Skip policy check for 'create:trusted_certs' if no trusted
        # certificate IDs were provided.
        
        # 鏡像證書
        trusted_certs = server_dict.get('trusted_image_certificates', None)
        if trusted_certs:
            create_kwargs['trusted_certs'] = trusted_certs
            context.can(server_policies.SERVERS % 'create:trusted_certs',
                        target=target)

        # TODO(Shao He, Feng) move this policy check to os-availability-zone
        # extension after refactor it.
        parse_az = self.compute_api.parse_availability_zone
        try:
            # 解析 --availability-zone AZ1:Compute1:Hypervisor1 參數
            availability_zone, host, node = parse_az(context,
                                                     availability_zone)
        except exception.InvalidInput as err:
            raise exc.HTTPBadRequest(explanation=six.text_type(err))
        if host or node:
            context.can(server_policies.SERVERS % 'create:forced_host', {})

        # NOTE(danms): Don't require an answer from all cells here, as
        # we assume that if a cell isn't reporting we won't schedule into
        # it anyway. A bit of a gamble, but a reasonable one.
        min_compute_version = service_obj.get_minimum_version_all_cells(
            nova_context.get_admin_context(), ['nova-compute'])
        # 是否支持 device tagging 功能
        supports_device_tagging = (min_compute_version >=
                                   DEVICE_TAGGING_MIN_COMPUTE_VERSION)

        # 兩個 Boot from volume 的 block device mapping 版本
        block_device_mapping_legacy = server_dict.get('block_device_mapping',
                                                      [])
        block_device_mapping_v2 = server_dict.get('block_device_mapping_v2',
                                                  [])

        if block_device_mapping_legacy and block_device_mapping_v2:
            expl = _('Using different block_device_mapping syntaxes '
                     'is not allowed in the same request.')
            raise exc.HTTPBadRequest(explanation=expl)

        if block_device_mapping_legacy:
            for bdm in block_device_mapping_legacy:
                if 'delete_on_termination' in bdm:
                    bdm['delete_on_termination'] = strutils.bool_from_string(
                        bdm['delete_on_termination'])
            create_kwargs[
                'block_device_mapping'] = block_device_mapping_legacy
            # Sets the legacy_bdm flag if we got a legacy block device mapping.
            create_kwargs['legacy_bdm'] = True
        elif block_device_mapping_v2:
            # Have to check whether --image is given, see bug 1433609
           
            image_href = server_dict.get('imageRef')
            image_uuid_specified = image_href is not None
            
            try:
                # Transform the API format of data to the internally used one.
                # block_device_mapping_v2 的數據結構:
                # "block_device_mapping_v2": [{
                # "boot_index": "0",
                # "uuid": "ac408821-c95a-448f-9292-73986c790911",
                # "source_type": "image",
                # "volume_size": "25",
                # "destination_type": "volume",
                # "delete_on_termination": true,
                # "tag": "disk1",
                # "disk_bus": "scsi"}]
                block_device_mapping = [
                    block_device.BlockDeviceDict.from_api(bdm_dict,
                        image_uuid_specified)
                    for bdm_dict in block_device_mapping_v2]
            except exception.InvalidBDMFormat as e:
                raise exc.HTTPBadRequest(explanation=e.format_message())
            create_kwargs['block_device_mapping'] = block_device_mapping
            # Unset the legacy_bdm flag if we got a block device mapping.
            create_kwargs['legacy_bdm'] = False

        block_device_mapping = create_kwargs.get("block_device_mapping")
        if block_device_mapping:
            # 檢查 target 是否支持 create:attach_volume 操做
            context.can(server_policies.SERVERS % 'create:attach_volume',
                        target)
            for bdm in block_device_mapping:
                if bdm.get('tag', None) and not supports_device_tagging:
                    msg = _('Block device tags are not yet supported.')
                    raise exc.HTTPBadRequest(explanation=msg)

        # 獲取指定的 image uuid
        # 若是沒有 image_href 而且存在 block_device_mapping 則返回 '' 空字符串,Boot from volume 就不須要指定 Image 了
        # 若是有 image_href,則 image_uuid = image_href
        image_uuid = self._image_from_req_data(server_dict, create_kwargs)

        # NOTE(cyeoh): Although upper layer can set the value of
        # return_reservation_id in order to request that a reservation
        # id be returned to the client instead of the newly created
        # instance information we do not want to pass this parameter
        # to the compute create call which always returns both. We use
        # this flag after the instance create call to determine what
        # to return to the client
        return_reservation_id = create_kwargs.pop('return_reservation_id',
                                                  False)
                                                  
        # 經過 networks attribute 建立一個 list of requested networks
        requested_networks = server_dict.get('networks', None)
        if requested_networks is not None:
            requested_networks = self._get_requested_networks(
                requested_networks, supports_device_tagging)

        # Skip policy check for 'create:attach_network' if there is no
        # network allocation request.
        if requested_networks and len(requested_networks) and \
                not requested_networks.no_allocate:
            context.can(server_policies.SERVERS % 'create:attach_network',
                        target)

        # 獲取 flavor object(DB)
        flavor_id = self._flavor_id_from_req_data(body)
        try:
            inst_type = flavors.get_flavor_by_flavor_id(
                    flavor_id, ctxt=context, read_deleted="no")

            # 是否支持 Cinder Mulit-Attach
            supports_multiattach = common.supports_multiattach_volume(req)
            # 跳轉到 Compute API 處理,實際上後來是繼續跳轉到 Conductor 了。
            (instances, resv_id) = self.compute_api.create(context,
                            inst_type,
                            image_uuid,
                            display_name=name,
                            display_description=description,
                            availability_zone=availability_zone,
                            forced_host=host, forced_node=node,
                            metadata=server_dict.get('metadata', {}),
                            admin_password=password,
                            requested_networks=requested_networks,
                            check_server_group_quota=True,
                            supports_multiattach=supports_multiattach,
                            **create_kwargs)
        except (exception.QuotaError,
                exception.PortLimitExceeded) as error:
            raise exc.HTTPForbidden(
                explanation=error.format_message())
        except exception.ImageNotFound:
            msg = _("Can not find requested image")
            raise exc.HTTPBadRequest(explanation=msg)
        except exception.KeypairNotFound:
            msg = _("Invalid key_name provided.")
            raise exc.HTTPBadRequest(explanation=msg)
        except exception.ConfigDriveInvalidValue:
            msg = _("Invalid config_drive provided.")
            raise exc.HTTPBadRequest(explanation=msg)
        except (exception.BootFromVolumeRequiredForZeroDiskFlavor,
                exception.ExternalNetworkAttachForbidden) as error:
            raise exc.HTTPForbidden(explanation=error.format_message())
        except messaging.RemoteError as err:
            msg = "%(err_type)s: %(err_msg)s" % {'err_type': err.exc_type,
                                                 'err_msg': err.value}
            raise exc.HTTPBadRequest(explanation=msg)
        except UnicodeDecodeError as error:
            msg = "UnicodeError: %s" % error
            raise exc.HTTPBadRequest(explanation=msg)
        except (exception.CPUThreadPolicyConfigurationInvalid,
                exception.ImageNotActive,
                exception.ImageBadRequest,
                exception.ImageNotAuthorized,
                exception.FixedIpNotFoundForAddress,
                exception.FlavorNotFound,
                exception.FlavorDiskTooSmall,
                exception.FlavorMemoryTooSmall,
                exception.InvalidMetadata,
                exception.InvalidRequest,
                exception.InvalidVolume,
                exception.MultiplePortsNotApplicable,
                exception.InvalidFixedIpAndMaxCountRequest,
                exception.InstanceUserDataMalformed,
                exception.PortNotFound,
                exception.FixedIpAlreadyInUse,
                exception.SecurityGroupNotFound,
                exception.PortRequiresFixedIP,
                exception.NetworkRequiresSubnet,
                exception.NetworkNotFound,
                exception.InvalidBDM,
                exception.InvalidBDMSnapshot,
                exception.InvalidBDMVolume,
                exception.InvalidBDMImage,
                exception.InvalidBDMBootSequence,
                exception.InvalidBDMLocalsLimit,
                exception.InvalidBDMVolumeNotBootable,
                exception.InvalidBDMEphemeralSize,
                exception.InvalidBDMFormat,
                exception.InvalidBDMSwapSize,
                exception.VolumeTypeNotFound,
                exception.AutoDiskConfigDisabledByImage,
                exception.ImageCPUPinningForbidden,
                exception.ImageCPUThreadPolicyForbidden,
                exception.ImageNUMATopologyIncomplete,
                exception.ImageNUMATopologyForbidden,
                exception.ImageNUMATopologyAsymmetric,
                exception.ImageNUMATopologyCPUOutOfRange,
                exception.ImageNUMATopologyCPUDuplicates,
                exception.ImageNUMATopologyCPUsUnassigned,
                exception.ImageNUMATopologyMemoryOutOfRange,
                exception.InvalidNUMANodesNumber,
                exception.InstanceGroupNotFound,
                exception.MemoryPageSizeInvalid,
                exception.MemoryPageSizeForbidden,
                exception.PciRequestAliasNotDefined,
                exception.RealtimeConfigurationInvalid,
                exception.RealtimeMaskNotFoundOrInvalid,
                exception.SnapshotNotFound,
                exception.UnableToAutoAllocateNetwork,
                exception.MultiattachNotSupportedOldMicroversion,
                exception.CertificateValidationFailed) as error:
            raise exc.HTTPBadRequest(explanation=error.format_message())
        except (exception.PortInUse,
                exception.InstanceExists,
                exception.NetworkAmbiguous,
                exception.NoUniqueMatch,
                exception.MultiattachSupportNotYetAvailable,
                exception.VolumeTypeSupportNotYetAvailable,
                exception.CertificateValidationNotYetAvailable) as error:
            raise exc.HTTPConflict(explanation=error.format_message())

        # If the caller wanted a reservation_id, return it
        if return_reservation_id:
            return wsgi.ResponseObject({'reservation_id': resv_id})

        server = self._view_builder.create(req, instances[0])

        # Enables returning of the instance password by the relevant server API calls
        # such as create, rebuild, evacuate, or rescue. 
        if CONF.api.enable_instance_password:
            server['server']['adminPass'] = password

        robj = wsgi.ResponseObject(server)

        return self._add_location(robj)

上述的參數基本上能夠與 Dashboard 上的 Form 表單一一對應:
在這裏插入圖片描述api

# File: /opt/stack/nova/nova/compute/api.py

    @hooks.add_hook("create_instance")
    def create(self, context, instance_type,
               image_href, kernel_id=None, ramdisk_id=None,
               min_count=None, max_count=None,
               display_name=None, display_description=None,
               key_name=None, key_data=None, security_groups=None,
               availability_zone=None, forced_host=None, forced_node=None,
               user_data=None, metadata=None, injected_files=None,
               admin_password=None, block_device_mapping=None,
               access_ip_v4=None, access_ip_v6=None, requested_networks=None,
               config_drive=None, auto_disk_config=None, scheduler_hints=None,
               legacy_bdm=True, shutdown_terminate=False,
               check_server_group_quota=False, tags=None,
               supports_multiattach=False, trusted_certs=None):
        """Provision instances, sending instance information to the scheduler. The scheduler will determine where the instance(s) go and will handle creating the DB entries. Returns a tuple of (instances, reservation_id) """
        
        if requested_networks and max_count is not None and max_count > 1:
            # 驗證不能指定一個 IP 地址來建立多個虛擬機
            self._check_multiple_instances_with_specified_ip(
                requested_networks)
            if utils.is_neutron():
                # # 驗證不能指定一個 Port 來建立多個虛擬機
                self._check_multiple_instances_with_neutron_ports(
                    requested_networks)

        # 驗證指定的 AZ 是否爲可用域
        if availability_zone:
            available_zones = availability_zones.\
                get_availability_zones(context.elevated(), True)
            if forced_host is None and availability_zone not in \
                    available_zones:
                msg = _('The requested availability zone is not available')
                raise exception.InvalidRequest(msg)

        # filter_properties 就是一個 Dict 類型對象,如今包含了實參的列表的內容。
        filter_properties = scheduler_utils.build_filter_properties(
                scheduler_hints, forced_host, forced_node, instance_type)

        return self._create_instance(
                       context, instance_type,
                       image_href, kernel_id, ramdisk_id,
                       min_count, max_count,
                       display_name, display_description,
                       key_name, key_data, security_groups,
                       availability_zone, user_data, metadata,
                       injected_files, admin_password,
                       access_ip_v4, access_ip_v6,
                       requested_networks, config_drive,
                       block_device_mapping, auto_disk_config,
                       filter_properties=filter_properties,
                       legacy_bdm=legacy_bdm,
                       shutdown_terminate=shutdown_terminate,
                       check_server_group_quota=check_server_group_quota,
                       tags=tags, supports_multiattach=supports_multiattach,
                       trusted_certs=trusted_certs)

    def _create_instance(self, context, instance_type,
               image_href, kernel_id, ramdisk_id,
               min_count, max_count,
               display_name, display_description,
               key_name, key_data, security_groups,
               availability_zone, user_data, metadata, injected_files,
               admin_password, access_ip_v4, access_ip_v6,
               requested_networks, config_drive,
               block_device_mapping, auto_disk_config, filter_properties,
               reservation_id=None, legacy_bdm=True, shutdown_terminate=False,
               check_server_group_quota=False, tags=None,
               supports_multiattach=False, trusted_certs=None):
        """Verify all the input parameters regardless of the provisioning strategy being performed and schedule the instance(s) for creation. """

        # Normalize and setup some parameters
        if reservation_id is None:
            reservation_id = utils.generate_uid('r')
        security_groups = security_groups or ['default']
        min_count = min_count or 1
        max_count = max_count or min_count
        block_device_mapping = block_device_mapping or []
        tags = tags or []

        if image_href:
            # 獲取 Image Id 和 Image Metadata
            image_id, boot_meta = self._get_image(context, image_href)
        else:
            # This is similar to the logic in _retrieve_trusted_certs_object.
            if (trusted_certs or
                (CONF.glance.verify_glance_signatures and
                 CONF.glance.enable_certificate_validation and
                 CONF.glance.default_trusted_certificate_ids)):
                msg = _("Image certificate validation is not supported "
                        "when booting from volume")
                raise exception.CertificateValidationFailed(message=msg)
            image_id = None
            # 若是沒有指定 Image 的話就獲取 block device 的 Metadata
            boot_meta = self._get_bdm_image_metadata(
                context, block_device_mapping, legacy_bdm)

        # ???
        self._check_auto_disk_config(image=boot_meta,
                                     auto_disk_config=auto_disk_config)

        # 進一步驗證和轉換虛擬機建立參數
        base_options, max_net_count, key_pair, security_groups, \
            network_metadata = self._validate_and_build_base_options(
                    context, instance_type, boot_meta, image_href, image_id,
                    kernel_id, ramdisk_id, display_name, display_description,
                    key_name, key_data, security_groups, availability_zone,
                    user_data, metadata, access_ip_v4, access_ip_v6,
                    requested_networks, config_drive, auto_disk_config,
                    reservation_id, max_count)
                    
        # max_net_count is the maximum number of instances requested by the
        # user adjusted for any network quota constraints, including
        # consideration of connections to each requested network
        # 若是最大的 network 數量小於最小的虛擬機數量,則觸發異常
        if max_net_count < min_count:
            raise exception.PortLimitExceeded()
        elif max_net_count < max_count:
            LOG.info("max count reduced from %(max_count)d to "
                     "%(max_net_count)d due to network port quota",
                     {'max_count': max_count,
                      'max_net_count': max_net_count})
            max_count = max_net_count

        # 進一步檢查 Boot from volume 的 block device 的可用性
        block_device_mapping = self._check_and_transform_bdm(context,
            base_options, instance_type, boot_meta, min_count, max_count,
            block_device_mapping, legacy_bdm)

        # We can't do this check earlier because we need bdms from all sources
        # to have been merged in order to get the root bdm.
        
        # 進一步檢查 quota 和 image 的可用性
        self._checks_for_create_and_rebuild(context, image_id, boot_meta,
                instance_type, metadata, injected_files,
                block_device_mapping.root_bdm())

        # 轉換爲多個虛擬機爲 InstanceGroup 數據結構
        instance_group = self._get_requested_instance_group(context,
                                   filter_properties)
        # 轉換爲 TagList 數據結構
        tags = self._create_tag_list_obj(context, tags)

        # 將諸多建立參數封裝到 instances_to_build 列表類型對象中
        instances_to_build = self._provision_instances(
            context, instance_type, min_count, max_count, base_options,
            boot_meta, security_groups, block_device_mapping,
            shutdown_terminate, instance_group, check_server_group_quota,
            filter_properties, key_pair, tags, trusted_certs,
            supports_multiattach, network_metadata)

        instances = []
        request_specs = []
        build_requests = []
        for rs, build_request, im in instances_to_build:
            build_requests.append(build_request)
            instance = build_request.get_new_instance(context)
            instances.append(instance)
            request_specs.append(rs)

        if CONF.cells.enable:
            # NOTE(danms): CellsV1 can't do the new thing, so we
            # do the old thing here. We can remove this path once
            # we stop supporting v1.
            for instance in instances:
                # 建立 Instance 數據庫對象
                instance.create()
            # NOTE(melwitt): We recheck the quota after creating the objects
            # to prevent users from allocating more resources than their
            # allowed quota in the event of a race. This is configurable
            # because it can be expensive if strict quota limits are not
            # required in a deployment.
            # 再一次檢查資源配額
            if CONF.quota.recheck_quota:
                try:
                    compute_utils.check_num_instances_quota(
                        context, instance_type, 0, 0,
                        orig_num_req=len(instances))
                except exception.TooManyInstances:
                    with excutils.save_and_reraise_exception():
                        # Need to clean up all the instances we created
                        # along with the build requests, request specs,
                        # and instance mappings.
                        self._cleanup_build_artifacts(instances,
                                                      instances_to_build)
 
            self.compute_task_api.build_instances(context,
                instances=instances, image=boot_meta,
                filter_properties=filter_properties,
                admin_password=admin_password,
                injected_files=injected_files,
                requested_networks=requested_networks,
                security_groups=security_groups,
                block_device_mapping=block_device_mapping,
                legacy_bdm=False)
        else:
            self.compute_task_api.schedule_and_build_instances(
                context,
                build_requests=build_requests,
                request_spec=request_specs,
                image=boot_meta,
                admin_password=admin_password,
                injected_files=injected_files,
                requested_networks=requested_networks,
                block_device_mapping=block_device_mapping,
                tags=tags)

        return instances, reservation_id

    def _validate_and_build_base_options(self, context, instance_type,
                                         boot_meta, image_href, image_id,
                                         kernel_id, ramdisk_id, display_name,
                                         display_description, key_name,
                                         key_data, security_groups,
                                         availability_zone, user_data,
                                         metadata, access_ip_v4, access_ip_v6,
                                         requested_networks, config_drive,
                                         auto_disk_config, reservation_id,
                                         max_count):
        """Verify all the input parameters regardless of the provisioning strategy being performed. """
        # 肯定 Flavor 可用
        if instance_type['disabled']:
            raise exception.FlavorNotFound(flavor_id=instance_type['id'])

        # 肯定 user_data 數據編碼格式爲 Base64
        if user_data:
            try:
                base64utils.decode_as_bytes(user_data)
            except TypeError:
                raise exception.InstanceUserDataMalformed()

        # When using Neutron, _check_requested_secgroups will translate and
        # return any requested security group names to uuids.
        security_groups = (
            self._check_requested_secgroups(context, security_groups))

        # Note: max_count is the number of instances requested by the user,
        # max_network_count is the maximum number of instances taking into
        # account any network quotas
        max_network_count = self._check_requested_networks(context,
                                     requested_networks, max_count)

        # Choose kernel and ramdisk appropriate for the instance.
        # ramdisk(虛擬內存盤)是一種將內存模擬成硬盤來使用的技術,能夠在內存啓動虛擬機。
        kernel_id, ramdisk_id = self._handle_kernel_and_ramdisk(
                context, kernel_id, ramdisk_id, boot_meta)

        # 依舊返回是布爾值類型
        config_drive = self._check_config_drive(config_drive)

        # 經過 keypair name 獲取 keypair data
        if key_data is None and key_name is not None:
            key_pair = objects.KeyPair.get_by_name(context,
                                                   context.user_id,
                                                   key_name)
            key_data = key_pair.public_key
        else:
            key_pair = None

        # 獲取系統盤設備的名稱(可能從 Image 獲取或者從 Volume 獲取)
        root_device_name = block_device.prepend_dev(
                block_device.properties_root_device_name(
                    boot_meta.get('properties', {})))

        try:
            # 此處的 image 是一個抽象概念,表示啓動操做系統的系統盤。將 Image File 和 Volume 統一爲 ImageMeta 對象。
            image_meta = objects.ImageMeta.from_dict(boot_meta)
        except ValueError as e:
            # there must be invalid values in the image meta properties so
            # consider this an invalid request
            msg = _('Invalid image metadata. Error: %s') % six.text_type(e)
            raise exception.InvalidRequest(msg)

        # 從 Flavor properties 和 Image properties 獲取 NUMA 屬性
        numa_topology = hardware.numa_get_constraints(
                instance_type, image_meta)

        system_metadata = {}

        # PCI requests come from two sources: instance flavor and
        # requested_networks. The first call in below returns an
        # InstancePCIRequests object which is a list of InstancePCIRequest
        # objects. The second call in below creates an InstancePCIRequest
        # object for each SR-IOV port, and append it to the list in the
        # InstancePCIRequests object

        # PCI requests come from two sources: instance flavor and requested_networks. 
        # 從 Flavor 獲取 PCI 設備
        pci_request_info = pci_request.get_pci_requests_from_flavor(
            instance_type)
        # ???
        network_metadata = self.network_api.create_resource_requests(
            context, requested_networks, pci_request_info)

        # base_options 的數據基本就是 instances 數據庫表的屬性。
        base_options = {
            'reservation_id': reservation_id,
            'image_ref': image_href,
            'kernel_id': kernel_id or '',
            'ramdisk_id': ramdisk_id or '',
            'power_state': power_state.NOSTATE,
            'vm_state': vm_states.BUILDING,
            'config_drive': config_drive,
            'user_id': context.user_id,
            'project_id': context.project_id,
            'instance_type_id': instance_type['id'],
            'memory_mb': instance_type['memory_mb'],
            'vcpus': instance_type['vcpus'],
            'root_gb': instance_type['root_gb'],
            'ephemeral_gb': instance_type['ephemeral_gb'],
            'display_name': display_name,
            'display_description': display_description,
            'user_data': user_data,
            'key_name': key_name,
            'key_data': key_data,
            'locked': False,
            'metadata': metadata or {},
            'access_ip_v4': access_ip_v4,
            'access_ip_v6': access_ip_v6,
            'availability_zone': availability_zone,
            'root_device_name': root_device_name,
            'progress': 0,
            'pci_requests': pci_request_info,
            'numa_topology': numa_topology,
            'system_metadata': system_metadata}

        options_from_image = self._inherit_properties_from_image(
                boot_meta, auto_disk_config)

        base_options.update(options_from_image)

        # return the validated options and maximum number of instances allowed
        # by the network quotas
        return (base_options, max_network_count, key_pair, security_groups,
                network_metadata)

    def _provision_instances(self, context, instance_type, min_count,
            max_count, base_options, boot_meta, security_groups,
            block_device_mapping, shutdown_terminate,
            instance_group, check_server_group_quota, filter_properties,
            key_pair, tags, trusted_certs, supports_multiattach,
            network_metadata=None):
        # Check quotas
        num_instances = compute_utils.check_num_instances_quota(
                context, instance_type, min_count, max_count)
        security_groups = self.security_group_api.populate_security_groups(
                security_groups)
        self.security_group_api.ensure_default(context)
        LOG.debug("Going to run %s instances...", num_instances)
        instances_to_build = []
        try:
            for i in range(num_instances):
                # Create a uuid for the instance so we can store the
                # RequestSpec before the instance is created.
                instance_uuid = uuidutils.generate_uuid()
                # Store the RequestSpec that will be used for scheduling.
                # 將於 scheduling 相關的參數都封裝到 RequestSpec 對象,便於在 Nova Scheduler 中應用。
                req_spec = objects.RequestSpec.from_components(context,
                        instance_uuid, boot_meta, instance_type,
                        base_options['numa_topology'],
                        base_options['pci_requests'], filter_properties,
                        instance_group, base_options['availability_zone'],
                        security_groups=security_groups)

                # 若是是 Boot from Volume,則使用系統盤做爲操做系統的根盤
                if block_device_mapping:
                    # Record whether or not we are a BFV instance
                    root = block_device_mapping.root_bdm()
                    # 標記爲 Boot from volume
                    req_spec.is_bfv = bool(root and root.is_volume)
                else:
                    # If we have no BDMs, we're clearly not BFV
                    req_spec.is_bfv = False

                # NOTE(danms): We need to record num_instances on the request
                # spec as this is how the conductor knows how many were in this
                # batch.
                req_spec.num_instances = num_instances
                # 建立 RequestSpec 數據庫記錄
                req_spec.create()

                # NOTE(stephenfin): The network_metadata field is not persisted
                # and is therefore set after 'create' is called.
                if network_metadata:
                    req_spec.network_metadata = network_metadata

                # Create an instance object, but do not store in db yet.
                instance = objects.Instance(context=context)
                instance.uuid = instance_uuid
                instance.update(base_options)
                instance.keypairs = objects.KeyPairList(objects=[])
                if key_pair:
                    instance.keypairs.objects.append(key_pair)

                instance.trusted_certs = self._retrieve_trusted_certs_object(
                    context, trusted_certs)

                instance = self.create_db_entry_for_new_instance(context,
                        instance_type, boot_meta, instance, security_groups,
                        block_device_mapping, num_instances, i,
                        shutdown_terminate, create_instance=False)
                        
                # 肯定 Block Device 時可用的,並設定其 Size 且與 Instance 關聯起來
                block_device_mapping = (
                    self._bdm_validate_set_size_and_instance(context,
                        instance, instance_type, block_device_mapping,
                        supports_multiattach))
                instance_tags = self._transform_tags(tags, instance.uuid)

                # 將於虛擬機建立(啓動)相關的參數封裝到 BuildRequest 對象
                build_request = objects.BuildRequest(context,
                        instance=instance, instance_uuid=instance.uuid,
                        project_id=instance.project_id,
                        block_device_mappings=block_device_mapping,
                        tags=instance_tags)
                build_request.create()

                # Create an instance_mapping. The null cell_mapping indicates
                # that the instance doesn't yet exist in a cell, and lookups
                # for it need to instead look for the RequestSpec.
                # cell_mapping will be populated after scheduling, with a
                # scheduling failure using the cell_mapping for the special
                # cell0.
                
                # 將於虛擬機定位(cells)相關的參數封裝到 InstanceMapping 對象
                inst_mapping = objects.InstanceMapping(context=context)
                inst_mapping.instance_uuid = instance_uuid
                inst_mapping.project_id = context.project_id
                inst_mapping.cell_mapping = None
                inst_mapping.create()

                instances_to_build.append(
                    (req_spec, build_request, inst_mapping))

                if instance_group:
                    if check_server_group_quota:
                        try:
                            objects.Quotas.check_deltas(
                                context, {'server_group_members': 1},
                                instance_group, context.user_id)
                        except exception.OverQuota:
                            msg = _("Quota exceeded, too many servers in "
                                    "group")
                            raise exception.QuotaError(msg)

                    members = objects.InstanceGroup.add_members(
                        context, instance_group.uuid, [instance.uuid])

                    # NOTE(melwitt): We recheck the quota after creating the
                    # object to prevent users from allocating more resources
                    # than their allowed quota in the event of a race. This is
                    # configurable because it can be expensive if strict quota
                    # limits are not required in a deployment.
                    if CONF.quota.recheck_quota and check_server_group_quota:
                        try:
                            objects.Quotas.check_deltas(
                                context, {'server_group_members': 0},
                                instance_group, context.user_id)
                        except exception.OverQuota:
                            objects.InstanceGroup._remove_members_in_db(
                                context, instance_group.id, [instance.uuid])
                            msg = _("Quota exceeded, too many servers in "
                                    "group")
                            raise exception.QuotaError(msg)
                    # list of members added to servers group in this iteration
                    # is needed to check quota of server group during add next
                    # instance
                    instance_group.members.extend(members)

        # In the case of any exceptions, attempt DB cleanup
        except Exception:
            with excutils.save_and_reraise_exception():
                self._cleanup_build_artifacts(None, instances_to_build)

        return instances_to_build

至此,POST /servers 在 nova-api service 的工做流程基本就完成了,總的來講主要作了兩件事情:校驗轉換安全

  • API 接口的版本控制、數據類型校驗(e.g. validation.schema)
  • 用戶操做意圖許可校驗(e.g. context.can、supports_device_tagging、supports_multiattach)
  • 虛擬機關聯資源的可用性校驗
  • 用戶輸入數據整理、概括、分類並轉化爲程序內部使用的數據結構(對象)

後面經過 self.compute_task_api 調用進入到 nova-conductor service 的工做流程。數據結構

相關文章
相關標籤/搜索