nova版本:Liberty
一、创建实例涉及的主要目录结构
(1)/nova/api/ec2/cloud.py
:云控制器,执行EC2 REST API的调用,这个调用是
通过AMQP RPC分派到其他节点
(2)/nova/image/s3.py
:从S3获取数据,建立镜像等相关方法
(3)/nova/image/glance.py
:使用Glance作为后端的镜像服务的实现
(4)/nova/compute/api.py
:处理关于计算资源的所有的请求
(5)/nova/objects/quotas.py
:实例配额和浮动IP
(6)/nova/objects/instance_action.py
:对一个实例的所有可能的操作
(7)/nova/db/api.py
:定义数据库访问接口
(8)/nova/db/sqlalchemy/api.py
:SQLAlchemy后端的执行
(9)/nova/conductor/api.py
:处理conductor service的所有请求
(10)/nova/conductor/rpcapi.py
:conductor RPC API客户端
(11)/nova/notifications.py
:系统常见的多层次通知的相关方法
(12)/nova/rpc.py
:远程过程调用方法
二. 创建实例时组件之间的调用关系
由图可看出,各个组件之间都是通过RPC调用来实现,api模块接收请求,conductor处理请求并由scheduler调度模块筛选出“最优”的创建实例的节点,然后由compute模块处理数据库相关的操作,并调用对应的hypervisor来实现具体的创建实例的功能。
调用所涉及的函数如下:
(1) /nova/api/openstack/compute/servers.py
create
(2) /nova/compute/api.py
create
(3) /nova/conductor/rpcapi.py
build-instances
(4) /nova/conductor/manager.py
build-instances
(5) /nova/scheduler/filter_scheduler.py
select_destinations
(4)(5)互相调用
(6) /nova/compute/rpcapi.py
build_and_run_instance
(7) /nova/compute/manager.py
build_and_run_instance
(8) /nova/virt/libvirt/driver.py
hypervisor
三. 源码具体分析
1. 使用EC2 API来创建一个实例
调用/nova/api/ec2/cloud.py
中的run_instances
方法:
# 准备实例,并且发送实例的信息和要运行实例的请求消息到远程调度器scheduler
# 实现实例的建立和运行,由调度器完成
# context: 上下文信息
def run_instances(self, context, **kwargs)
1.1 首先是对参数的格式校验
# 设置最小建立实例的数目
min_count = int(kwargs.get('min_count', 1))
# 设置最大建立实例的数目,如果没有值,则设置为最小建立实例数
max_count = int(kwargs.get('max_count'), min_count)
# 对最小实例数和最大实例数进行整数验证
try:
min_count = utils.validate_integer(
min_count, "min_count", min_value=1)
max_count = utils.validate_integer(
max_count, "max_count", min_value=1)
except exception.InvalidInput as e:
raise exception.InvalidInput(message=e.format_message())
if min_count > max_count:
msg = _('min_count must be <= max_count')
raise exception.InvalidInput(message=msg)
1.2 通过client_token来判断是否匹配db中预留的ID,如果有对应ID,则直接返回之前格式化好的实例,而不需要重新创建。
client_token = kwargs.get('client_token')
if client_token:
# 通过client_token获取db中对应的预留ID
resv_id = self._resv_id_from_token(context, client_token)
if resv_id:
# 如果有,表面当前的client_token已经匹配上一个预留的ID
# 则返回一个格式化运行实例,但不实际去创建新的实例
return self._format_run_instances(context, resv_id)
1.3 获取对应的虚拟机内核和ramdisk数据
# kernel_id为虚拟机内核ID值
if kwargs.get('kernel_id'):
# 根据kernel_id查询数据库中匹配的S3镜像数据,获取它的uuid属性,并返回
kernel = self._get_image(context, kwargs['kernel_id'])
# 返回匹配的db.sqlalchemy.models.S3Image.uuid给kwargs['kernel_id']
kwargs['kernel_id'] = ec2utils.id_to_glance_id(context,
kernel['id'])
# 跟上面相似,这里是把对应的镜像image数据返回给ramdisk
if kwargs.get('ramdisk_id'):
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
kwargs['ramdisk_id'] = ec2utils.id_to_glance_id(context,
ramdisk['id'])
1.4 解析块设备映射bdm
# 循环获取每一个块设备映射
# 解析块设备映射bdm
for bdm in kwargs.get('block_device_mapping', []):
_parse_block_device_mapping(bdm)
其中,_parse_block_device_mapping
方法如下:
# 解析块设备映射bdm
# 更新实例数据的snapshot_id和volume_id
def _parse_block_device_mapping(bdm):
ebs = bdm.pop('ebs', None)
if ebs:
ec2_id = ebs.pop('snapshot_id', None)
if ec2_id:
# 判断如果是快照
# 则根据获取的ec2_id值,从匹配的数据库信息中获取uuid值赋值给bdm['snapshot_id']
# 更新bdm中的相应数据
if ec2_id.startswith('snap-'):
bdm['snapshot_id'] = ec2utils.ec2_snap_id_to_uuid(ec2_id)
# 判断如果是volume(卷)
elif ec2_id.startswith('vol-'):
bdm['volume_id'] = ec2utils.ec2_snap_id_to_uuid(ec2_id)
ebs.setdefault('delete_on_termination', True)
bdm.update(ebs)
return bdm
1.5 获取对应的镜像image数据,并检查镜像状态是否可用
# 获取镜像image数据
image = self._get_image(context, kwargs['image_id'])
image_uuid = ec2utils.id_to_glance_id(context, image['id'])
# 获取镜像image的状态
if image:
image_state = self._get_image_state(image)
else:
raise exception.ImageNotFoundEC2(image_id=kwargs['image_id'])
# 镜像状态必须为可用
if image_state != 'available':
msg = _('Image must be available')
raise exception.ImageNotActive(message=msg)
1.6 获取其他信息,以便构建create方法
# 获取实例初始化的结束状态
# 这里是用作下面create方法的shutdown_terminate
iisb = kwargs.get('instance_initiated_shutdown_behavior', 'stop')
shutdown_terminate = (iisb == 'terminate')
# flavor: 类型模板
# get_by_name:通过给定的name检索单个实例类型信息
# 以字典的形式返回查询结果
# 这里是用做下面create方法的instance_type
flavor = objects.Flavor.get_by_name(context,
kwargs.get('instance_type', None))
1.7 执行实例创建create方法
# create:创建实例
# 这里实现请求消息的发送
(instances, resv_id) = self.compute_api.create(context,
instance_type=flavor,
image_href=image_uuid,
max_count=int(kwargs.get('max_count', min_count)),
min_count=min_count,
kernel_id=kwargs.get('kernel_id'),
ramdisk_id=kwargs.get('ramdisk_id'),
key_name=kwargs.get('key_name'),
user_data=kwargs.get('user_data'),
security_group=kwargs.get('security_group'),
availability_zone=kwargs.get('placement', {}).get(
'availability_zone'),
block_device_mapping=kwargs.get('block_device_mapping', {}),
shutdown_terminate=shutdown_terminate)
这里将构造创建实例所需要的数据,并发送给compute模块进行创建,同时返回新实例对象instances和保留的resv_id。
参照1.2可看出,resv_id是作为是否已经创建好实例的重要标示。
1.8 做最后的格式化工作并返回实例
# 格式化运行实例并返回
instances = self._format_run_instances(context, resv_id)
if instances:
instance_ids = [i['instanceId'] for i in instances['instancesSet']]
# 增加client_token到预留ID的映射
self._add_client_token(context, client_token, instance_ids)
return instances
将创建好的新实例的ID放入一个集合Set里,这里也对应着1.2中通过client_token获取对应的resv_id。
2. 分析_get_image方法
该方法在虚拟机内核、ramdisk和镜像image数据时都有调用,同样在/nova/api/ec2/cloud.py
里
# 获取ec2_id指定的镜像image元数据
def _get_image(self, context, ec2_id):
2.1 先转换internal_id,然后获取对应的image数据
try:
# 转换一个EC2的ID为一个实例(镜像)的ID(INT格式) (主要是格式变换问题)
internal_id = ec2utils.ec2_id_to_id(ec2_id)
# show:
# 转换镜像image的ID值internal_id到image_uuid
# 根据给定的image_uuid从glance下载image元数据,并且转换为字典格式
# 转换镜像image中的image_uuid到新的image_id
# 更新image当中的相关属性,返回更新后的image数据
# 注:S3是EC2的存储平台,所有这里调用/nova/image/s3.py中的show方法
image = self.image_service.show(context, internal_id)
except (exception.InvalidEc2Id, exception.ImageNotFound):
filters = {'name': ec2_id}
images = self.image_service_detail(context, filters=filters)
try:
return images[0]
except IndexError:
raise exception.ImageNotFound(image_id=ec2_id)
2.2 获取到镜像image后,还要判断image_type是否一致,最后才返回镜像image
# flag:通过ec2_id获取镜像image类型
image_type = ec2_id.split('-')[0]
# 验证找到的镜像image是否为所要求找的镜像
# 如果通过ec2_id返回的镜像image的image_type和flag处的image_type不一致时
# 则抛出异常,提示所要求找的镜像找不到
if ec2utils.image_type(image.get('container_format')) != image_type:
raise exception.ImageNotFound(image_id=ec2_id)
return image
(未完待续...)