--------------- 緊接上篇 nova例項建立(2) --------------------
5. 分析create方法
在最開始的run_instances方法中可知建立例項是通過self.compute_api.create
方法來實現的,該方法是在/nova/compute/api.py
裡
# 建立所有型別的例項都要執行
# 返回一個元組(例項或者是reservation_id的元組)
# 元組裡面的例項可以是"None"或者是例項字典的一個列表,這要取決於是否等待scheduler返回的資訊
@hooks.add_hook("create_instance")
def create(self, context, instance_type,
image_href, kernel_id=None, ramdisk_id=None,
min_count=None, max_count=None,
display_name=None, display_description=None,
key_name=None, key_data=None, security_group=None,
availability_zone=None, user_data=None, metadata=None,
injected_files=None, admin_password=None,
block_device_mapping=None, access_ip_v4=None,
access_ip_v6=None, requested_networks=None, config_drive=None,
auto_disk_config=None, scheduler_hints=None, legacy_bdm=True,
shutdown_terminate=None, check_server_group_quota=False):複製程式碼
5.1 首先對執行create方法的資格進行驗證
# 驗證是否有資格執行create這個方法
# policy是nova中的一個資格驗證機制
self._check_create_policies(context, availability_zone,
requested_networks, block_device_mapping)複製程式碼
5.2 檢測多個例項情況下IP和埠的使用情況
if requested_networks and max_count > 1:
# 檢測多個例項是否同時佔用一個固定IP,如果是,引發異常
self._check_multiple_instances_and_specified_ip(requested_networks)
if utils.is_neutron():
# 檢測多個例項是否使用同一個埠,如果不是,引發異常
self._check_multiple_instances_neutron_ports(
requested_networks)
複製程式碼
5.3 驗證所有引數並最後傳送請求訊息給排程器
# 驗證所有的輸入例項引數
# 傳送要執行例項(‘run_instance’)的請求訊息到遠端排程器
return self._create_instance(
context, instance_type,
image_href, kernel_id, ramdisk_id,
min_count, max_count,
display_name, display_description,
key_name, key_data, security_group,
availability_zone, user_data, metadata,
injected_files, admin_password,
access_ip_v4, access_ip_v6,
requested_networks, config_drive,
block_device_mapping, auto_disk_config,
scheduler_hints=scheduler_hints,
legacy_bdm=legacy_dbm,
shutdown_terminate=shutdown_terminate,
check_server_group_quota=check_server_group_quota)複製程式碼
5.3.1 分析_create_instance方法
# 實際建立例項方法
# 不管之前建立例項前做了什麼檢驗策略,這裡還要對輸入引數進行校驗
def _create_instance(self, context, instance_type,
image_href, kernel_id, ramdisk_id,
min_count, max_count,
display_name, display_description,
key_name, key_data, security_groups,
availability_zone, user_data, metadata,
injected_files, admin_password,
access_ip_v4, access_ip_v6,
requested_networks, config_drive,
block_device_mapping, auto_disk_config,
reservation_id=None, scheduler_hints=None,
legacy_bdm=True, shutdown_terminate=False,
check_server_group_quota=False):複製程式碼
(1)設定一些基本引數
# 設定一些基本引數
# generate_uid:隨機生成一個uuid值賦值給reservation_id
if reservation_id is None:
reservation_id = utils.generate_uid('r')
# 設定安全組或者使用預設的安全策略
security_groups = security_groups or ['default']
min_count = min_count or 1
max_count = max_count or min_count
block_device_mapping = block_device_mapping or []
if not instance_type:
instance_type = flavors.get_default_flavor()
# 設定映象image資訊
if image_href:
image_id, boot_meta = self._get_image(context, image_href)
else:
image_id = None
boot_meta = self._get_bdm_image_metedata(
context, block_device_mapping, legacy_bdm)
複製程式碼
(2)設定磁碟配置和可用區
self._check_auto_disk_config(image=boot_meta,
auto_disk_config=auto_disk_config)
handle_az = self._handle_availability_zone
availability_zone, forced_host, forced_node = handle_az(context,
availability_zone)
if not self.skip_policy_check and (forced_host or forced_node):
check_policy(context, 'create:forced_host', {})
複製程式碼
(3)對所有輸入引數進行驗證
# 對輸入的所有引數進行驗證,並建立基本的元素
base_options, max_net_count = self._validate_and_build_base_options(
context,
instance_type, boot_meta, image_href, image_id, kernel_id,
ramdisk_id, display_name, display_description,
key_name, key_data, security_groups, availability_zone,
forced_host, user_data, metadata, access_ip_v4,
access_ip_v6, requested_networks, config_drive,
auto_disk_config, reservation_id, max_count)複製程式碼
深入其中的_validate_and_build_base_options方法進行分析:
1》驗證可用的空間
# 驗證是否有可用的空間
# 如果空間不可用,引發異常
if availability_zone:
available_zones = availability_zones.\
get_availability_zones(context.elevated(), True)
if forced_host is None and availability_zone not in \
available_zones:
msg = _('The requested availability zone is not available')
raise exception.InvalidRequest(msg)
複製程式碼
2》驗證例項型別
# 處理例項型別為disabled的情況
if instance_type['disabled']:
raise exception.FlavorNotFound(flavor_id=instance_type['id'])
複製程式碼
3》驗證user_data
if user_data:
l = len(user_data)
if l > MAX_USERDATA_SIZE:
# user_data會儲存在一個text型別的列中
# 如果該值過長,資料庫會自動縮短長度
raise exception.InstanceUserDataTooLarge(
length=1, maxsize=MAX_USERDATA_SIZE)
# 處理user_data的字符集
try:
base64.deeedestring(user_data)
except base64.binascii.Error:
raise exception.InstanceUserDataMalformed()
複製程式碼
4》驗證安全組
# 檢測所要求的安全組是否存在,並且屬於指定的物件,如果不存在則會引發異常
self._check_requested_secgroups(context, security_groups)複製程式碼
5》驗證網路
# 檢測所需求的網路是否屬於指定的物件(工程)
# 並且為每個網路提供的固定的IP地址是否處在同一個網段中
max_network_count = self._check_requested_networks(context,
requested_networks, max_count)複製程式碼
6》處理並獲取kernel和ramdisk值
# 為例項獲取合適的核心和ramdisk的值
kernel_id, ramdisk_id = self._handle_kernel_and_ramdisk(
context, kernel_id, ramdisk_id, boot_meta)
複製程式碼
7》轉換驅動格式
# 轉換驅動drive的格式,從string到bool串
config_drive = self._check_config_drive(config_drive)複製程式碼
8》處理key引數
# 處理key引數有name值但沒有data值的情況
if key_data is None and key_name is not None:
key_pair = objects.KeyPair.get_by_name(context,
context.user_id,
key_name)
key_data = key_pair.public_key
複製程式碼
9》處理其他資訊
# 獲取映象image後設資料
image_meta = objects.ImageMeta.from_dict(boot_meta)
# numa拓撲
numa_topology = hardware.numa_get_constraints(
instance_type, image_meta)
system_metadata = {}
pci_request_info = pci_request.get_pci_requests_from_flavor(
instance_type)
self.network_api.create_pci_requests_for_sriov_ports(context,
pci_request_info, requested_networks)複製程式碼
10》然後構建所有的基本元素base_options
11》獲取更新後的base_options屬性
# 從image映象中繼承相關屬性資訊
# 包括os_type、architecture、vm_mode和auto_disk_config
options_from_image = self._inherit_properties_from_image(
boot_meta, auto_disk_config)
# 相關屬性更新到字典base_options當中
base_options.update(options_from_image)複製程式碼
12》最終返回結果
# 返回驗證過的引數
# 以及根據網路配額計算的最大允許例項數
return base_options, max_network_count複製程式碼
(4)對最大例項數進行調整
# 根據返回的最大網路數max_net_count進行相應調整
# 如果等於0,引發異常
if max_net_count == 0:
raise exception.PortLimitExceeded()
elif max_net_count < max_count:
LOG.debug("max count reduced from %(max_count)d to "
"%(max_net_count)d due to network port quota",
{'max_count': max_count,
'max_net_count': max_net_count})
# 如果返回的最大網路數小於開始設定的最大例項數
# 則應該把max_count修改為較小值,則此時的max_net_count
max_count = max_net_count複製程式碼
(5)獲取塊裝置對映
# 獲取塊裝置對映
block_device_mapping = self._check_and_transform_bdm(context,
base_options, instance_type, boot_meta, min_count, max_count,
block_device_mapping, legary_bdm)複製程式碼
(6)對基本元素進行檢測然後建立或重建
# 這個檢測需要等所有來源的bdm都合併在一塊去獲取到root bdm後去執行
self._checks_for_create_and_rebuild(context, image_id, boot_meta,
instance_type, metadata, injected_files,
block_device_mapping.root_bdm())複製程式碼
深入_checks_for_create_and_rebuild
方法,可以發現它是依次呼叫三個檢測方法:
# 對磁碟配額的後設資料屬性進行檢測
self._check_metadata_properties_quota(context, metadata)
# 對磁碟配額注入檔案的限制進行檢測
self._check_injected_file_quota(context, files_to_inject)
# 對所要求的映象image進行檢測
self._check_requested_image(context, image_id, image,
instance_type, root_bdm)複製程式碼
1》_check_metadata_properties_quota
1.1》metadata格式預處理
if not metadata:
metadata = {}
# 確保metadata為字典
if not isinstance(metadata, dict):
msg = (_("Metadata type should be dict."))
raise exception.InvalidMetadata(reason=msg)
複製程式碼
1.2》metadata配額處理
# limit_check:簡單的磁碟配額限制檢測,並構造一個資源列表
# 這個資源列表將會被values.items所限制,會據此來檢測磁碟資源配額是否符合要求
# 如果資源超出配額限制,則會引發異常
try:
objects.Quotas.limit_check(context, metadata_items=num_metadata)
except exception.OverQuota as exc:
quota_metadata = exc.kwargs['quotas']['metadata_items']
raise exception.MetadataLimitExceeded(allowed=quota_metadata)
複製程式碼
1.3》檢測metadata的長度
for k, v in six.iteritems(metadata):
try:
utils.check_string_length(v)
utils.check_string_length(k, min_length=1)
except exception.InvalidInput as e:
raise exception.InvalidMetadata(reason=e.format_message())
# 確保metadata中資料的長度在0-255之間
if len(k) > 255:
msg = _("Metadata property key greater than 255 characters")
raise exception.InvalidMetadataSize(reason=msg)
if len(v) > 255:
msg = _("Metadata property value greater than 255 characters")
raise exception.InvalidMetadataSize(reason=msg)
複製程式碼
2》_check_injected_file_quota
2.1》限制注入檔案數目
try:
objects.Quotas.limit_check(context,
injected_files=len(injected_files))
except exception.OverQuota:
raise exception.OnsetFileLimitExceeded()
複製程式碼
2.2》限制注入檔案路徑長度和注入內容長度
max_path = 0
max_context = 0
for path, content in injected_files:
max_path = max(max_path, len(path))
max_content = max(max_content, len(content))
try:
objects.Quotas.limit_check(context,
injected_file_path_bytes=max_path,
injected_file_content_bytes=max_content)
複製程式碼
3》_check_requested_image
3.1》檢測映象狀態
# 映象狀態應該為active
if image['status'] != 'active':
raise exception.ImageNotActive(image_id=image_id)
複製程式碼
3.2》檢測drive配置選項
# drive配置選項應該為“可選”或“強制”
image_properties = image.get('properties', {})
config_drive_option = image_properties.get(
'img_config_drive', 'optional')
if config_drive_option not in ['optional', 'mandatory']:
raise exception.InvalidImageConfigDrive(
config_drive=config_drive_option)
複製程式碼
3.3》檢測記憶體空間
# 記憶體空間應該足夠大
if instance_type['memory_mb'] < int(image.get('min_ram') or 0):
raise exception.FlavorMemoryTooSmall()
複製程式碼
3.4》(還有一些檢測,有待研究)
(7)獲取例項組
instance_group = self._get_requested_instance_group(context,
scheduler_hints, check_server_group_quota)複製程式碼
(未完待續...)