深挖Openstack Nova - 例項建立(4)

self_blank發表於2018-06-05
--------------- 緊接上篇 nova例項建立(3) --------------------


(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.pyquota_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)
複製程式碼


(未完待續...)


相關文章