通过配置的debug环境来查看虚拟机创建过程,在命令行执行如下创建语句:
openstack server create --flavor m1.nano --image cirros-0.3.4-x86_64-disk --nic net-id=df9ea6f5-c5bf-4af7-aafa-16e80c15a7bc --security-group default instance_hrp2
进入如下断点处(/nova/api/openstack/compute/servers.py中ServersController的create(self, req, body)函数):
def create(self, req, body): """Creates a new server for a given user.""" import pydevd pydevd.settrace('localhost', port=12345, stdoutToServer=True, stderrToServer=True)
# nova上下文信息 context = req.environ['nova.context']
# server_dict保存需要创建的虚拟机的信息,包括镜像,网络等 server_dict = body['server']
# 获取password, 如果server_dict中存在'adminPass'直接读取,否则随机产生 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 = {} # TODO(alex_xu): This is for back-compatible with stevedore # extension interface. But the final goal is that merging # all of extended code into ServersController.
# 写入了一些信息到create_kwargs中,似乎检测出了server_dict中没配全的信息,但又必须的 self._create_by_func_list(server_dict, create_kwargs, body) 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') # 将创建的REST属性转换为服务器对象kwargs helpers.translate_attributes(helpers.CREATE, server_dict, create_kwargs) target = { 'project_id': context.project_id, 'user_id': context.user_id, 'availability_zone': availability_zone}
# Verifies that the given action is valid on the target in this context context.can(server_policies.SERVERS % 'create', 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, 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', {}) min_compute_version = service_obj.get_minimum_version_all_cells( nova_context.get_admin_context(), ['nova-compute']) supports_device_tagging = (min_compute_version >= DEVICE_TAGGING_MIN_COMPUTE_VERSION) block_device_mapping = create_kwargs.get("block_device_mapping") # TODO(Shao He, Feng) move this policy check to os-block-device-mapping # extension after refactor it. if block_device_mapping: 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)
# Get image data from the request or raise appropriate 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) requested_networks = server_dict.get('networks', None) if requested_networks is not None:
# Create a list of requested networks from the networks attribute 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_id = self._flavor_id_from_req_data(body) try:
# 通过flavor_id获取到flavor信息 inst_type = flavors.get_flavor_by_flavor_id( flavor_id, ctxt=context, read_deleted="no") supports_multiattach = common.supports_multiattach_volume(req)
# 调用/nova/compute/api.py中API类create()方法 (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.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.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) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except (exception.PortInUse, exception.InstanceExists, exception.NetworkAmbiguous, exception.NoUniqueMatch, exception.MultiattachSupportNotYetAvailable) 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}) req.cache_db_instances(instances)
# View that should be returned when an instance is created 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)
/nova/compute/api.py中API类create()方法
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): """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(), 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)
/nova/compute/api.py中API类_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): """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: 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 = \ 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 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. self._checks_for_create_and_rebuild(context, image_id, boot_meta, instance_type, metadata, injected_files, block_device_mapping.root_bdm()) 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, supports_multiattach) 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.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:
# 向schedule服务发送创建虚拟机的请求 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
在执行上文中self.compute_task_api.schedule_and_build_instances()之前,关于创建虚拟机的部分信息已经写入到了数据库中,通过openstack server list可以看到,如下:
root@ubuntu:/opt/stack/devstack# openstack server list +--------------------------------------+---------------+---------+--------------------------------------------------------+--------------------------+---------+ | ID | Name | Status | Networks | Image | Flavor | +--------------------------------------+---------------+---------+--------------------------------------------------------+--------------------------+---------+ | a31738cd-4dd4-44e8-a538-dc6ad4eb774b | instance_hrp2 | BUILD | | cirros-0.3.4-x86_64-disk | m1.nano | | 45182105-305b-4124-99e4-c7e6b07a0089 | instance_hrp1 | SHUTOFF | private=fd3c:90a1:3f1f:0:f816:3eff:fe0b:7766, 10.0.0.7 | cirros-0.3.4-x86_64-disk | m1.nano | | f23ef205-ed8c-4c15-97df-fd06233bef56 | cirros | SHUTOFF | private=fd3c:90a1:3f1f:0:f816:3eff:fe41:85ef, 10.0.0.8 | | m1.nano | +--------------------------------------+---------------+---------+--------------------------------------------------------+--------------------------+---------+
self.compute_task_api.schedule_and_build_instances()存在于/nova/conductor/api.py,具体如下:
def schedule_and_build_instances(self, context, build_requests, request_spec, image, admin_password, injected_files, requested_networks, block_device_mapping, tags=None):
# 要通过rpc发送任务 self.conductor_compute_rpcapi.schedule_and_build_instances( context, build_requests, request_spec, image, admin_password, injected_files, requested_networks, block_device_mapping, tags)
self.conductor_compute_rpcapi.schedule_and_build_instances()存在于/nova/conductor/rpcapi.py,具体如下:
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)
# 发送异步rpc消息 cctxt.cast(context, 'schedule_and_build_instances', **kw)