本帖最后由 xioaxu790 于 2014-6-12 13:46 编辑
问题导读:
1、ReservableResource类,是如何实例化的?
继续看/nova/compute/api.py中的creat方法:
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):
"""
准备实例,并且发送实例的信息和要运行实例的请求消息到远程调度器scheduler;
实现实例的简历和运行,由调度器完成,这部分代码实际上只是实现请求消息的发送;
返回一个元组(实例或者是reservation_id的元组),元组里面的实例可以是“None”或者是实例字典的一个列表,这要取决于是否等待scheduler返回的信息;
"""
self._check_create_policies(context, availability_zone,requested_networks, block_device_mapping)
# 验证所有的输入实例参数;
# 发送要运行实例('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) 复制代码
语句self._check_create_policies(context, availability_zone,requested_networks, block_device_mapping)实现的是验证是否有资格执行creat这个方法,policy是nova中的一个资格验证机制,我会在其他博文中进行总结;
然后就进入了方法_create_instance,/nova/compute/api.py----def _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_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,
reservation_id=None, scheduler_hints=None):
"""
验证所有的输入实例参数;
发送要运行实例('run_instance')的请求消息到远程调度器;
"""
# generate_uid:随机生成一个uid值赋值给reservation_id;
if reservation_id is None:
reservation_id = utils.generate_uid('r')
# _validate_and_provision_instance:验证所有的输入参数;
# 返回要建立实例的各类信息;
# 这个方法中做了很多事,稍后会好好总结;
(instances, request_spec, filter_properties) = \
self._validate_and_provision_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, access_ip_v4,
access_ip_v6, requested_networks, config_drive,
block_device_mapping, auto_disk_config,
reservation_id, scheduler_hints)
# 循环获取instances中每个实例action的一些相关信息(包括启动时间等);
# _record_action_start获取要启动的实例action的一些相关信息(包括启动时间等);
for instance in instances:
self._record_action_start(context, instance,instance_actions.CREATE)
# run_instance:实现了发送要运行实例('run_instance')的请求消息到远程节点;
# 远程节点会在队列中提取这条消息,然后调用相应资源,实现运行实例的这个请求;
# 这个方法只是实现了请求信息的发送;
self.scheduler_rpcapi.run_instance(context,
request_spec=request_spec,
admin_password=admin_password, injected_files=injected_files,
requested_networks=requested_networks, is_first_time=True,
filter_properties=filter_properties)
return (instances, reservation_id) 复制代码
这个方法中共有四条语句:
reservation_id = utils.generate_uid('r')
(instances, request_spec, filter_properties) = self._validate_and_provision_instance(......)
self._record_action_start(context, instance,instance_actions.CREATE)
self.scheduler_rpcapi.run_instance(......) 复制代码
我们逐条进行解析。
A.reservation_id = utils.generate_uid('r'):
随机生成一个长度固定的uid值,赋值给reservation_id;
B.(instances,request_spec,filter_properties)=self._validate_and_provision_instance(......):
这条语句非常重要,它主要完成的是对输入各个参数进行检查验证,其中完成了一个非常重要的任务,即资源的配额管理。现在对这条语句进行解析,首先来看_validate_and_provision_instance这个方法:
def _validate_and_provision_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,
access_ip_v4, access_ip_v6,
requested_networks, config_drive,
block_device_mapping,
auto_disk_config, reservation_id,
scheduler_hints):
"""
验证所有的输入参数;
返回要建立实例的各类信息;
"""
if not metadata:
metadata = {}
if not security_groups:
security_groups = ['default']
if not instance_type:
instance_type = instance_types.get_default_instance_type()
if not min_count:
min_count = 1
if not max_count:
max_count = min_count
block_device_mapping = block_device_mapping or []
if min_count > 1 or max_count > 1:
if any(map(lambda bdm: 'volume_id' in bdm, block_device_mapping)):
msg = _('Cannot attach one or more volumes to multiple' ' instances')
raise exception.InvalidRequest(msg)
if instance_type['disabled']:
raise exception.InstanceTypeNotFound(
instance_type_id=instance_type['id'])
if user_data:
l = len(user_data)
if l > MAX_USERDATA_SIZE:
# NOTE(mikal): user_data is stored in a text column, and
# the database might silently truncate if its over length.
raise exception.InstanceUserDataTooLarge(
length=l, maxsize=MAX_USERDATA_SIZE)
try:
base64.decodestring(user_data)
except base64.binascii.Error:
raise exception.InstanceUserDataMalformed()
# 根据配额资源限制计算所要建立实例的数目,并获取了分配好的资源(块存储)的UUID的列表;
# _check_num_instances_quota:根据磁盘配额资源限制确定所要建立实例的数目;
# num_instances:返回值max_count表示建立实例的最大数目;
# quota_reservations: 返回值reservations表示建立的预定(分配)的资源的UUID的列表;
num_instances, quota_reservations = self._check_num_instances_quota(context, instance_type, min_count, max_count)
try:
instances = []
instance_uuids = []
# 对磁盘配额的元数据属性、磁盘配额的注入文件以及安全组和网络进行检测;
self._check_metadata_properties_quota(context, metadata)
"""
检测metadata参数;
强制对磁盘配额的元数据属性的限制进行检测;
进行了简单的磁盘配额限制的检测,并构造了一个符合要求的资源列表;
"""
self._check_injected_file_quota(context, injected_files)
"""
检测injected_files参数;
强制对磁盘配额注入文件的限制进行检测,如果超出任何限制会引发异常;
具体限制包括注入文件数目、注入文件路径长度和注入文件内容长度三项指标;
"""
self._check_requested_secgroups(context, security_groups)
"""
检测security_groups参数;
检测所要求的安全组是否存在,并且属于指定的对象,如果不存在则会引发异常;
"""
self._check_requested_networks(context, requested_networks)
"""
检测requested_networks参数;
检测所需求的网络是否属于指定的对象(工程),
并且为每个网络提供的固定的IP地址是否处在同一个网段中;
"""
# 检测image_href参数;
# 如果image_href值为真,则创建image_service并从给定的image_href解析它的ID值,赋给image_id;
# 并建立一个glance的客户端,具体方法是应用相关参数实例化一个新的glanceclient.Client对象;
# 获取image_service的状态,以此判断image_service是否创建成功;
if image_href:
(image_service, image_id) = glance.get_remote_image_service(context, image_href)
image = image_service.show(context, image_id)
if image['status'] != 'active':
raise exception.ImageNotActive(image_id=image_id)
else:
image = {}
# 检测instance_type['memory_mb']和instance_type['root_gb']参数;
if instance_type['memory_mb'] < int(image.get('min_ram') or 0):
raise exception.InstanceTypeMemoryTooSmall()
if instance_type['root_gb'] < int(image.get('min_disk') or 0):
raise exception.InstanceTypeDiskTooSmall()
# 为实例获取合适的内核和ramdisk的值;
kernel_id, ramdisk_id = self._handle_kernel_and_ramdisk(context, kernel_id, ramdisk_id, image)
"""
为实例选择合适的内核和ramdisk,返回获取到的内核和ramdisk的值kernel_id和ramdisk_id;
内核和ramdisk可以选择以下三种中的一种:
1.通过建立实例的要求来决定;
2.从image(镜像)来继承;
3.通过应用 ‘null_kernel’标志强制不使用;
注:如果是第三种情况,则还建立了image_service服务。而且建立一个glance的客户端,具体方法是应用相关参数实例化一个新的glanceclient.Client对象。
"""
# 处理config_drive这个参数;
config_drive_id = None
if config_drive and not utils.is_valid_boolstr(config_drive):
# config_drive is volume id
config_drive_id = config_drive
config_drive = None
# Ensure config_drive image exists
# 检测config_drive参数,确保config_drive镜像是存在的;
# 如果config_drive值是正确无误的,则创建cd_image_service并从给定的config_drive_id解析它的ID值,赋给config_drive_id;
# 并建立一个glance的客户端,具体方法是应用相关参数实例化一个新的glanceclient.Client对象;
# 获取cd_image_service的状态,以此判断cd_image_service是否创建成功;
cd_image_service, config_drive_id = glance.get_remote_image_service(context, config_drive_id)
cd_image_service.show(context, config_drive_id)
# 处理key_data这个参数;
if key_data is None and key_name:
key_pair = self.db.key_pair_get(context, context.user_id,key_name)
key_data = key_pair['public_key']
# 从镜像元数据中获取root_device_name值,如果这个值没有被指定,则返回None值;
root_device_name = block_device.properties_root_device_name(image.get('properties', {}))
# 从参数availability_zone中分离出availability_zone值和forced_host值;
availability_zone, forced_host = self._handle_availability_zone(availability_zone)
# 从instance_type这个参数中保存instance type属性到实例的system_metadata数据中,格式如下所示:
# [prefix]instance_type_[key]
# 保存instance type信息到system_metadata当中,按照一定的格式;
system_metadata = instance_types.save_instance_type_info(dict(), instance_type)
# 保存所有参数选项的字典;
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_id': config_drive_id or '',
'config_drive': config_drive or '',
'user_id': context.user_id,
'project_id': context.project_id,
'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),
'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 or '',
'user_data': user_data,
'key_name': key_name,
'key_data': key_data,
'locked': False,
'metadata': metadata,
'access_ip_v4': access_ip_v4,
'access_ip_v6': access_ip_v6,
'availability_zone': availability_zone,
'root_device_name': root_device_name,
'progress': 0,
'system_metadata': system_metadata}
# 从image镜像中继承相关属性信息;
# 包括os_type、architecture、vm_mode和auto_disk_config;
options_from_image = self._inherit_properties_from_image(image, auto_disk_config)
# 相关属性更新到字典base_options当中;
base_options.update(options_from_image)
# num_instances表示要建立实例的最大数目;
LOG.debug(_("Going to run %s instances...") % num_instances)
# 从调度提示信息scheduler_hints中获取过滤器信息;
filter_properties = dict(scheduler_hints=scheduler_hints)
# 获取filter_properties['force_hosts']的值;(注:这里forced_host是怎么来的还需要进一步研究明白;)
if forced_host:
check_policy(context, 'create:forced_host', {})
filter_properties['force_hosts'] = [forced_host]
# num_instances表示要建立实例的最大数目;
# xrange(num_instances)表示从0到num_instances-1的序列;
for i in xrange(num_instances):
# 拷贝base_options对象;
options = base_options.copy()
# 为每一个新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等);
instance = self.create_db_entry_for_new_instance(context, instance_type, image, options, security_groups, block_device_mapping, num_instances, i)
# 实例instance添加到instances中;
instances.append(instance)
# instance['uuid']添加到instance_uuids中;
instance_uuids.append(instance['uuid'])
# 验证一个实例instance中的所有的块设备映射;
self._validate_bdm(context, instance)
# send a state update notification for the initial create to
# show it going from non-existent to BUILDING
# 为实例的初始化建立发送一个状态更新通知,表示实例状态从不存在到建立;
# send_update_with_states:在一个实例中,发送compute.instance.update来通知实例的任何的改变信息;
notifications.send_update_with_states(context, instance, None, vm_states.BUILDING, None, None, service="api")
# In the case of any exceptions, attempt DB cleanup and rollback the
# quota reservations.
# 在引发异常的情况下
except Exception:
# 先保存当前的异常Exception,运行一些代码以后,再引发这个异常;
# 这个挺有意思的;
with excutils.save_and_reraise_exception():
try:
# 循环取出每个实例的UUID值;
# 根据每个实例的UUID值,删除具体的实例;
# instance_destroy:销毁当前要建立的实例,如果实例不存在,则引发异常;
# 返回删除后的实例对象;
for instance_uuid in instance_uuids:
self.db.instance_destroy(context, instance_uuid)
finally:
# 配额quota_reservations预约信息的删除;
QUOTAS.rollback(context, quota_reservations)
# Commit the reservations
# 提交设定好的reservations预约信息;
QUOTAS.commit(context, quota_reservations)
# 生成包含请求建立实例的信息的字典;
request_spec = {
'image': jsonutils.to_primitive(image), # 转换镜像image对象到primitives格式;
'instance_properties': base_options, # 实例属性;
'instance_type': instance_type, # 实例类型;
'instance_uuids': instance_uuids, # 实例UUID;
'block_device_mapping': block_device_mapping, # 块设备映射信息;
'security_group': security_groups, # 安全组信息; 复制代码
这个方法中要完成很多东西,下面来逐条进行解析:
a.metadata、security_groups、instance_type、min_count、max_count、block_device_mapping、user_data等参数的验证和初始化:
if not metadata:
metadata = {}
if not security_groups:
security_groups = ['default']
if not instance_type:
instance_type = instance_types.get_default_instance_type()
if not min_count:
min_count = 1
if not max_count:
max_count = min_count
block_device_mapping = block_device_mapping or []
if min_count > 1 or max_count > 1:
if any(map(lambda bdm: 'volume_id' in bdm, block_device_mapping)):
msg = _('Cannot attach one or more volumes to multiple' ' instances')
raise exception.InvalidRequest(msg)
if instance_type['disabled']:
raise exception.InstanceTypeNotFound(instance_type_id=instance_type['id'])
if user_data:
l = len(user_data)
if l > MAX_USERDATA_SIZE:
raise exception.InstanceUserDataTooLarge(length=l, maxsize=MAX_USERDATA_SIZE)
try:
base64.decodestring(user_data)
except base64.binascii.Error:
raise exception.InstanceUserDataMalformed() 复制代码
b.num_instances, quota_reservations = self._check_num_instances_quota(context, instance_type, min_count, max_count)资源配额管理,根据磁盘配额资源限制确定所要建立实例的数目,并获取了分配好的资源(块存储)的UUID的列表;
进入方法_check_num_instances_quota:
def _check_num_instances_quota(self, context, instance_type, min_count,max_count):
"""
根据配额资源限制所要建立实例的数目;
返回值max_count表示建立实例的最大数目;
返回值reservations表示建立的预定(分配)的资源的UUID的列表;
"""
# 确定请求分配的内核数和RAM;
req_cores = max_count * instance_type['vcpus']
req_ram = max_count * instance_type['memory_mb']
# reserve:检查配额并且分配存储资源;
# 如果没有错误,方法返回所建立的预定(分配)的资源的UUID的列表给reservations;
try:
reservations = QUOTAS.reserve(context, instances=max_count,cores=req_cores, ram=req_ram)
except exception.OverQuota as exc:
# 查找超出配额限制的原因;
quotas = exc.kwargs['quotas']
usages = exc.kwargs['usages']
overs = exc.kwargs['overs']
headroom = dict((res, quotas[res] -
(usages[res]['in_use'] + usages[res]['reserved']))
for res in quotas.keys())
allowed = headroom['instances']
# Reduce 'allowed' instances in line with the cores & ram headroom
if instance_type['vcpus']:
allowed = min(allowed,headroom['cores'] // instance_type['vcpus'])
if instance_type['memory_mb']:
allowed = min(allowed,headroom['ram'] // instance_type['memory_mb'])
# Convert to the appropriate exception message
if allowed <= 0:
msg = _("Cannot run any more instances of this type.")
allowed = 0
elif min_count <= allowed <= max_count:
# We're actually OK, but still need reservations
return self._check_num_instances_quota(context, instance_type,min_count, allowed)
else:
msg = (_("Can only run %s more instances of this type.") %
allowed)
resource = overs[0]
used = quotas[resource] - headroom[resource]
total_allowed = used + headroom[resource]
overs = ','.join(overs)
pid = context.project_id
LOG.warn(_("%(overs)s quota exceeded for %(pid)s,"
" tried to run %(min_count)s instances. %(msg)s"),locals())
requested = dict(instances=min_count, cores=req_cores, ram=req_ram)
raise exception.TooManyInstances(overs=overs,req=requested[resource],used=used, allowed=total_allowed,resource=resource)
return max_count, reservations 复制代码
b.1 确定请求分配的内核数和RAM:
req_cores = max_count * instance_type['vcpus']
req_ram = max_count * instance_type['memory_mb'] 复制代码
注:instance_type示例:
instance_type = {'memory_mb': 2048L, 'root_gb': 20L, 'deleted_at': None, 'name': u'm1.small', 'deleted': 0L, 'created_at': None, 'ephemeral_gb': 0L, 'updated_at': None, 'disabled': False, 'vcpus': 1L, 'extra_specs': {}, 'swap': 0L, 'rxtx_factor': 1.0, 'is_public': True, 'flavorid': u'2', 'vcpu_weight': None, 'id': 5L}
b.2 语句reservations = QUOTAS.reserve(context, instances=max_count,cores=req_cores, ram=req_ram)分析:
这条语句实现了对资源配额的检测、管理和分配,如果没有错误,则返回所建立的预定(分配)的资源的UUID的列表;这里调用了方法/nova/quota.py----def reserve(self, context, expire=None, project_id=None, **deltas):
def reserve(self, context, expire=None, project_id=None, **deltas):
"""
检查配额并且分配存储资源;
如果没有错误,方法返回所建立的预定(分配)的资源的UUID的列表;
"""
reservations = self._driver.reserve(context, self._resources, deltas,expire 复制代码
我们先来看方法_driver的定义:
def _driver(self):
if self.__driver:
return self.__driver
# quota_driver:这个参数定义了配额管理默认的驱动类;
# 参数的默认值为'nova.quota.DbQuotaDriver';
if not self._driver_cls:
self._driver_cls = CONF.quota_driver
if isinstance(self._driver_cls, basestring):
self._driver_cls = importutils.import_object(self._driver_cls)
self.__driver = self._driver_cls
return self.__driver 复制代码
可以看到返回的self.__driver值,默认为'nova.quota.DbQuotaDriver';所以上一个方法中调用的就是类nova.quota.DbQuotaDriver下的方法reserve----def reserve(self, context, resources, deltas, expire=None, project_id=None):
def reserve(self, context, resources, deltas, expire=None, project_id=None):
"""
@@@@检测配额和储备资源;
"""
# 如果expire没有指定,则采用默认参数的值;
# reservation_expire:这个参数定义了预约(资源配额)的到期时间长度;
# 参数的默认值为86400;
if expire is None:
expire = CONF.reservation_expire
if isinstance(expire, (int, long)):
expire = datetime.timedelta(seconds=expire)
if isinstance(expire, datetime.timedelta):
expire = timeutils.utcnow() + expire
if not isinstance(expire, datetime.datetime):
raise exception.InvalidReservationExpiration(expire=expire)
# 如果没有定义project_id,则应用context中的project_id值;
if project_id is None:
project_id = context.project_id
# 获取适用的配额信息;
quotas = self._get_quotas(context, resources, deltas.keys(), has_sync=True, project_id=project_id)
return db.quota_reserve(context, resources, quotas, deltas, expire,
CONF.until_refresh, CONF.max_age,
project_id=project_id) 复制代码
首先,为参数expire赋值,expire定义了备份(预约)(资源配额)的到期时间长度,这里把当前时间加上预约时间长度,得到的就是到期时间:
if expire is None:
expire = CONF.reservation_expire
if isinstance(expire, (int, long)):
expire = datetime.timedelta(seconds=expire)
if isinstance(expire, datetime.timedelta):
expire = timeutils.utcnow() + expire
if not isinstance(expire, datetime.datetime):
raise exception.InvalidReservationExpiration(expire=expire) 复制代码
可见,初始化方法中又调用了父类BaseResource的初始化方法,所以我们在看看其父类BaseResource:
class BaseResource(object):
"""为配额检测定义一个资源类;"""
def __init__(self, name, flag=None):
"""
初始化这个类。
"""
self.name = name
self.flag = flag 复制代码
所以我们能看到,参数resources的定义中的语句ReservableResource('instances', _sync_instances, 'quota_instances')实现了:
i. 初始化ReservableResource类,实例化这个类的对象;
ii. name = 'instances';
flag = 'quota_instances';
sync = _sync_instances;
我们注意一下这个方法_sync_instances,它作为一个参数传递赋值给sync;(当然这里并没有执行这个方法!)
本文,接着上一篇内容:OpenStack建立实例完整过程源码详细分析(5)