OpenStack Nova深入學習 -- 建立instance的過程之原始碼分析
Nova的核心元件有nova API, nova Conductor,nova Scheduler和nova compute。如下圖所示:
nova主要元件的作用:1). nova API -- 主要是接收HTTP請求(通常來自nova client),將其轉換成命令,並通過oslo message和nova Conductor或者nova Compute互動。為了完成虛機的處理(比如建立、修改,刪除等)操作,nova API還需要通過HTTP協議向Keystone, Neutron, Glance發出請求。
2). nova Conductor -- 主要作為一個數據庫代理存在,避免nova Compute直接操作資料庫。
3). nova Compute -- 主要是操縱compute node上的hypervisor對instance進行建立和管理。
4). nova Scheduler -- 主要是用來判斷新建立的instance應該被放在哪一個compute node上,它的配置在/etc/nova/nova.conf裡完成,預設配置如下:
scheduler_available_filters=nova.scheduler.filters.all_filtersscheduler_default_filters=RetryFilter,AvailabilityZoneFilter,RamFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter,ServerGroupAffinityFilter
scheduler_driver=nova.scheduler.filter_scheduler.FilterScheduler
其中,scheduler_driver決定了nova用什麼樣的scheduler,scheduler_default_filters決定了在一個compute node被選中作為instance的宿主前,都需要過哪些檢查關口,如下是這些檢查關口的說明:
-- RetryFilter:找出還沒有被嘗試filter過的compute node
-- AvailabilityZoneFilter:必須在規定的availability zone裡選compute node
-- RamFilter:compute node必須有足夠的記憶體支援instance的建立
-- ComputeFilter:必須有compute node存在
-- ComputeCapabilitiesFilter:compute node必須滿足instance建立需要的extra specs
-- ImagePropertiesFilter:compute node必須滿足映象裡定義的屬性,比如architecture是否是qcow2,是否支援KVM hyperviosr等。
-- ServerGroupAntiAffinityFilter:如果建立多個instance,需要讓這些instance分佈在不同的compute node上
-- ServerGroupAffinityFilter:如果建立多個instance,需要讓這些instance分佈在相同的compute node上
下面,通過nova的原始碼看一下一個新的instance被建立需要經過哪些過程,程式碼來自Liberty版本:
建立instance的入口程式碼在nova/api/openstack/compute/servers.py,方法是create(),如下:
def create(self, req, body):
(instances, resv_id) = self.compute_api.create(context,
inst_type,
image_uuid,
display_name=name,
display_description=name,
metadata=server_dict.get('metadata', {}),
admin_password=password,
requested_networks=requested_networks,
check_server_group_quota=True,
**create_kwargs)
compute_api物件來自compute.API類,如下。而compute.API來自nova/compute/__init__.py。
self.compute_api = compute.API(skip_policy_check=True)
檢視nova/compute/__init__.py,看到API()返回的就是nova.compute.api.API,如下。所以compute_api就是nova.compute.api.API。
CELL_TYPE_TO_CLS_NAME = {'api': 'nova.compute.cells_api.ComputeCellsAPI',
'compute': 'nova.compute.api.API',
None: 'nova.compute.api.API',
}
def _get_compute_api_class_name():
"""Returns the name of compute API class."""
cell_type = nova.cells.opts.get_cell_type()
return CELL_TYPE_TO_CLS_NAME[cell_type]
def API(*args, **kwargs):
class_name = _get_compute_api_class_name()
return importutils.import_object(class_name, *args, **kwargs)
檢視nova/compute/api.py,看到create()方法呼叫了_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,
…
return self._create_instance(
context, instance_type,
image_href, kernel_id, ramdisk_id,
min_count, max_count,
…
_create_instance()也在nova/compute/api.py中定義,它又呼叫了compute_task_api物件的build_instances()方法,如下:
def _create_instance(self, context, instance_type,
image_href, kernel_id, ramdisk_id,
min_count, max_count,
…
self.compute_task_api.build_instances(context,
instances=instances, image=boot_meta,
filter_properties=filter_properties,
…
compute_task_api物件也在nova/compute/api.py中定義,來自conductor.ComputeTaskAPI物件,如下:
def compute_task_api(self):
if self._compute_task_api is None:
# TODO(alaski): Remove calls into here from conductor manager so
# that this isn't necessary. #1180540
from nova import conductor
self._compute_task_api = conductor.ComputeTaskAPI()
return self._compute_task_api
ComputeTaskAPI物件在nova/conductor/__init__.py中定義,來自nova.conductor.api.ComputeTaskAPI,如下:
from nova.conductor import api as conductor_api
def ComputeTaskAPI(*args, **kwargs):
use_local = kwargs.pop('use_local', False)
if oslo_config.cfg.CONF.conductor.use_local or use_local:
api = conductor_api.LocalComputeTaskAPI
else:
api = conductor_api.ComputeTaskAPI
return api(*args, **kwargs)
檢視nova/conductor/api.py,build_instances()方法呼叫了conductor_compute_rpcapi物件的build_instances()方法。conductor_compute_rpcapi物件來自nova/conductor/rpcapi.py中定義的ComputeTaskAPI類。
class ComputeTaskAPI(object):
…
def __init__(self):
self.conductor_compute_rpcapi = rpcapi.ComputeTaskAPI()
…
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,
security_groups, block_device_mapping, legacy_bdm=True):
self.conductor_compute_rpcapi.build_instances(context,
instances=instances, image=image,……
nova/conductor/rpcapi.py是專門處理nova元件之間的RPC呼叫的。因為這裡定義的topic是conductor,根據http://www.cnblogs.com/littlebugfish/p/4090311.html中的描述的訊息佇列原理,這裡將轉到nova-conductor程序中進行處理。
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,
…
cctxt = self.client.prepare(version=version)
cctxt.cast(context, 'build_instances', **kw)
檢視nova/conductor/manager.py,看到build_instances()方法會呼叫scheduler_client.select_destinations()和compute_rpcapi.build_and_run_instance()。前者會呼叫schedular的RPC API選擇在哪些主機上建立instance,後者會通過RPC請求nova-compute服務去構建和執行instance。
class ComputeTaskManager(base.Base):
…
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,…
…
try:
…
hosts = self.scheduler_client.select_destinations(context,
request_spec, filter_properties)
…
for (instance, host) in itertools.izip(instances, hosts):
…
self.compute_rpcapi.build_and_run_instance(context,
instance=instance, host=host['host'], image=image,
request_spec=request_spec,…
檢視nova/compute/manager.py,整個的呼叫過程如下:
build_and_run_instance() --> _do_build_and_run_instance() --> _build_and_run_instance() --> driver.spawn()
driver物件是在nova/virt/driver.py裡定義,呼叫load_compute_driver()方法獲得並載入compute driver。compute driver的定義是在/etc/nova/nova.conf裡由compute_driver的值確定。
def __init__(self, compute_driver=None, *args, **kwargs):
self.driver = driver.load_compute_driver(self.virtapi, compute_driver)
def load_compute_driver(virtapi, compute_driver=None):
if not compute_driver:
compute_driver = CONF.compute_driver
if not compute_driver:
LOG.error(_LE("Compute driver option required, but not specified"))
sys.exit(1)
LOG.info(_LI("Loading compute driver '%s'"), compute_driver)
try:
driver = importutils.import_object_ns('nova.virt',
compute_driver,
virtapi)
return utils.check_isinstance(driver, ComputeDriver)