(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)
複製程式碼
(未完待續...)