Nova 启动虚拟机流程解析
生活随笔
收集整理的這篇文章主要介紹了
Nova 启动虚拟机流程解析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
目錄
文章目錄
- 目錄
- 前言
- 從請求說起
- nova-api service 階段
前言
Nova 啟動虛擬機的東西太多,持續更新…
從請求說起
無論是通過 Dashboard 還是 CLI 啟動一個虛擬機,發送的是 POST /servers請求,改與該請求的 Body 詳情,可以瀏覽官方文檔 Create server。
nova-api service 階段
Nova API Service 本質是一個 WSGI Application,采用了 Paste + PasteDeploy + Routes + WebOb 框架,簡稱 PPRW。關于架構的實現不屬于本文范疇,所以直接看接收到 POST /servers 請求之后的處理函數(View Method)create。
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 = nameif api_version_request.is_supported(req, min_version='2.19'):description = server_dict.get('description')# Arguments to be passed to instance create functioncreate_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_countcreate_kwargs['max_count'] = max_countcreate_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_certscontext.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_zonetry:# 解析 --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'] = Trueelif block_device_mapping_v2:# Have to check whether --image is given, see bug 1433609image_href = server_dict.get('imageRef')image_uuid_specified = image_href is not Nonetry:# 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'] = Falseblock_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_hrefimage_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 clientreturn_reservation_id = create_kwargs.pop('return_reservation_id',False)# 通過 networks attribute 創建一個 list of requested networksrequested_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-Attachsupports_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" % errorraise 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 itif 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'] = passwordrobj = wsgi.ResponseObject(server)return self._add_location(robj)上述的參數基本上可以與 Dashboard 上的 Form 表單一一對應:
至此,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 的工作流程。
轉載于:https://www.cnblogs.com/jmilkfan-fanguiju/p/10589721.html
總結
以上是生活随笔為你收集整理的Nova 启动虚拟机流程解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对称加密实现重要日志上报Openrest
- 下一篇: VUE搭建手机商城心得