深挖Openstack Nova - 实例创建(1)

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复制代码


(未完待续...)


转载于:https://juejin.im/post/5b0e4fa36fb9a009fb32c950

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值