接着上篇文章,我们来继续分析_create_instance方法。这个方法定义在nova/nova/compute/api.py中。
class API(base.Base):
def _create_instance(self,context,instance_type,
...):
...
#验证客户端传入参数,做虚拟机创建前的准备工作
(instances,request_spec,filter_properties) = self._validate_and_provision_instance(context,
instance_type,
...)
for instance in instances:
#更新InstanceAction表记录
self._record_action_start(context,instance,instance_actions.CREATE)
#向Nova Scheduler服务发送RPC请求,创建虚拟机
self.scheduler_rpcapi.run_instance(context,...)
return (instances,reservation_id)
可以看到_create_instance方法包括三个步骤:1.调用_validate_and_provision_instance方法完成虚拟机创建的准备工作。2.调用_record_action_start方法更新数据库中InstanceAction表的记录,将虚拟机状态改为“开始创建”。3.调用scheduler_rpcapi.run_instance方法,向Nova Scheduler服务发送RPC请求,将虚拟机创建请求交给Nova Scheduler服务处理。
1. _validate_and_provision_instance
这个方法非常重要,它主要完成的是对输入的各个参数的检验,其中完成了一个非常重要的任务,就是资源的配额管理。该方法的定义如下。
class API(base.Base):
def _validate_and_provision_instance(self,context,instance_type,
...):
#一些参数的验证和初始化
if not metadata:
metadata = {}
if not security_group:
security_group = 'default'
...
#如果客户端没有指定虚拟机规格,则使用默认的格式
if not instance_type:
instance_type = instance_type.get_default_instance_type()
...
#根据配额资源限制计算所要建立实例的数目,并获取了分配好的资源(块存储)的UUID的列表
num_instances, quota_reservations = self._check_num_instances_quota(
context, instance_type,...)
try:
instances = []
instance_uuids = []
#检查metadata项数是否超标
self._check_metadata_properties_quota(context, metadata)
#<span><span class="comment">检查注入文件的个数和大小是否超标</span></span>
self._check_injected_file_quota(context, injected_files)
#检查需求网络是否合法
self._check_requested_networks(context, requested_networks)
#获取、检查虚拟机镜像文件
if image_href:
#获取虚拟机镜像文件的uuid
(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 = {}
#检查虚拟机内存是否足够大
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()
...
#用于创建数据库的记录
base_options = {
'reservation_id': reservation_id,
...}
#获取镜像中指定的参数
options_from_image = self._inherit_properties_from_image(
image, auto_disk_config)
#将镜像中的参数合并至base_options
base_options.update(options_from_image)
...
这个方法完成了很多工作,来逐条分析吧。
(1) num_instances, quota_reservations = self._check_num_instances_quota(context, instance_type, min_count, max_count)
这个方法主要实现的是资源配额管理,根据磁盘配额限制确定所要创建的实例数目,并获取分配好的资源(块存储)uuid的列表。
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']
...
return max_count, reservations
重点分析这句: reservations = QUOTAS.reserve(context, instances=max_count,cores=req_cores, ram=req_ram)
这条语句实现了对资源配额的检测、管理和分配,如果没有错误,则返回所建立的资源的uuid列表。这里调用了QUOTAS类的reserve方法,这个类位于nova/nova/quotas.py中。
def reserve(self, context, expire=None, project_id=None, **deltas):
"""
检查配额并且分配存储资源;
如果没有错误,方法返回所建立的预定(分配)的资源的UUID的列表;
"""
reservations = self._driver.reserve(context, self._resources, deltas,expire=expire,project_id=project_id)
LOG.debug(_("Created reservations %(reservations)s") % locals())
return reservations
我们再来看看方法_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的值默认为配置文件中的“quota_driver",这里是“nova.quota.DbQuotaDriver”,所以上一个方法调用的就是这个类的reserve方法。
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)
这里先定义了预约资源的到期时间,接下来调用_get_quotas方法来获取project_id对应对象的配额信息。在分析_get_quotas方法之前,先来看看这个方法的参数。主要是参数resources,通过这个参数我们可以从数据库中获取实例相应的资源的信息,另外detals是
(instances=max_count,cores=req_cores, ram=req_ram)组成的字典。下面来分析_get_quotas方法。
def _get_quotas(self, context, resources, keys, has_sync, project_id=None):
#筛选资源
if has_sync:
sync_filt = lambda x: hasattr(x, 'sync')#判断对象x是否包含sync的特性
else:
sync_filt = lambda x: not hasattr(x, 'sync')#判断对象x是否不包含sync的特性
desired = set(keys)
sub_resources = dict((k, v) for k, v in resources.items()
if k in desired and sync_filt(v))
#确保所有磁盘配额资源都是已知的,否则引发异常,提示某些磁盘配额资源是未知的
if len(keys) != len(sub_resources):
unknown = desired - set(sub_resources.keys())
raise exception.QuotaResourceUnknown(unknown=sorted(unknown))
#获取并返回磁盘配额
quotas = self.get_project_quotas(context, sub_resources,project_id,context.quota_class, usages=False)
return dict((k, v['limit']) for k, v in quotas.items())
来看get_project_quotas方法,这里的sub_resources就是从resources字典中获取的‘instances’、‘ram’、‘cores’三部分。
篇幅有点长了~~下篇文章再继续分析get_project_quotas方法。