【Nova】nova-compute代码学习3-创建虚拟机

本文探讨了在Nova中创建虚拟机的详细流程,包括block_device_mapping的解析,区分root、swap和ephemeral设备。创建过程涉及BUILDING、NETWORKING、BLOCK_DEVICE_MAPPING和SPAWNING阶段,如分配网络资源、创建磁盘文件、设置iptables规则及启动虚拟机。
摘要由CSDN通过智能技术生成

这一篇学习下nova-compute创建虚拟机的流程

首先说下,创建虚拟机实例时,我们需要给它分配磁盘设备block device,OpenStack中block device有4种类型: root device、swap device、ephemeral device和cinder volume, 前3种对应我们创建flavor时的 根磁盘、交换盘和临时磁盘,因为cinder我至今没有使用过,因此涉及到的时候会直接跳过,后面会单独学习。

root device: 提供boot loader,虚拟机会从这里启动,可以有多个,但是需要确定其中一个的boot_index = 0

swap device:作为交换分区,只能有1个,与虚拟内存有关

ephemeral device:额外提供的临时设备

针对每个block device在数据库里都包括一条block_device_mapping记录,顾名思义, 就是块设备的映射信息,里面包括device_type(设备类型,disk/cdrom/floppy/lun),device_name(设备名,例如/dev/vda, 没有指定的情况下使用默认值), boot_index(引导顺序), disk_bus(总线类型,没有指定的情况下使用虚拟化驱动支持的默认类型),source_type(image/snapshot/volume/blank,决定块设备产生的源头, 例如image, 那么块设备就是使用镜像创建的, 这种情况下还要记录对应的image_id),destination_type(local/volume,决定块设备的实际存储位置, 是在cinder提供的volume上, 还是在hypervisor本地)、guest_format

那怎么通过block_device_mapping中的信息判断block device是哪种类型呢?

swap device:source_type为blank,destination_type为local,guest_format为swap

ephemeral device: source_type为blank 且 不是swap device, 可见

root device: source_type不为blank 且 boot_index = 0

之前我们在nova-scheduler调度过程分析中提到过在调度完成后,由nova-scheduler通过调用目标主机nova-compute服务的run_instance RpcAPI来发起虚拟机实例的实际创建工作,那么就从run_instance这个RpcAPI开始进行分析,可以看出创建虚拟机实例的过程大致分为以下几步:

1.BUILDING阶段,验证实例组策略;

2.NETWORKING阶段,异步为实例分配网络资源;

3.BLOCK_DEVICE_MAPPING阶段,为实例的块设备获取默认的设备名

4.SPAWNING阶段,缓存镜像,为实例创建磁盘文件(包括文件注入),进行volume相关操作,配置实例的iptables防火墙规则,通过虚拟化驱动为实例创建虚拟机并启动,等待虚拟机的电源状态变为RUNNING,创建即宣告完成。

文件注入有2种方式:一种是通过cdrom挂载iso文件,另一种是将文件写入虚拟机的guest文件系统

# 验证给定的disk_bus在给定的virt_type下是否是有效的
def is_disk_bus_valid_for_virt(virt_type, disk_bus):
    valid_bus = {
        'qemu': ['virtio', 'scsi', 'ide', 'usb', 'fdc'],
        'kvm': ['virtio', 'scsi', 'ide', 'usb', 'fdc'],
        'xen': ['xen', 'ide'],
        'uml': ['uml'],
        'lxc': ['lxc'],
        }

    if virt_type not in valid_bus:
        raise exception.UnsupportedVirtType(virt=virt_type)

    return disk_bus in valid_bus[virt_type]
    
# 获取指定设备类型的总线
def get_disk_bus_for_device_type(virt_type, image_meta=None, device_type="disk"):
    if image_meta:
        key = "hw_" + device_type + "_bus"
        disk_bus = image_meta.get('properties', {}).get(key)
        # 首先尝试从镜像元数据中找出disk_bus
        if disk_bus is not None:
            # 如果镜像元数据中的disk_bus不为空, 那么验证disk_bus是否是有效的
            if not is_disk_bus_valid_for_virt(virt_type, disk_bus):
                raise exception.UnsupportedHardware(model=disk_bus,
                                                    virt=virt_type)
            # 如果有效, 直接返回disk_bus
            return disk_bus

    # 然后尝试返回hypervisor默认的disk_bus
    if virt_type == "uml":
        if device_type == "disk":
            return "uml"
    elif virt_type == "lxc":
        return "lxc"
    elif virt_type == "xen":
        if device_type == "cdrom":
            return "ide"
        elif device_type == "disk":
            return "xen"
    elif virt_type in ("qemu", "kvm"):
        if device_type == "cdrom":
            arch = libvirt_utils.get_arch(image_meta)
            if arch in ("ppc", "ppc64"):
                return "scsi"
            else:
                return "ide"
        elif device_type == "disk":
            return "virtio"
        elif device_type == "floppy":
            return "fdc"

    return None

# 从设备名获取其总线
def get_disk_bus_for_disk_dev(virt_type, disk_dev):
    if disk_dev[:2] == 'hd':
        return "ide"
    elif disk_dev[:2] == 'sd':
        if virt_type == "xen":
            return "xen"
        else:
            return "scsi"
    elif disk_dev[:2] == 'vd':
        return "virtio"
    elif disk_dev[:2] == 'fd':
        return "fdc"
    elif disk_dev[:3] == 'xvd':
        return "xen"
    elif disk_dev[:3] == 'ubd':
        return "uml"
    else:
        raise exception.NovaException(
            _("Unable to determine disk bus for '%s'") %
            disk_dev[:1])

# 从磁盘总线类型获取设备名前缀
def get_dev_prefix_for_disk_bus(disk_bus):
    # 如果配置了disk_prefix, 那么直接返回即可
    if CONF.libvirt.disk_prefix:
        return CONF.libvirt.disk_prefix
    if disk_bus == "ide":
        return "hd"
    elif disk_bus == "virtio":
        return "vd"
    elif disk_bus == "xen":
        return "sd"
    elif disk_bus == "scsi":
        return "sd"
    elif disk_bus == "usb":
        return "sd"
    elif disk_bus == "fdc":
        return "fd"
    elif disk_bus == "uml":
        return "ubd"
    elif disk_bus == "lxc":
        return None
    else:
        raise exception.NovaException(
            _("Unable to determine disk prefix for %s") %
            disk_bus)

# 通过磁盘总线获取最多支持的设备数量
def get_dev_count_for_disk_bus(disk_bus):
    # ide总线只支持4个设备
    if disk_bus == "ide":
        return 4
    else:
        return 26
            
# 通过总线类型获取设备名
def find_disk_dev_for_disk_bus(mapping, bus, last_device=False):
    # 通过总线类型获取设备名前缀
    dev_prefix = get_dev_prefix_for_disk_bus(bus)
    if dev_prefix is None:
        return None

    # 通过总线类型获取最多支持的设备数量
    max_dev = get_dev_count_for_disk_bus(bus)
    # last_device用于指定是否是最后一个设备
    if last_device:
        devs = [max_dev - 1]
    else:
        devs = range(max_dev)

    for idx in devs:
        # Linux上的设备名是有规律的, 对于同种类型的设备, 它们的名称从"<设备名前缀>a"依次递增
        disk_dev = dev_prefix + chr(ord('a') + idx)
        # 还要判断设备名是否在已有的映射信息中, 不能重名
        if not has_disk_dev(mapping, disk_dev):
            return disk_dev

    raise exception.NovaException(
        _("No free disk device names for prefix '%s'"),
        dev_prefix)
    
# 获取root块设备的信息
def get_root_info(virt_type, image_meta, root_bdm, disk_bus, cdrom_bus, root_device_name=None):
    # source_type为image, destination_type为local?
    no_root_bdm = (not root_bdm or (
        root_bdm.get('source_type') == 'image' and
        root_bdm.get('destination_type') == 'local'))
    if no_root_bdm:
        if (image_meta and image_meta.get('disk_format') == 'iso'):
            # 如果镜像的disk_format是iso, 那说明我们要从cdrom启动
            root_device_bus = cdrom_bus
            root_device_type = 'cdrom'
        else:
            # 从硬盘启动
            root_device_bus = disk_bus
            root_device_type = 'disk'
        if root_device_name:
            # 通常我们可以设备名获取其设备类型继而了解其总线类型, 使用Linux的人应该有经验
            # 从root设备名称获取其总线类型, 当设备名为vd*, 意味着我们要使用virtio总线
            root_device_bus = get_disk_bus_for_disk_dev(virt_type,
                                                        root_device_name)
        else:
            # 反过来, 我们也可以根据总线类型获取到设备名, 当然设备名不能与已有设备重名
            root_device_name = find_disk_dev_for_disk_bus({}, root_device_bus)
    
        # 返回root块设备信息
        return {'bus': root_device_bus,
                'type': root_device_type,
                'dev': block_device.strip_dev(root_device_name),
                'boot_index': '1'}
    else:
        # 如果root块设备没有设备名且指定了root_device_name
        if not get_device_name(root_bdm) and root_device_name:
            # 因为要修改root_bdm, 对root_bdm进行拷贝, 避免对原始root_bdm进行污染
            root_bdm = root_bdm.copy()
            root_bdm['device_name'] = root_device_name
        # 返回root_bdm的信息, 包括bus、dev、type、format和boot_index等
        return get_info_from_bdm(virt_type, root_bdm, {}, disk_bus)

# 获取实例最终的块设备映射信息
def get_disk_mapping(virt_type, instance,
                     disk_bus, cdrom_bus,
                     block_device_info=None,
                     image_meta=None, rescue=False):
    # 获取实例的云主机类型即flavor
    inst_type = flavors.extract_flavor(instance)

    mapping = {}

    # 获取所有块设备的名称列表, 例如['sda', 'sdb']
    pre_assigned_device_names = \
    [block_device.strip_dev(get_device_name(bdm)) for bdm in itertools.chain(
        driver.block_device_info_get_ephemerals(block_device_info), # DriverEphemeralBlockDevice实例列表
        [driver.block_device_info_get_swap(block_device_info)],     # DriverSwapBlockDevice实例列表, 只有一个元素
        driver.block_device_info_get_mapping(block_device_info))    # DriverSnapshotBlockDevice和DriverVolumeBlockDevice实例列表
     if get_device_name(bdm)]

    if virt_type == "lxc":
        root_disk_bus = disk_bus
        root_device_type = 'disk'

        # 获取总线类型上下个设备的设备信息作为root块设备信息
        root_info = get_next_disk_info(mapping,
                                       root_disk_bus,
                                       root_device_type,
                                       boot_index=1)
        mapping['root'] = root_info
        mapping['disk'] = root_info

        return mapping

    if rescue:
        # 用于救援模式
        # 获取总线类型上下个设备的设备信息作为root块设备信息和救援用的块设备信息
        rescue_info = get_next_disk_info(mapping,
                                         disk_bus, boot_index=1)
        mapping['disk.rescue'] = rescue_info
        mapping['root'] = rescue_info

        os_info = get
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值