--------------- 紧接上篇 nova实例创建(2) --------------------
5. 分析create方法
在最开始的run_instances方法中可知创建实例是通过self.compute_api.create
方法来实现的,该方法是在/nova/compute/api.py
里
# 建立所有类型的实例都要执行
# 返回一个元组(实例或者是reservation_id的元组)
# 元组里面的实例可以是"None"或者是实例字典的一个列表,这要取决于是否等待scheduler返回的信息
@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_group=None,
availability_zone=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=None, check_server_group_quota=False):复制代码
5.1 首先对执行create方法的资格进行验证
# 验证是否有资格执行create这个方法
# policy是nova中的一个资格验证机制
self._check_create_policies(context, availability_zone,
requested_networks, block_device_mapping)复制代码
5.2 检测多个实例情况下IP和端口的使用情况
if requested_networks and max_count > 1:
# 检测多个实例是否同时占用一个固定IP,如果是,引发异常
self._check_multiple_instances_and_specified_ip(requested_networks)
if utils.is_neutron():
# 检测多个实例是否使用同一个端口,如果不是,引发异常
self._check_multiple_instances_neutron_ports(
requested_networks)
复制代码
5.3 验证所有参数并最后发送请求消息给调度器
# 验证所有的输入实例参数
# 发送要运行实例(‘run_instance’)的请求消息到远程调度器
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_group,
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,
scheduler_hints=scheduler_hints,
legacy_bdm=legacy_dbm,
shutdown_terminate=shutdown_terminate,
check_server_group_quota=check_server_group_quota)复制代码
5.3.1 分析_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,
reservation_id=None, scheduler_hints=None,
legacy_bdm=True, shutdown_terminate=False,
check_server_group_quota=False):复制代码
(1)设置一些基本参数
# 设置一些基本参数
# generate_uid:随机生成一个uuid值赋值给reservation_id
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 []
if not instance_type:
instance_type = flavors.get_default_flavor()
# 设置镜像image信息
if image_href:
image_id, boot_meta = self._get_image(context, image_href)
else:
image_id = None
boot_meta = self._get_bdm_image_metedata(
context, block_device_mapping, legacy_bdm)
复制代码
(2)设置磁盘配置和可用区
self._check_auto_disk_config(image=boot_meta,
auto_disk_config=auto_disk_config)
handle_az = self._handle_availability_zone
availability_zone, forced_host, forced_node = handle_az(context,
availability_zone)
if not self.skip_policy_check and (forced_host or forced_node):
check_policy(context, 'create:forced_host', {})
复制代码
(3)对所有输入参数进行验证
# 对输入的所有参数进行验证,并建立基本的元素
base_options, max_net_count = 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,
forced_host, user_data, metadata, access_ip_v4,
access_ip_v6, requested_networks, config_drive,
auto_disk_config, reservation_id, max_count)复制代码
深入其中的_validate_and_build_base_options方法进行分析:
1》验证可用的空间
# 验证是否有可用的空间
# 如果空间不可用,引发异常
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)
复制代码
2》验证实例类型
# 处理实例类型为disabled的情况
if instance_type['disabled']:
raise exception.FlavorNotFound(flavor_id=instance_type['id'])
复制代码
3》验证user_data
if user_data:
l = len(user_data)
if l > MAX_USERDATA_SIZE:
# user_data会存储在一个text类型的列中
# 如果该值过长,数据库会自动缩短长度
raise exception.InstanceUserDataTooLarge(
length=1, maxsize=MAX_USERDATA_SIZE)
# 处理user_data的字符集
try:
base64.deeedestring(user_data)
except base64.binascii.Error:
raise exception.InstanceUserDataMalformed()
复制代码
4》验证安全组
# 检测所要求的安全组是否存在,并且属于指定的对象,如果不存在则会引发异常
self._check_requested_secgroups(context, security_groups)复制代码
5》验证网络
# 检测所需求的网络是否属于指定的对象(工程)
# 并且为每个网络提供的固定的IP地址是否处在同一个网段中
max_network_count = self._check_requested_networks(context,
requested_networks, max_count)复制代码
6》处理并获取kernel和ramdisk值
# 为实例获取合适的内核和ramdisk的值
kernel_id, ramdisk_id = self._handle_kernel_and_ramdisk(
context, kernel_id, ramdisk_id, boot_meta)
复制代码
7》转换驱动格式
# 转换驱动drive的格式,从string到bool串
config_drive = self._check_config_drive(config_drive)复制代码
8》处理key参数
# 处理key参数有name值但没有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
复制代码
9》处理其他信息
# 获取镜像image元数据
image_meta = objects.ImageMeta.from_dict(boot_meta)
# numa拓扑
numa_topology = hardware.numa_get_constraints(
instance_type, image_meta)
system_metadata = {}
pci_request_info = pci_request.get_pci_requests_from_flavor(
instance_type)
self.network_api.create_pci_requests_for_sriov_ports(context,
pci_request_info, requested_networks)复制代码
10》然后构建所有的基本元素base_options
11》获取更新后的base_options属性
# 从image镜像中继承相关属性信息
# 包括os_type、architecture、vm_mode和auto_disk_config
options_from_image = self._inherit_properties_from_image(
boot_meta, auto_disk_config)
# 相关属性更新到字典base_options当中
base_options.update(options_from_image)复制代码
12》最终返回结果
# 返回验证过的参数
# 以及根据网络配额计算的最大允许实例数
return base_options, max_network_count复制代码
(4)对最大实例数进行调整
# 根据返回的最大网络数max_net_count进行相应调整
# 如果等于0,引发异常
if max_net_count == 0:
raise exception.PortLimitExceeded()
elif max_net_count < max_count:
LOG.debug("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
max_count = max_net_count复制代码
(5)获取块设备映射
# 获取块设备映射
block_device_mapping = self._check_and_transform_bdm(context,
base_options, instance_type, boot_meta, min_count, max_count,
block_device_mapping, legary_bdm)复制代码
(6)对基本元素进行检测然后创建或重建
# 这个检测需要等所有来源的bdm都合并在一块去获取到root bdm后去执行
self._checks_for_create_and_rebuild(context, image_id, boot_meta,
instance_type, metadata, injected_files,
block_device_mapping.root_bdm())复制代码
深入_checks_for_create_and_rebuild
方法,可以发现它是依次调用三个检测方法:
# 对磁盘配额的元数据属性进行检测
self._check_metadata_properties_quota(context, metadata)
# 对磁盘配额注入文件的限制进行检测
self._check_injected_file_quota(context, files_to_inject)
# 对所要求的镜像image进行检测
self._check_requested_image(context, image_id, image,
instance_type, root_bdm)复制代码
1》_check_metadata_properties_quota
1.1》metadata格式预处理
if not metadata:
metadata = {}
# 确保metadata为字典
if not isinstance(metadata, dict):
msg = (_("Metadata type should be dict."))
raise exception.InvalidMetadata(reason=msg)
复制代码
1.2》metadata配额处理
# limit_check:简单的磁盘配额限制检测,并构造一个资源列表
# 这个资源列表将会被values.items所限制,会据此来检测磁盘资源配额是否符合要求
# 如果资源超出配额限制,则会引发异常
try:
objects.Quotas.limit_check(context, metadata_items=num_metadata)
except exception.OverQuota as exc:
quota_metadata = exc.kwargs['quotas']['metadata_items']
raise exception.MetadataLimitExceeded(allowed=quota_metadata)
复制代码
1.3》检测metadata的长度
for k, v in six.iteritems(metadata):
try:
utils.check_string_length(v)
utils.check_string_length(k, min_length=1)
except exception.InvalidInput as e:
raise exception.InvalidMetadata(reason=e.format_message())
# 确保metadata中数据的长度在0-255之间
if len(k) > 255:
msg = _("Metadata property key greater than 255 characters")
raise exception.InvalidMetadataSize(reason=msg)
if len(v) > 255:
msg = _("Metadata property value greater than 255 characters")
raise exception.InvalidMetadataSize(reason=msg)
复制代码
2》_check_injected_file_quota
2.1》限制注入文件数目
try:
objects.Quotas.limit_check(context,
injected_files=len(injected_files))
except exception.OverQuota:
raise exception.OnsetFileLimitExceeded()
复制代码
2.2》限制注入文件路径长度和注入内容长度
max_path = 0
max_context = 0
for path, content in injected_files:
max_path = max(max_path, len(path))
max_content = max(max_content, len(content))
try:
objects.Quotas.limit_check(context,
injected_file_path_bytes=max_path,
injected_file_content_bytes=max_content)
复制代码
3》_check_requested_image
3.1》检测镜像状态
# 镜像状态应该为active
if image['status'] != 'active':
raise exception.ImageNotActive(image_id=image_id)
复制代码
3.2》检测drive配置选项
# drive配置选项应该为“可选”或“强制”
image_properties = image.get('properties', {})
config_drive_option = image_properties.get(
'img_config_drive', 'optional')
if config_drive_option not in ['optional', 'mandatory']:
raise exception.InvalidImageConfigDrive(
config_drive=config_drive_option)
复制代码
3.3》检测内存空间
# 内存空间应该足够大
if instance_type['memory_mb'] < int(image.get('min_ram') or 0):
raise exception.FlavorMemoryTooSmall()
复制代码
3.4》(还有一些检测,有待研究)
(7)获取实例组
instance_group = self._get_requested_instance_group(context,
scheduler_hints, check_server_group_quota)复制代码
(未完待续...)