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

self_blank發表於2018-06-05

--------------- 緊接上篇 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)複製程式碼


(未完待續...)


相關文章