接觸Openstack也有一段時間了,因爲工作需要着重閱讀了Glance、Nova、Cinder模塊源碼並通過搭建的devstack測試環境調試學習相關操作的執行流程。現準備陸續將相關的學習成果和心得在博客中分享出來,希望對讀者有所幫助。這是第一篇 - 雲主機啓動過程源碼分析 - 介紹雲主機啓動時,接口調用在nova-api、nova-conductor、nova-sechduler及nova-compute等組件上的流轉過程。
既可以通過Dashboard啓動雲主機,也可以通過Nova CLI命令行實現,下面的命令啓動了一臺類型爲2(--flavor 2
)名爲vm2的雲主機,使用的鏡像是:226bc6e5-60d7-4a2c-bf0d-a568a1e26e00
,--debug
參數用打開調試信息:
nova --debug boot --flavor 2 --image 226bc6e5-60d7-4a2c-bf0d-a568a1e26e00 vm2
nova-api
啓動雲主機過程中,nova-api
主要完成輸入參數的驗證和裝配,之後根據輸入參數創建雲主機實例對象、記錄數據庫條目,最後將請求轉發給nova-conductor
進行後續處理。簡單分析如下:
根據nova-api
啓動過程中建立的路由映射以及調試信息的佐證,如下:
我們知道啓動雲主機的請求經過novaclient
的處理後,發送給了nova-api,並由nova/api/openstack/servers.py.ServersController.create
處理,從代碼上看該方法的處理過程比較簡單,主要是完成:用戶參數的轉換、policy驗證,方法聲明如下:
@wsgi.response(202)
@extensions.expected_errors((400, 403, 409, 413))
@validation.schema(schema_server_create_v20, '2.0', '2.0')
@validation.schema(schema_server_create, '2.1')
def create(self,req,body):
來看看body參數的內容:
body參數是該次請求的請求體,顯示的是待啓動雲主機的配置參數,包含有:雲主機名、image uuid
,雲主機類型等,很明顯這就是之前在nova boot
命令行中指定的參數,之後根據extension
擴展方法轉換用戶參數,完成參數解析,policy認證等,之後將請求轉發給nova/compute/api.py.API.create
。
API.create
方法雖然參數很多,但啓動雲主機必須的參數只有instance_type
及image_href
,分別指定主機類型及image uuid,處理邏輯也非常的簡單:執行網絡及塊設備policy檢查(如果有),接着將參數原封不動的傳遞給_create_instance
方法,該方法主要完成如下工作:
- 驗證輸入參數
- 獲取鏡像元信息
- 生成主機配置並創建主機實例對象
- 在數據庫生成主機信息
下面具體來看看該方法的實現(說明見紅色註釋部分):
//nova/compute/api.py.API
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):
"""Verify all the input parameters regardless of the provisioning
strategy being performed and schedule the instance(s) for
creation.
"""
# Normalize and setup some parameters
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()
#根據uuid獲取image的metadata信息
if image_href:
image_id, boot_meta = self._get_image(context, image_href)
else:
image_id = None
boot_meta = self._get_bdm_image_metadata(
context, block_device_mapping, legacy_bdm)
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', {})
#根據輸入參數,生成主機配置
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)
# max_net_count is the maximum number of instances requested by the
# user adjusted for any network quota constraints, including
# consideration of connections to each requested network
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
#歸總用戶輸入(--block-device-mapping參數)、鏡像屬性中指定、flavor配置中指定的塊設備映射
block_device_mapping = self._check_and_transform_bdm(context,
base_options, instance_type, boot_meta, min_count, max_count,
block_device_mapping, legacy_bdm)
# We can't do this check earlier because we need bdms from all sources
# to have been merged in order to get the root bdm.
self._checks_for_create_and_rebuild(context, image_id, boot_meta,
instance_type, metadata, injected_files,
block_device_mapping.root_bdm())
instance_group = self._get_requested_instance_group(context,
scheduler_hints, check_server_group_quota)
#創建雲主機實例對象,並生成數據庫條目
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)
#scheduler需要用的過濾選項
filter_properties = self._build_filter_properties(context,
scheduler_hints, forced_host,
forced_node, instance_type)
#更新數據庫中實例的啓動狀態
for instance in instances:
self._record_action_start(context, instance,
instance_actions.CREATE)
#調用conductor api,通過conductor rpc將請求轉發給conductor manager
self.compute_task_api.build_instances(context,
instances=instances, image=boot_meta,
filter_properties=filter_properties,
admin_password=admin_password,
injected_files=injected_files,
requested_networks=requested_networks,
security_groups=security_groups,
block_device_mapping=block_device_mapping,
legacy_bdm=False)
return (instances, reservation_id)
接着上面的分析,nova/conductor/api.py.ComputeTaskAPI.build_instance
直接將請求轉發給nova/conductor/rpcapi.py.ComputeTaskAPI.build_instance
,來看看該函數的實現(請根據註解閱讀):
//nova/conductor/rpcapi.py.ComputeTaskAPI
def build_instances(self, context, instances, image,
filter_properties,
admin_password, injected_files, requested_networks,
security_groups, block_device_mapping, legacy_bdm=True):
'''
該函數的邏輯清晰簡單:根據rpc client的版本,調整雲主機參數:主機過濾參數,網絡參數,塊設備映射參數,最後通過一個異步rpc調用,將請求轉發給conductor-manager
'''
image_p = jsonutils.to_primitive(image)
version = '1.10'
if not self.client.can_send_version(version):
version = '1.9'
if 'instance_type' in filter_properties:
flavor = filter_properties['instance_type']
flavor_p = objects_base.obj_to_primitive(flavor)
filter_properties = dict(filter_properties,
instance_type=flavor_p)
kw = {'instances': instances, 'image': image_p,
'filter_properties': filter_properties,
'admin_password': admin_password,
'injected_files': injected_files,
'requested_networks': requested_networks,
'security_groups': security_groups}
if not self.client.can_send_version(version):
version = '1.8'
kw['requested_networks'] =
kw['requested_networks'].as_tuples()
if not self.client.can_send_version('1.7'):
version = '1.5'
bdm_p =
objects_base.obj_to_primitive(block_device_mapping)
kw.update({'block_device_mapping': bdm_p,
'legacy_bdm': legacy_bdm})
#返回_CallContext對象
cctxt = self.client.prepare(version=version)
#發送異步rpc消息到消息隊列,conductor-manager會收到該消息
cctxt.cast(context, 'build_instances', **kw)
至此,nova-api
的任務就完成了,下一篇文章分析conductor-manage的處理過程。