openstack-nova源码之创建虚拟机
1.nova/api/openstack/compute/servers.py create()
在create函数前面包含三种装饰器,分别为:@wsgi.response、@wsgi.expected_errors、@validation.schema
(1)@wsgi.response属于限制装饰器,限制请求完成后的成功状态码202
(2)@wsgi.expected_errors属于限制装饰器,限制请求完成后的失败状态码400、403、409
(3)@validation.schema属于验证装饰器,在请求create函数开始前拦截http请求,对http请求中的参数进行校验,若校验成功则进入create,若失败则返回失败状态码,此装饰器主要功能是进行版本兼容
create函数中主要分为两个功能
(1)收集并校验参数,为后续创建虚拟机做准备
(2)调用nova/compute/api.py中的create()函数,此函数的主要功能是提供实例并将实例信息发送给调度器
@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', '2.73') @validation.schema(schema_servers.base_create_v274, '2.74') 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 = {} 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. create_kwargs['key_name'] = server_dict.get('key_name') 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)) 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 availability_zone = server_dict.pop("availability_zone", None) 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} 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) parse_az = self.compute_api.parse_availability_zone try: 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', {}) if api_version_request.is_supported(req, min_version='2.74'): self._process_hosts_for_create(context, target, server_dict, create_kwargs, host, node) self._process_bdms_for_create( context, target, server_dict, create_kwargs) image_uuid = self._image_from_req_data(server_dict, create_kwargs) self._process_networks_for_create( context, target, server_dict, create_kwargs) 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") supports_multiattach = common.supports_multiattach_volume(req) supports_port_resource_request = \ common.supports_port_resource_request(req) (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, check_server_group_quota=True, supports_multiattach=supports_multiattach, supports_port_resource_request=supports_port_resource_request, **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.ImageNotActive, exception.ImageBadRequest, exception.ImageNotAuthorized, exception.FixedIpNotFoundForAddress, exception.FlavorNotFound, exception.FlavorDiskTooSmall, exception.FlavorMemoryTooSmall, exception.InvalidMetadata, 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.InstanceGroupNotFound, exception.SnapshotNotFound, exception.UnableToAutoAllocateNetwork, exception.MultiattachNotSupportedOldMicroversion, exception.CertificateValidationFailed, exception.CreateWithPortResourceRequestOldVersion, exception.ComputeHostNotFound) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except INVALID_FLAVOR_IMAGE_EXCEPTIONS as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except (exception.PortInUse, exception.InstanceExists, exception.NetworkAmbiguous, exception.NoUniqueMatch, exception.VolumeTypeSupportNotYetAvailable) as error: raise exc.HTTPConflict(explanation=error.format_message()) # If the caller wanted a reservation_id, return it if server_dict.get('return_reservation_id', False): return wsgi.ResponseObject({'reservation_id': resv_id}) server = self._view_builder.create(req, instances[0]) if CONF.api.enable_instance_password: server['server']['adminPass'] = password robj = wsgi.ResponseObject(server) return self._add_location(robj)
第17行~第111行的功能为收集数据、校验数据将数据打包
第80行
context.can(server_policies.SERVERS % 'create', target)
最终调用nova/policy.py中authorize()
def authorize(context, action, target=None, do_raise=True, exc=None): """Verifies that the action is valid on the target in this context. :param context: nova context :param action: string representing the action to be checked this should be colon separated for clarity. i.e. ``compute:create_instance``, ``compute:attach_volume``, ``volume:attach_volume`` :param target: dictionary representing the object of the action for object creation this should be a dictionary representing the location of the object e.g. ``{'project_id': instance.project_id}`` If None, then this default target will be considered: {'project_id': self.project_id, 'user_id': self.user_id} :param do_raise: if True (the default), raises PolicyNotAuthorized; if False, returns False :param exc: Class of the exception to raise if the check fails. Any remaining arguments passed to :meth:`authorize` (both positional and keyword arguments) will be passed to the exception class. If not specified, :class:`PolicyNotAuthorized` will be used. :raises nova.exception.PolicyNotAuthorized: if verification fails and do_raise is True. Or if 'exc' is specified it will raise an exception of that type. :return: returns a non-False value (not necessarily "True") if authorized, and the exact value False if not authorized and do_raise is False. """ init() credentials = context.to_policy_values() if not exc: exc = exception.PolicyNotAuthorized # Legacy fallback for emtpy target from context.can() # should be removed once we improve testing and scope checks if target is None: target = default_target(context) try: result = _ENFORCER.authorize(action, target, credentials, do_raise=do_raise, exc=exc, action=action) except policy.PolicyNotRegistered: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Policy not registered')) except Exception: with excutils.save_and_reraise_exception(): LOG.debug('Policy check for %(action)s failed with credentials ' '%(credentials)s', {'action': action, 'credentials': credentials}) return result
第30行init()为一个初始化函数,主要功能是加载路由和配置文件
def init(policy_file=None, rules=None, default_rule=None, use_conf=True): """Init an Enforcer class. :param policy_file: Custom policy file to use, if none is specified, `CONF.policy_file` will be used. :param rules: Default dictionary / Rules to use. It will be considered just in the first instantiation. :param default_rule: Default rule to use, CONF.default_rule will be used if none is specified. :param use_conf: Whether to load rules from config file. """ global _ENFORCER global saved_file_rules if not _ENFORCER: _ENFORCER = policy.Enforcer(CONF, policy_file=policy_file, rules=rules, default_rule=default_rule, use_conf=use_conf) register_rules(_ENFORCER) _ENFORCER.load_rules() # Only the rules which are loaded from file may be changed. current_file_rules = _ENFORCER.file_rules current_file_rules = _serialize_rules(current_file_rules) # Checks whether the rules are updated in the runtime if saved_file_rules != current_file_rules: _warning_for_deprecated_user_based_rules(current_file_rules) saved_file_rules = copy.deepcopy(current_file_rules)
第22行为注册路由功能(详情可参考源码)
跳转至nova/compute/api.py中create()方法
@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, supports_port_resource_request=False, requested_host=None, requested_hypervisor_hostname=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: self._check_multiple_instances_with_specified_ip( requested_networks) if utils.is_neutron(): self._check_multiple_instances_with_neutron_ports( requested_networks) if availability_zone: available_zones = availability_zones. \ get_availability_zones(context.elevated(), self.host_api, get_only_available=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 = 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, supports_port_resource_request=supports_port_resource_request, requested_host=requested_host, requested_hypervisor_hostname=requested_hypervisor_hostname)
整段代码主要对网络ip、端口进行校验,获取可用区列表检验可用区是否属于此列表,并根据条件筛选符合创建虚机所需的主机的条件,最后调用nova/compute/api.py中的_create_instance()方法
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, supports_port_resource_request=False, requested_host=None, requested_hypervisor_hostname=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, 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 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, supports_port_resource_request) # 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 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 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. # Set validate_numa=False since numa validation is already done by # _validate_and_build_base_options(). self._checks_for_create_and_rebuild(context, image_id, boot_meta, instance_type, metadata, injected_files, block_device_mapping.root_bdm(), validate_numa=False) instance_group = self._get_requested_instance_group(context, filter_properties) tags = self._create_tag_list_obj(context, tags) 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, requested_host, requested_hypervisor_hostname) 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) 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
整段代码的作用为收集创建虚机所需的disk,image信息,保证创建顺利创建,最后调用nova/conductor/api.py中schedule_and_build_instances()方法
def schedule_and_build_instances(self, context, build_requests, request_spec, image, admin_password, injected_files, requested_networks, block_device_mapping, tags=None): self.conductor_compute_rpcapi.schedule_and_build_instances( context, build_requests, request_spec, image, admin_password, injected_files, requested_networks, block_device_mapping, tags)
整段代码只起到缓冲作用,立刻调用nova/conductor/rpcapi.py中schedule_and_build_instances()方法
def schedule_and_build_instances(self, context, build_requests, request_specs, image, admin_password, injected_files, requested_networks, block_device_mapping, tags=None): version = '1.17' kw = {'build_requests': build_requests, 'request_specs': request_specs, 'image': jsonutils.to_primitive(image), 'admin_password': admin_password, 'injected_files': injected_files, 'requested_networks': requested_networks, 'block_device_mapping': block_device_mapping, 'tags': tags} if not self.client.can_send_version(version): version = '1.16' del kw['tags'] cctxt = self.client.prepare(version=version) cctxt.cast(context, 'schedule_and_build_instances', **kw)
整段代码有两个作用,一是进行版本判断,根据版本调整参数,二是使用rpc调用nova/conductor/manager.py中schedule_and_build_instances()方法
def schedule_and_build_instances(self, context, build_requests, request_specs, image, admin_password, injected_files, requested_networks, block_device_mapping, tags=None): # Add all the UUIDs for the instances instance_uuids = [spec.instance_uuid for spec in request_specs] try: host_lists = self._schedule_instances(context, request_specs[0], instance_uuids, return_alternates=True) except Exception as exc: LOG.exception('Failed to schedule instances') self._bury_in_cell0(context, request_specs[0], exc, build_requests=build_requests, block_device_mapping=block_device_mapping, tags=tags) return host_mapping_cache = {} cell_mapping_cache = {} instances = [] host_az = {} # host=az cache to optimize multi-create for (build_request, request_spec, host_list) in six.moves.zip( build_requests, request_specs, host_lists): instance = build_request.get_new_instance(context) # host_list is a list of one or more Selection objects, the first # of which has been selected and its resources claimed. host = host_list[0] # Convert host from the scheduler into a cell record if host.service_host not in host_mapping_cache: try: host_mapping = objects.HostMapping.get_by_host( context, host.service_host) host_mapping_cache[host.service_host] = host_mapping except exception.HostMappingNotFound as exc: LOG.error('No host-to-cell mapping found for selected ' 'host %(host)s. Setup is incomplete.', {'host': host.service_host}) self._bury_in_cell0( context, request_spec, exc, build_requests=[build_request], instances=[instance], block_device_mapping=block_device_mapping, tags=tags) # This is a placeholder in case the quota recheck fails. instances.append(None) continue else: host_mapping = host_mapping_cache[host.service_host] cell = host_mapping.cell_mapping # Before we create the instance, let's make one final check that # the build request is still around and wasn't deleted by the user # already. try: objects.BuildRequest.get_by_instance_uuid( context, instance.uuid) except exception.BuildRequestNotFound: # the build request is gone so we're done for this instance LOG.debug('While scheduling instance, the build request ' 'was already deleted.', instance=instance) # This is a placeholder in case the quota recheck fails. instances.append(None) # If the build request was deleted and the instance is not # going to be created, there is on point in leaving an orphan # instance mapping so delete it. try: im = objects.InstanceMapping.get_by_instance_uuid( context, instance.uuid) im.destroy() except exception.InstanceMappingNotFound: pass self.report_client.delete_allocation_for_instance( context, instance.uuid) continue else: if host.service_host not in host_az: host_az[host.service_host] = ( availability_zones.get_host_availability_zone( context, host.service_host)) instance.availability_zone = host_az[host.service_host] with obj_target_cell(instance, cell): instance.create() instances.append(instance) cell_mapping_cache[instance.uuid] = cell # 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.flavor, 0, 0, orig_num_req=len(build_requests)) except exception.TooManyInstances as exc: with excutils.save_and_reraise_exception(): self._cleanup_build_artifacts(context, exc, instances, build_requests, request_specs, block_device_mapping, tags, cell_mapping_cache) zipped = six.moves.zip(build_requests, request_specs, host_lists, instances) for (build_request, request_spec, host_list, instance) in zipped: if instance is None: # Skip placeholders that were buried in cell0 or had their # build requests deleted by the user before instance create. continue cell = cell_mapping_cache[instance.uuid] # host_list is a list of one or more Selection objects, the first # of which has been selected and its resources claimed. host = host_list.pop(0) alts = [(alt.service_host, alt.nodename) for alt in host_list] LOG.debug("Selected host: %s; Selected node: %s; Alternates: %s", host.service_host, host.nodename, alts, instance=instance) filter_props = request_spec.to_legacy_filter_properties_dict() scheduler_utils.populate_retry(filter_props, instance.uuid) scheduler_utils.populate_filter_properties(filter_props, host) # Now that we have a selected host (which has claimed resource # allocations in the scheduler) for this instance, we may need to # map allocations to resource providers in the request spec. try: scheduler_utils.fill_provider_mapping( context, self.report_client, request_spec, host) except Exception as exc: # If anything failed here we need to cleanup and bail out. with excutils.save_and_reraise_exception(): self._cleanup_build_artifacts( context, exc, instances, build_requests, request_specs, block_device_mapping, tags, cell_mapping_cache) # TODO(melwitt): Maybe we should set_target_cell on the contexts # once we map to a cell, and remove these separate with statements. with obj_target_cell(instance, cell) as cctxt: # send a state update notification for the initial create to # show it going from non-existent to BUILDING # This can lazy-load attributes on instance. notifications.send_update_with_states(cctxt, instance, None, vm_states.BUILDING, None, None, service="conductor") objects.InstanceAction.action_start( cctxt, instance.uuid, instance_actions.CREATE, want_result=False) instance_bdms = self._create_block_device_mapping( cell, instance.flavor, instance.uuid, block_device_mapping) instance_tags = self._create_tags(cctxt, instance.uuid, tags) # TODO(Kevin Zheng): clean this up once instance.create() handles # tags; we do this so the instance.create notification in # build_and_run_instance in nova-compute doesn't lazy-load tags instance.tags = instance_tags if instance_tags \ else objects.TagList() # Update mapping for instance. Normally this check is guarded by # a try/except but if we're here we know that a newer nova-api # handled the build process and would have created the mapping inst_mapping = objects.InstanceMapping.get_by_instance_uuid( context, instance.uuid) inst_mapping.cell_mapping = cell inst_mapping.save() if not self._delete_build_request( context, build_request, instance, cell, instance_bdms, instance_tags): # The build request was deleted before/during scheduling so # the instance is gone and we don't have anything to build for # this one. continue # NOTE(danms): Compute RPC expects security group names or ids # not objects, so convert this to a list of names until we can # pass the objects. legacy_secgroups = [s.identifier for s in request_spec.security_groups] with obj_target_cell(instance, cell) as cctxt: self.compute_rpcapi.build_and_run_instance( cctxt, instance=instance, image=image, request_spec=request_spec, filter_properties=filter_props, admin_password=admin_password, injected_files=injected_files, requested_networks=requested_networks, security_groups=legacy_secgroups, block_device_mapping=instance_bdms, host=host.service_host, node=host.nodename, limits=host.limits, host_list=host_list)
此方法比较复杂,第9行调用_schedule_instances()获取符合创建虚机的主机
def _schedule_instances(self, context, request_spec, instance_uuids=None, return_alternates=False): scheduler_utils.setup_instance_group(context, request_spec) with timeutils.StopWatch() as timer: host_lists = self.query_client.select_destinations( context, request_spec, instance_uuids, return_objects=True, return_alternates=return_alternates) LOG.debug('Took %0.2f seconds to select destinations for %s ' 'instance(s).', timer.elapsed(), len(instance_uuids)) return host_lists
第5行调用select_destinations()方法,此方法调用shceduler_rpcapi中的select_destinations()方法进行rpc调用
def select_destinations(self, context, spec_obj, instance_uuids, return_objects=False, return_alternates=False): """Returns destinations(s) best suited for this request_spec and filter_properties. When return_objects is False, the result will be the "old-style" list of dicts with 'host', 'nodename' and 'limits' as keys. The value of return_alternates is ignored. When return_objects is True, the result will be a list of lists of Selection objects, with one list per instance. Each instance's list will contain a Selection representing the selected (and claimed) host, and, if return_alternates is True, zero or more Selection objects that represent alternate hosts. The number of alternates returned depends on the configuration setting `CONF.scheduler.max_attempts`. """ return self.scheduler_rpcapi.select_destinations(context, spec_obj, instance_uuids, return_objects, return_alternates)
def select_destinations(self, ctxt, spec_obj, instance_uuids, return_objects=False, return_alternates=False): # Modify the parameters if an older version is requested version = '4.5' msg_args = {'instance_uuids': instance_uuids, 'spec_obj': spec_obj, 'return_objects': return_objects, 'return_alternates': return_alternates} if not self.client.can_send_version(version): if msg_args['return_objects'] or msg_args['return_alternates']: # The client is requesting an RPC version we can't support. raise exc.SelectionObjectsWithOldRPCVersionNotSupported( version=self.client.version_cap) del msg_args['return_objects'] del msg_args['return_alternates'] version = '4.4' if not self.client.can_send_version(version): del msg_args['instance_uuids'] version = '4.3' if not self.client.can_send_version(version): del msg_args['spec_obj'] msg_args['request_spec'] = spec_obj.to_legacy_request_spec_dict() msg_args['filter_properties' ] = spec_obj.to_legacy_filter_properties_dict() version = '4.0' cctxt = self.client.prepare( version=version, call_monitor_timeout=CONF.rpc_response_timeout, timeout=CONF.long_rpc_timeout) return cctxt.call(ctxt, 'select_destinations', **msg_args)
注:精力有限,后续方法可自行阅读源码,方法如上
回归正题
此方法多为操作数据库,进行数据查询并判断,最后遍历host_list进行虚机创建,此处由于是for循环,所以每创建一个虚机都会调用一次build_and_run_instance()方法
def build_and_run_instance(self, ctxt, instance, host, image, request_spec, filter_properties, admin_password=None, injected_files=None, requested_networks=None, security_groups=None, block_device_mapping=None, node=None, limits=None, host_list=None): # NOTE(edleafe): compute nodes can only use the dict form of limits. if isinstance(limits, objects.SchedulerLimits): limits = limits.to_dict() kwargs = {"instance": instance, "image": image, "request_spec": request_spec, "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, "node": node, "limits": limits, "host_list": host_list, } client = self.router.client(ctxt) version = '5.0' cctxt = client.prepare(server=host, version=version) cctxt.cast(ctxt, 'build_and_run_instance', **kwargs)
此方法主要功能是用rpc调用nova/compute/manager.py中build_and_run_instances()方法
@wrap_exception() @reverts_task_state @wrap_instance_fault def build_and_run_instance(self, context, instance, image, request_spec, filter_properties, admin_password=None, injected_files=None, requested_networks=None, security_groups=None, block_device_mapping=None, node=None, limits=None, host_list=None): @utils.synchronized(instance.uuid) def _locked_do_build_and_run_instance(*args, **kwargs): # NOTE(danms): We grab the semaphore with the instance uuid # locked because we could wait in line to build this instance # for a while and we want to make sure that nothing else tries # to do anything with this instance while we wait. with self._build_semaphore: try: result = self._do_build_and_run_instance(*args, **kwargs) except Exception: # NOTE(mriedem): This should really only happen if # _decode_files in _do_build_and_run_instance fails, and # that's before a guest is spawned so it's OK to remove # allocations for the instance for this node from Placement # below as there is no guest consuming resources anyway. # The _decode_files case could be handled more specifically # but that's left for another day. result = build_results.FAILED raise finally: if result == build_results.FAILED: # Remove the allocation records from Placement for the # instance if the build failed. The instance.host is # likely set to None in _do_build_and_run_instance # which means if the user deletes the instance, it # will be deleted in the API, not the compute service. # Setting the instance.host to None in # _do_build_and_run_instance means that the # ResourceTracker will no longer consider this instance # to be claiming resources against it, so we want to # reflect that same thing in Placement. No need to # call this for a reschedule, as the allocations will # have already been removed in # self._do_build_and_run_instance(). self.reportclient.delete_allocation_for_instance( context, instance.uuid) if result in (build_results.FAILED, build_results.RESCHEDULED): self._build_failed(node) else: self._build_succeeded(node) # NOTE(danms): We spawn here to return the RPC worker thread back to # the pool. Since what follows could take a really long time, we don't # want to tie up RPC workers. utils.spawn_n(_locked_do_build_and_run_instance, context, instance, image, request_spec, filter_properties, admin_password, injected_files, requested_networks, security_groups, block_device_mapping, node, limits, host_list)
def spawn_n(func, *args, **kwargs): """Passthrough method for eventlet.spawn_n. This utility exists so that it can be stubbed for testing without interfering with the service spawns. It will also grab the context from the threadlocal store and add it to the store on the new thread. This allows for continuity in logging the context when using this method to spawn a new thread. """ _context = common_context.get_current() profiler_info = _serialize_profile_info() @functools.wraps(func) def context_wrapper(*args, **kwargs): # NOTE: If update_store is not called after spawn_n it won't be # available for the logger to pull from threadlocal storage. if _context is not None: _context.update_store() if profiler_info and profiler: profiler.init(**profiler_info) func(*args, **kwargs) eventlet.spawn_n(context_wrapper, *args, **kwargs)
此方法使用协程进行创建虚机(为保证数据一致性,创建前需要加锁)
openstack-nova源码之创建虚拟机的更多相关文章
- Openstack Nova 源码分析 — 使用 VCDriver 创建 VMware Instance
目录 目录 前言 流程图 nova-compute vCenter 前言 在上一篇Openstack Nova 源码分析 - Create instances (nova-conductor阶段)中, ...
- Openstack Nova 源码分析 — Create instances (nova-conductor阶段)
目录 目录 前言 Instance Flavor Instance Status Virt Driver Resource Tracker nova-conductor Create Instance ...
- Openstack Nova 源码分析 — RPC 远程调用过程
目录 目录 Nova Project Services Project 的程序入口 setuppy Nova中RPC远程过程调用 nova-compute RPC API的实现 novacompute ...
- openstack nova 源码解析 — Nova API 执行过程从(novaclient到Action)
目录 目录 Nova API Nova API 的执行过程 novaclient 将 Commands 转换为标准的HTTP请求 PasteDeploy 将 HTTP 请求路由到具体的 WSGI Ap ...
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
- Spring IOC 容器源码分析 - 创建原始 bean 对象
1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...
- Spring IOC 容器源码分析 - 创建单例 bean 的过程
1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...
- nova创建虚拟机源码分析系列之五 nova源码分发实现
前面讲了很多nova restful的功能,无非是为本篇博文分析做铺垫.本节说明nova创建虚拟机的请求发送到openstack之后,nova是如何处理该条URL的请求,分析到处理的类. nova对于 ...
- 【OpenStack】OpenStack系列13之Nova源码解析与API扩展
学习思路 议程:代码结构-主干流程-分层架构-业务模型-数据库模型-消息模型 分布式架构:Api:横向扩展 rpc:纵向扩展 分层架构:Controller接口层.View/Manager逻辑层 ...
随机推荐
- Windbg妙用
计算器 当你在调试,需要做一些从十六进制到十进制的简单转换,一些整数计算你不需要切换到calc.exe,你可以只使用windbg的表达式计算器.假设你得到了一个十六进制的大小,比如说2e903000, ...
- pgloader 学习(九) pg 2 pg 使用with 参数控制同步逻辑
pgloader 支持比较丰富的配置参数,同时默认数据在同步的时候是会进行索.schema 以及数据的同步对于实际我们可能存在需要进行控制,我们可以通过with 参数方便的处理 参考配置 load 文 ...
- Collecting metrics with the PostgreSQL and TimescaleDB output plugin for Telegraf
转自:https://docs.timescale.com/v1.3/tutorials/telegraf-output-plugin 文章演示了如何使用pg output 插件 以及Telegraf ...
- 洛谷 P1281 书的复制 题解
P1281 书的复制 题目背景 大多数人的错误原因:尽可能让前面的人少抄写,如果前几个人可以不写则不写,对应的人输出0 0. 不过,已经修改数据,保证每个人都有活可干. 题目描述 现在要把m本有顺序的 ...
- 【JZOJ6225】【20190618】计数
题目 对于一个01串,定义\(f(s)\)为\(f(s) = \sum_{i=0}^{\lfloor \frac{|s|}{2} \rfloor -1 }[s_i=s_{|s|-1-i}]\) 定义\ ...
- Ecms7.5版CK编辑器保留word格式如何修改
7.5版的编辑器默认会清除多余的word代码,如果要保留word格式怎么修改? CKeditor编辑器默认复制会清除多余word代码,如果要保留word格式可以按下面修改配置: 修改 /e/admin ...
- vscode前端插件(我的标配)
前言 今天给我的vscode编辑汉化了一下)我也不知道为什么要汉化一下... 但是汉化后 我的vue文件木有高亮了,2333(只好一顿操作 再安装插件 还要去百度找 自己留存) 汉化后 是所有的插件都 ...
- gmake: Nothing to be done for `all'.
安装gc_buffercache的时候报错: [root@~ pg_buffercache]# gmake gmake: Nothing to be done for `all'. 解决方法: > ...
- linux,卸载文件系统的时候,报busy情况的解决记录
背景描述: 前几天由于文件系统io异常的问题,要对文件系统的属性进行修改,修改该参数需要将磁盘umount,在umount的过程中遇到问题,在此记录下. 处理过程: 1.执行umount进行卸载磁盘, ...
- Puppeteer最大化显示,分辨率自适应
Puppeteer自适应分辨率,可以将defaultViewport设为null, 启动的时候还是半屏显示,点击最大化按钮,可以最大化显示. 这样分辨率能够自适应操作系统. 具体可看:https:// ...