(8)完成配额管理
instances = 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)
深入_provision_instances
方法:
1》获取建立实例数目
# _check_num_instances_quota:根据资源配额限制确定所要建立实例的数目 num_instances, quotas = self._check_num_instances_quota( context, instance_type, min_count, max_count) LOG.debug("Going to run %s instances..." % num_instances)
查看其中的_check_num_instances_quota
方法:
1.1》确定内核数和RAM
req_cores = max_count * instance_type['vcpus'] vram_mb = int(instance_type.get('extra_specs', {}).get(VIDEO_RAM, 0)) req_ram = max_count * (instance_type['memory_mb'] + vram_mb)
1.2》分配配额
# 检查配额并且分配存储资源 try: quotas = objects.Quotas(context=context) # reserve:实现了对资源配额的检测、管理和分配 quotas.reserve(instances=max_count, cores=req_cores, ram=req_ram, project_id=project_id, user_id=user_id)
其中reserve方法对应在/nova/objects/quotas.py
文件里
1.2.1》获取文件配额的到期时间
# 如果expire没有指定,则采用默认参数的值 # reservation_expire:这个参数定义了预约(资源配额)的到期时间长度 # 参数的默认值为86400 if expire in None: expire = CONF.reservation_expire if isinstance(expire, six.integer_types): expire = datetime.timedelta(seconds=expire) # 把当前时间加上预约时间长度,得到到期时间expire if isinstance(expire, datetime.timedelta): expire = timeutils.utcnow() + expire if not isinstance(expire, datetime.datetime): raise exception.InvalidReservationExpiration(expire=expire)
1.2.2》获取project_id和user_id
# 如果没有定义project_id,则应用context中的project_id值 if project_id is None: project_id = context.project_id LOG.debug('Reserving resources using context.project_id: %s', project_id) # 如果没有定义user_id,则应用context中的user_id值 if user_id is None: user_id = context.user_id LOG.debug('Reserving resources using context.user_id: %s', user_id)
1.2.3》获取对象的配额信息
# 获取user_id和project_id确定的用户对象的配额信息 user_quotas = self._get_quotas(context, resources, deltas.keys(), has_sync=True, project_id=project_id, user_id=user_id, project_quotas=project_quotas)
# 获取project_id确定的对象的配额信息 quotas = self._get_quotas(context, resources, deltas.keys(), has_sync=True, project_id=project_id, project_quotas=project_quotas)
# 对于一个给定的项目,检索它的所有的磁盘配额 # 根据project_id查询数据库中相应项目的数据库信息 # 获取其中的hard_limit值,也就是获取规定的资源最大限额值 project_quotas = db.quota_get_all_by_project(context, project_id)
其中分析_get_quotas
方法,这是个辅助方法,从数据库获取特定的配额资源信息:
1.2.3.1》筛选资源
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 = {k: v for k, v in resources.items() if k in desired and sync_filt(v)}
1.2.3.2》检测磁盘配额资源
# 确保所有磁盘配额资源都是已知的,否则引发异常,提示某些磁盘配额资源是未知的 if len(keys) != len(sub_resources): unknown = desired - set(sub_resoures.keys()) raise exception.QuotaResourceUnknown(unknown=sorted(unknown))
1.2.3.3》根据user_id或project_id获取配额信息
有user_id,执行:
# 获取并返回未使用的磁盘配额 quotas = self.get_user_quotas(context, sub_resources, project_id, user_id, context.quota_class, usages=False, project_quotas=project_quotas)
如果没有user_id,则执行:
quotas = self.get_project_quotas(context, sub_resources, project_id, context.quota_class, usages=False, project_quotas=project_quotas)
1.2.3.4》返回资源的limit值
# 以字典的形式返回各种资源的配额信息limit值 # 三种资源:instances、ram、cores return {k: v['limit'] for k, v in quotas.items()}
1.2.4》最后获取资源配额
return db.quota_reserve(context, resources, quotas, user_quotas, deltas, expire, CONF.until_refresh, CONF.max_age, project_id=project_id, user_id=user_id)
这里quota_reserve
方法调用的是/nova/db/api.py
文件的相应方法,该方法返回的是IMPL的quota_reserve
方法:
return IMPL.quota_reserve(context, resources, quotas, user_quotas, deltas, expire, until_refresh, max_age, project_id=project_id, user_id=user_id)
从_BACKEND_MAPPING
设置中可看出,方法最终是调用了/nova/db/sqlalchemy.py
的quota_reserve
方法。
# context:程序运行上下文信息 # deltas:建立一个实例要求的资源信息,也就是每建立或者删除一个实例,资源配额的变化量 # expires:reservations的有限期 # until_refresh:是从配置信息CONF.until_refresh赋值的 # until_refresh作用:直到usage刷新,reservations的数目,默认值为0 # max_age:是从配置信息CONF.max_age赋值的 # max_age作用:刷新usage之间停留的秒数,默认值为0 @require_context @oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True) def quota_reserve(context, resources, project_quotas, user_quotas, deltas, expire, until_refresh, max_age, project_id=None, user_id=None):
1.2.4.1》获取session
# 获取db_session的session # get_session:返回一个SQLAlchemy session,若没有定义,则新建一个SQLAlchemy session session = get_session() with session.begin():
1.2.4.2》通过project_id和user_id获取资源使用情况
# 获取context中的project_id和user_id if project_id is None: project_id = context.project_id if user_id is None: user_id = context.user_id # 从quota_usages表中获得当前工程的各种资源的使用情况 project_usages, user_usages = _get_project_user_quota_usages( context, session, project_id, )
1.2.4.3》取出resource元素判断是否需要进行refresh操作
# 处理usage的refresh操作 # 这里deltas.keys() = ['instances', 'ram', 'cores'] work = set(deltas.keys()) while work: # 任意地从集合work中取出一个元素 resource = work.pop()
1.2.4.4》判断条件
# 判断是否需要重新刷新usage created = _create_quota_usage_if_missing(user_usages, resource, until_refresh, project_id, user_id, session) refresh = created or _is_quota_refresh_needed( user_usages[resource], max_age)
1.2.4.5》获取不同资源的同步方法
# 执行更新(同步)usage if refresh: # 获取同步方法,_sync_*(),这些方法定义在quota模块中 # 在不同的资源有不同的同步方法,也即三种资源:instances、ram、cores # sync方法能够实时查询到工程当前所使用资源的情况 # 也就能够用于刷新(同步)资源使用信息的操作 sync = QUOTA_SYNC_FUNCTIONS[resources[resource], sync]
1.2.4.6》查询并更新实时数据
# 查询当前正在使用的实时的资源的数据信息 # 同步方法实现从数据库中查询到匹配的数据表‘instances’ # 进而获取其id、vcpus和memory_mb三种资源的实时使用情况 # 分别赋值给'instances'、'cores'和'ram',以字典的形式返回给updates updates = sync(elevated, project_id, user_id, session) for res, in_use in updates.items(): # 如果实时使用的资源没有在usages中,那么把它添加进去 _create_quota_usage_if_missing(user_usages, res, until_refresh, project_id, user_id, session) # 更新usage中的until_refresh数据信息 _refresh_quota_usages(user_usages[res], until_refresh, in_use)
1.2.4.7》处理in_use小于0的情况
# 检测资源数据中in_use加上delta之后,可能小于0的情况 # unders是检查delta为负数的情况,即执行了删除等操作,使delta为负,in_use减少 # 导致in_use值可能小于0 unders = [res for res, delta in deltas.items() if delta < 0 and delta + user_usages[res].in_use < 0]
1.2.4.8》检测是否有超额
# 检测这个resource的hard_limit是否小于in_use+resourced+delta之和 # 如果overs为真,说明in_use+resourced+delta的值已经大于系统限定的资源配额的数值 overs = _calculate_overquota(project_quotas, user_quotas, deltas, project_usages, user_usages)
1.2.4.9》其余的是做其他信息的更新操作,不一一列举
2》循环创建实例
for i in range(num_instances): # 创建instance实例对象 instance = objects.Instance(context=context) instance.update(base_options) # 为每一个新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等) instance = self.create_db_entry_for_new_instance( context, instance_type, boot_meta, instance, security_groups, block_device_mapping, num_instances, i, shutdown_terminate) # 实例instance添加到instances中 instances.append(instance)
深入create_db_entry_for_new_instance
方法:
2.1》建立新实例的开端
# _populate_instance_for_create:建立一个新的实例的开销 # 首先执行instance = base_options # 然后补充一些实例的相关信息到instance这个字典中 # 返回设置好信息的实例字典 # 另外,还做了: # 存储image镜像的属性信息,以便后面我们能够用到它们 # 对目前这个image镜像实例的来源,即这个基础镜像的记录信息进行保存 self._populate_instance_for_create(context, instance, image, index, security_group, instance_type)
2.2》确定基本信息
# 确定实例的显示名称和主机名称(display_name和hostname) self._populate_instance_names(instance, num_instances) # 确定实例的关机和终止状态信息(shutdown_terminate) instance.shutdown_terminate = shutdown_terminate # ensure_default:确保contenxt有一个安全组,如果没有就建立一个 self.security_group_api.ensure_default(context)
2.3》创建实例
# 创建一个新的实例,并记录在数据库中 instance.create()
2.4》处理建立多个实例的情况
# 如果要建立实例的最大数目大于1 # 当一个请求创建多个实例时,这时实例的命名遵循名称模板来进行 if num_instances > 1: instance = self._apply_instance_name_template(context, instance, index)
(未完待续...)