(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)
复制代码
(未完待续...)