Fabain4 IT路上的潜行者

libvirt boot


介绍

openstack nova启动虚拟机底层通过调用libvirt spawn方式创建虚拟机,创建虚拟机的instancem,network_info等参数在调用spawn方法的时候进行了传递进来

spawn 步骤

步骤主要分为三个步骤,步骤如下:

  • 创建镜像阶段, self._create_image
  • self._get_guest_xml 创建instance的xml使用文件,libvirt.xml文件, self._get_guest_xml
  • 创建domain与network阶段,self._create_domain_and_network

spawn代码分析

  • 镜像创建阶段代码分析

首先由nova/compute/manager.py下的类ComputeManager中的_build_and_run_instance方法调用

def spawn(self, context, instance, image_meta, injected_files,
              admin_password, network_info=None, block_device_info=None):

        # disk_info 为一个字典,其中包含了虚拟机的磁盘类型,挂载到虚拟机的磁盘名称等信息
        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
                                            instance,
                                            block_device_info,
                                            image_meta)

        # 这里开始调用创建镜像
        self._create_image(context, instance,
                           disk_info['mapping'],
                           network_info=network_info,
                           block_device_info=block_device_info,
                           files=injected_files,
                           admin_pass=admin_password)

        # 这里比较重要开始创建虚拟机的libvirt.xml文件,在创建过程中会检测instance/xxxxxx的目录等
        xml = self._get_guest_xml(context, instance, network_info,
                                  disk_info, image_meta,
                                  block_device_info=block_device_info,
                                  write_to_disk=True)

        # 这里开始执行创建instance与网络
        self._create_domain_and_network(context, xml, instance, network_info,
                                        block_device_info, disk_info=disk_info)
        LOG.debug("Instance is running", instance=instance)

        def _wait_for_boot():
            """Called at an interval until the VM is running."""
            state = self.get_info(instance)['state']

            if state == power_state.RUNNING:
                LOG.info(_LI("Instance spawned successfully."),
                         instance=instance)
                raise loopingcall.LoopingCallDone()

        timer = loopingcall.FixedIntervalLoopingCall(_wait_for_boot)
        timer.start(interval=0.5).wait()

下面展开分析create_image

  def _create_image(self, context, instance,
                      disk_mapping, suffix='',
                      disk_images=None, network_info=None,
                      block_device_info=None, files=None,
                      admin_pass=None, inject_files=True):
        booted_from_volume = self._is_booted_from_volume(
            instance, disk_mapping)

        def image(fname, image_type=CONF.libvirt.images_type):
            return self.image_backend.image(instance,
                                            fname + suffix, image_type)

        def raw(fname):
            return image(fname, image_type='raw')

        # ensure directories exist and are writable
        fileutils.ensure_tree(libvirt_utils.get_instance_path(instance))

        LOG.info(_LI('Creating image'), instance=instance)

        # NOTE(dprince): for rescue console.log may already exist... chown it.
        self._chown_console_log_for_instance(instance)

        # NOTE(yaguang): For evacuate disk.config already exist in shared
        # storage, chown it.
        self._chown_disk_config_for_instance(instance)

        # NOTE(vish): No need add the suffix to console.log
        # 这里会创建一个console.log 在instance/xxxxxx目录下
        libvirt_utils.write_to_file(
            self._get_console_log_path(instance), '', 7)

        if not disk_images:
            disk_images = {'image_id': instance['image_ref'],
                           'kernel_id': instance['kernel_id'],
                           'ramdisk_id': instance['ramdisk_id']}

        if disk_images['kernel_id']:
            fname = imagecache.get_cache_fname(disk_images, 'kernel_id')
            raw('kernel').cache(fetch_func=libvirt_utils.fetch_image,
                                context=context,
                                filename=fname,
                                image_id=disk_images['kernel_id'],
                                user_id=instance['user_id'],
                                project_id=instance['project_id'])
            if disk_images['ramdisk_id']:
                fname = imagecache.get_cache_fname(disk_images, 'ramdisk_id')
                raw('ramdisk').cache(fetch_func=libvirt_utils.fetch_image,
                                     context=context,
                                     filename=fname,
                                     image_id=disk_images['ramdisk_id'],
                                     user_id=instance['user_id'],
                                     project_id=instance['project_id'])

        # 获取flavor信息
        inst_type = flavors.extract_flavor(instance)

        # NOTE(ndipanov): Even if disk_mapping was passed in, which
        # currently happens only on rescue - we still don't want to
        # create a base image.
        if not booted_from_volume:
            root_fname = imagecache.get_cache_fname(disk_images, 'image_id')
            size = instance['root_gb'] * units.Gi

            if size == 0 or suffix == '.rescue':
                size = None

            backend = image('disk')
            if backend.SUPPORTS_CLONE:
                def clone_fallback_to_fetch(*args, **kwargs):
                    try:
                        backend.clone(context, disk_images['image_id'])
                    except exception.ImageUnacceptable:
                        libvirt_utils.fetch_image(*args, **kwargs)
                fetch_func = clone_fallback_to_fetch
            else:
                fetch_func = libvirt_utils.fetch_image
            backend.cache(fetch_func=fetch_func,
                          context=context,
                          filename=root_fname,
                          size=size,
                          image_id=disk_images['image_id'],
                          user_id=instance['user_id'],
                          project_id=instance['project_id'])

        # Lookup the filesystem type if required
        os_type_with_default = disk.get_fs_type_for_os_type(
                                                          instance['os_type'])

        ephemeral_gb = instance['ephemeral_gb']
        if 'disk.local' in disk_mapping:
            disk_image = image('disk.local')
            fn = functools.partial(self._create_ephemeral,
                                   fs_label='ephemeral0',
                                   os_type=instance["os_type"],
                                   is_block_dev=disk_image.is_block_dev)
            fname = "ephemeral_%s_%s" % (ephemeral_gb, os_type_with_default)
            size = ephemeral_gb * units.Gi
            disk_image.cache(fetch_func=fn,
                             context=context,
                             filename=fname,
                             size=size,
                             ephemeral_size=ephemeral_gb)

        for idx, eph in enumerate(driver.block_device_info_get_ephemerals(
                block_device_info)):
            disk_image = image(blockinfo.get_eph_disk(idx))

            specified_fs = eph.get('guest_format')
            if specified_fs and not self.is_supported_fs_format(specified_fs):
                msg = _("%s format is not supported") % specified_fs
                raise exception.InvalidBDMFormat(details=msg)

            # 这个地方会对disk进行mkfs,但是具体没有搞明白
            fn = functools.partial(self._create_ephemeral,
                                   fs_label='ephemeral%d' % idx,
                                   os_type=instance["os_type"],
                                   is_block_dev=disk_image.is_block_dev)
            size = eph['size'] * units.Gi
            fname = "ephemeral_%s_%s" % (eph['size'], os_type_with_default)
            disk_image.cache(fetch_func=fn,
                             context=context,
                             filename=fname,
                             size=size,
                             ephemeral_size=eph['size'],
                             specified_fs=specified_fs)

        if 'disk.swap' in disk_mapping:
            mapping = disk_mapping['disk.swap']
            swap_mb = 0

            swap = driver.block_device_info_get_swap(block_device_info)
            if driver.swap_is_usable(swap):
                swap_mb = swap['swap_size']
            elif (inst_type['swap'] > 0 and
                  not block_device.volume_in_mapping(
                    mapping['dev'], block_device_info)):
                swap_mb = inst_type['swap']

            if swap_mb > 0:
                size = swap_mb * units.Mi
                image('disk.swap').cache(fetch_func=self._create_swap,
                                         context=context,
                                         filename="swap_%s" % swap_mb,
                                         size=size,
                                         swap_mb=swap_mb)

        # Config drive
        if configdrive.required_by(instance):
            LOG.info(_LI('Using config drive'), instance=instance)
            extra_md = {}
            if admin_pass:
                extra_md['admin_pass'] = admin_pass

            inst_md = instance_metadata.InstanceMetadata(instance,
                content=files, extra_md=extra_md, network_info=network_info)
            with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb:
                configdrive_path = self._get_disk_config_path(instance, suffix)
                LOG.info(_LI('Creating config drive at %(path)s'),
                         {'path': configdrive_path}, instance=instance)

                try:
                    cdb.make_drive(configdrive_path)
                except processutils.ProcessExecutionError as e:
                    with excutils.save_and_reraise_exception():
                        LOG.error(_LE('Creating config drive failed '
                                      'with error: %s'),
                                  e, instance=instance)

        # File injection only if needed
        elif inject_files and CONF.libvirt.inject_partition != -2:
            if booted_from_volume:
                LOG.warn(_LW('File injection into a boot from volume '
                             'instance is not supported'), instance=instance)
            self._inject_data(
                instance, network_info, admin_pass, files, suffix)

        if CONF.libvirt.virt_type == 'uml':
            libvirt_utils.chown(image('disk').path, 'root')

上面的创建create_image,如果基于卷的,不会关注镜像信息,只关注虚拟机与磁盘如何挂载,挂载了多少磁盘,驱动等信息

  • instance libvirt.xml文件创建代码分析
   def _get_guest_xml(self, context, instance, network_info, disk_info,
                       image_meta=None, rescue=None,
                       block_device_info=None, write_to_disk=False):

        if image_meta is None:
            image_ref = instance['image_ref']
            image_meta = compute_utils.get_image_metadata(
                                context, self._image_api, image_ref, instance)
        # NOTE(danms): Stringifying a NetworkInfo will take a lock. Do
        # this ahead of time so that we don't acquire it while also
        # holding the logging lock.
        network_info_str = str(network_info)
        msg = ('Start _get_guest_xml '
               'network_info=%(network_info)s '
               'disk_info=%(disk_info)s '
               'image_meta=%(image_meta)s rescue=%(rescue)s '
               'block_device_info=%(block_device_info)s' %
               {'network_info': network_info_str, 'disk_info': disk_info,
                'image_meta': image_meta, 'rescue': rescue,
                'block_device_info': block_device_info})
        # NOTE(mriedem): block_device_info can contain auth_password so we
        # need to sanitize the password in the message.
        LOG.debug(logging.mask_password(msg), instance=instance)

        # 这个地方比较重要,将会分析xml文件内容,在_get_guest_config中会调用LibvirtConfigGuest模板
        conf = self._get_guest_config(instance, network_info, image_meta,
                                      disk_info, rescue, block_device_info,
                                      context)
        xml = conf.to_xml()

        # 这里会将生成的xml变成libvirt.xml文件保存在instance/xxxx目录下
        if write_to_disk:
            instance_dir = libvirt_utils.get_instance_path(instance)
            xml_path = os.path.join(instance_dir, 'libvirt.xml')
            libvirt_utils.write_to_file(xml_path, xml)

        LOG.debug('End _get_guest_xml xml=%(xml)s',
                  {'xml': xml}, instance=instance)
        return xml

下面看下与driver.py同一个文件下,相同的类下面的_get_guest_config方法

   def _get_guest_config(self, instance, network_info, image_meta,
                          disk_info, rescue=None, block_device_info=None,
                          context=None):
        """Get config data for parameters.

        :param rescue: optional dictionary that should contain the key
            'ramdisk_id' if a ramdisk is needed for the rescue image and
            'kernel_id' if a kernel is needed for the rescue image.
        """

        flavor = objects.Flavor.get_by_id(
            nova_context.get_admin_context(read_deleted='yes'),
            instance['instance_type_id'])
        inst_path = libvirt_utils.get_instance_path(instance)
        disk_mapping = disk_info['mapping']
        img_meta_prop = image_meta.get('properties', {}) if image_meta else {}

        CONSOLE = "console=tty0 console=ttyS0"

        guest = vconfig.LibvirtConfigGuest()
        guest.virt_type = CONF.libvirt.virt_type
        guest.name = instance['name']
        guest.uuid = instance['uuid']
        # We are using default unit for memory: KiB
        guest.memory = flavor.memory_mb * units.Ki
        guest.vcpus = flavor.vcpus
        allowed_cpus = hardware.get_vcpu_pin_set()

        cpuset, cputune, guest_cpu_numa = self._get_guest_numa_config(
                context, instance, flavor, allowed_cpus)
        guest.cpuset = cpuset
        guest.cputune = cputune

        guest.metadata.append(self._get_guest_config_meta(context,
                                                          instance,
                                                          flavor))
        guest.idmaps = self._get_guest_idmaps()

        cputuning = ['shares', 'period', 'quota']
        for name in cputuning:
            key = "quota:cpu_" + name
            if key in flavor.extra_specs:
                if guest.cputune is None:
                    guest.cputune = vconfig.LibvirtConfigGuestCPUTune()
                setattr(guest.cputune, name,
                        int(flavor.extra_specs[key]))

        guest.cpu = self._get_guest_cpu_config(
                flavor, image_meta, guest_cpu_numa)

上面列出一小部分,可以看到都是在通过解析,获取xml信息

  • 创建域与网络代码分析
  def _create_domain_and_network(self, context, xml, instance, network_info,
                                   block_device_info=None, power_on=True,
                                   reboot=False, vifs_already_plugged=False,
                                   disk_info=None):

        """Do required network setup and create domain."""
        block_device_mapping = driver.block_device_info_get_mapping(
            block_device_info)

        for vol in block_device_mapping:
            connection_info = vol['connection_info']
            info = blockinfo.get_info_from_bdm(
                   CONF.libvirt.virt_type, vol)
            conf = self._connect_volume(connection_info, info)

            # cache device_path in connection_info -- required by encryptors
            if 'data' in connection_info:
                connection_info['data']['device_path'] = conf.source_path
                vol['connection_info'] = connection_info
                vol.save(context)

            if (not reboot and 'data' in connection_info and
                    'volume_id' in connection_info['data']):
                volume_id = connection_info['data']['volume_id']
                encryption = encryptors.get_encryption_metadata(
                    context, self._volume_api, volume_id, connection_info)

                if encryption:
                    encryptor = self._get_volume_encryptor(connection_info,
                                                           encryption)
                    encryptor.attach_volume(context, **encryption)

        timeout = CONF.vif_plugging_timeout
        if (self._conn_supports_start_paused and
            utils.is_neutron() and not
            vifs_already_plugged and power_on and timeout):
            events = self._get_neutron_events(network_info)
        else:
            events = []

        launch_flags = events and libvirt.VIR_DOMAIN_START_PAUSED or 0
        domain = None
        try:
            with self.virtapi.wait_for_instance_event(
                    instance, events, deadline=timeout,
                    error_callback=self._neutron_failed_callback):
                self.plug_vifs(instance, network_info)
                self.firewall_driver.setup_basic_filtering(instance,
                                                           network_info)
                self.firewall_driver.prepare_instance_filter(instance,
                                                             network_info)
                with self._lxc_disk_handler(instance, block_device_info,
                                            disk_info):
           
                    # 这里调用self._create_domain创建instance
                    domain = self._create_domain(
                        xml, instance=instance,
                        launch_flags=launch_flags,
                        power_on=power_on)

                self.firewall_driver.apply_instance_filter(instance,
                                                           network_info)
        except exception.VirtualInterfaceCreateException:
            # Neutron reported failure and we didn't swallow it, so
            # bail here
            with excutils.save_and_reraise_exception():
                if domain:
                    domain.destroy()
                self.cleanup(context, instance, network_info=network_info,
                             block_device_info=block_device_info)
        except eventlet.timeout.Timeout:
            # We never heard from Neutron
            LOG.warn(_LW('Timeout waiting for vif plugging callback for '
                         'instance %(uuid)s'), {'uuid': instance['uuid']})
            if CONF.vif_plugging_is_fatal:
                if domain:
                    domain.destroy()
                self.cleanup(context, instance, network_info=network_info,
                             block_device_info=block_device_info)
                raise exception.VirtualInterfaceCreateException()

        # Resume only if domain has been paused
        if launch_flags & libvirt.VIR_DOMAIN_START_PAUSED:
            domain.resume()
        return domain

下面展开同相同类下的_create_domain 方法

def _create_domain(self, xml=None, domain=None,
                       instance=None, launch_flags=0, power_on=True):
        """Create a domain.

        Either domain or xml must be passed in. If both are passed, then
        the domain definition is overwritten from the xml.
        """
        err = None
        try:
            if xml:
                err = _LE('Error defining a domain with XML: %s') % xml
                domain = self._conn.defineXML(xml)

            if power_on:
                err = _LE('Error launching a defined domain with XML: %s') \
                          % strutils.safe_decode(domain.XMLDesc(0),
                                                 errors='ignore')
                domain.createWithFlags(launch_flags)

            if not utils.is_neutron():
                err = _LE('Error enabling hairpin mode with XML: %s') \
                          % strutils.safe_decode(domain.XMLDesc(0),
                                                 errors='ignore')
                self._enable_hairpin(domain.XMLDesc(0))
        except Exception:
            with excutils.save_and_reraise_exception():
                if err:
                    LOG.error(err)

        return domain

到此步骤,Instance的域创建完成,在创建过程中可能会触发各种异常,触发异常后虚拟机创建可能存在创建异常,无法完成创建


下一篇 Nova evacuate

Comments