openstack创建虚拟机源码分析三(Glance下载镜像)

创建虚拟机是个很好的应用实例,因为几乎包括了所有主要组件的应用。我们可以看到虚拟创建流程图如下,很复杂,所以进行拆解,我们今天主要看标红这部分(下载镜像)

 

在理创建虚拟机时glance之前,我们先看看glance本身主要分几块,有个大概了解

glance 主要职责是:

  • 提供api让用户能够查询和获取镜像的元数据和镜像本身
  • 支持多种方式存储镜像,包括普通的文件系统、Swift、Ceph 等;
  • 对实例执行快照创建新的镜像。

查询镜像、上传镜像、下载镜像这些都是glance的日常操作,我们会以获取镜像为例,先简单介绍一下glance组件

由图2我们可以看到:

1.外部来的请求统一都由glance-api处理,继续秉承openstack大家族的一贯风格。主要的职责又分为两部分

       1.1 做一些对请求的解析工作(如分析出版本号) 

       1.2 另外一部分提供实际的服务(如与镜像上传下载的后端存储接口交互),实际服务glance-api的请求转发原则是:

  •  如果是与镜像 metadata(元数据)相关的操作,glance-api 会把请求转发给 glance-registry;
  •  如果是与镜像自身存取相关的操作,glance-api 会把请求转发给该 image 的存储后端。

 

2.glance-registry主要负责存储或者获取镜像的元数据,与MySQL数据库进行交互。例如镜像的大小和类型。glance-registry也可以简单的再细分为两部分,API和具体的Server。

这里的元数据是指镜像相关的一些信息(如id,size, status,location, checksum, min_disk, min_ram, owner等),有了这些数据,一是我们就可以判断这个镜像是不是我们想要的那个,二是这样才能获取到location去后端存储去取这个镜像。

 

3.后端存储(支持很多种,用那种用户自己选),用来真正存放镜像本体的backend。因为glance 自己并不存储镜像。

下面这些是glance 支持的许多种后端存储的一些,比如:

  • A directory on a local file system:这是默认配置,在本地的文件系统里进行保存镜像。
  • GridFS:使用MongoDB存储镜像。
  • Ceph RBD:使用Ceph的RBD接口存储到Ceph集群中。
  • Amazon S3:亚马逊的S3。
  • Sheepdog:专为QEMU/KVM提供的一个分布式存储系统。
  • OpenStack Block Storage (Cinder)
  • OpenStack Object Storage (Swift)
  • HTTP:可以使用英特网上的http服务获取镜像。这种方式只能只读。
  • VMware ESX/ESXi or vCenter。

具体使用哪种 backend,可以在 /etc/glance/glance-api.conf 中配置。

 

glance主要干嘛的讲了,咱们又接着讲创建虚拟机时glance干了点啥。

我们展开glance这部分可以看到,她内部的逻辑如下,从上面的图1我们可以知道,创建虚拟机的时候是compute的nova组件向glance发起请求,因为创建虚拟机需要从glance获取镜像。(图三省略了组件之间通信经过RabbitMQ的过程)

ps:所以这里结合咱们的图,就可以看出来了,咱们虚拟机创建需要的涉及获取镜像元数据和获取镜像本身,而不是直接一步到位从存储直接拿,没有元数据拿不到地址信息没办法取。

大致流程就是咱们这个图3这样的,下面咱们来串一串涉及的几个主要的函数。

在此我插一句题外话,我满网去搜从glance下载镜像的文章材料的时候,出来的全是glance的介绍还有上传镜像创建镜像的,下载镜像的材料基本没有,没啥逻辑线参考,所以下面的内容就属于我参考上传还有注释摸索的,正确性不保证啊,仅供参考。

 

继续,从图3我们可以看到,是计算节点的nova组件向glance发出了获取镜像请求,所以我们将从这里切入去看源码。#在openStack\nova-stable-queens\nova\compute\api.py中我们可以看到创建虚拟机的函数

ps:当然这并不是指在这个函数就完成了创建虚拟机的事情,openstack里面很多地方用到了非纯责任链模式。就是一个传一个,搞得定的自己搞,搞不定的传给下一个

_create_instance这个函数只是将创建虚拟机这个大任务,他所能完成的部分(向glance发出获取虚拟机镜像的请求)完成,不能完成的部分继续交给他的下家

#创建虚拟机,由此函数compute节点向glance发出获取镜像请求

def _create_instance(self, context, instance_type,
               image_href, kernel_id, ramdisk_id,
               min_count, max_count,
               display_name, display_description,
               key_name, key_data, security_groups,
               availability_zone, user_data, metadata, injected_files,
               admin_password, access_ip_v4, access_ip_v6,
               requested_networks, config_drive,
               block_device_mapping, auto_disk_config, filter_properties,
               reservation_id=None, legacy_bdm=True, shutdown_terminate=False,
               check_server_group_quota=False, tags=None,
               supports_multiattach=False):

        #获取镜像源数据(包括image_id、)
        if image_href:
            image_id, boot_meta = self._get_image(context, image_href)
        else:
            image_id = None
            boot_meta = self._get_bdm_image_metadata(
                context, block_device_mapping, legacy_bdm)

        self._check_auto_disk_config(image=boot_meta,
                                     auto_disk_config=auto_disk_config)

        #检查flavor配置与镜像规格是否匹配
        self._checks_for_create_and_rebuild(context, image_id, boot_meta,
                instance_type, metadata, injected_files,
                block_device_mapping.root_bdm())


        return instances, reservation_id

 

_get_image()函数,调用了image_api.get去调用manager.py

    #获取镜像函数
    def _get_image(self, context, image_href):
        if not image_href:
            return None, {}
        #实例化一个compute API对象image_api调用manager
        image = self.image_api.get(context, image_href)
        return image['id'], image

这个image_api不是凭空冒出来的,是实例化了一个compute  API类 image_api

@profiler.trace_cls("compute_api")
class API(base.Base):
    """API for interacting with the compute manager."""

    def __init__(self, image_api=None, network_api=None, volume_api=None,
                 security_group_api=None, **kwargs):
        self.image_api = image_api or image.API()

在openStack\nova-stable-queens\nova\compute\manager.py中,_build_and_run_instance函数会调用objects.ImageMeta.from_dict(image)获取image元数据

def _build_and_run_instance(self, context, instance, image, injected_files,
        admin_password, requested_networks, security_groups,
        block_device_mapping, node, limits, filter_properties,
        request_spec=None):

    # 获取镜像名
    image_name = image.get('name')

    self._check_device_tagging(requested_networks, block_device_mapping)

            #获取镜像元数据
            image_meta = objects.ImageMeta.from_dict(image)

 

从此compute节点的nova组件,正式将消息发给glance。在openStack\glance-stable-queens\glance\api\v1\router.py里面,请求被glance.api.v1.router:API的对象处理。

然后我们看一下router.py文件的API类。它继承了wsgi.Router,可以根据REST请求的URL以及请求的类型(GET、PUT等)将请求映射给不同对象的不同方法进行处理。

class API(wsgi.Router):

    """WSGI router for Glance v1 API requests."""

    def __init__(self, mapper):
        reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())

 

然后wsgi开始请求映射,请求最终都是被images.py、image_data.py、schemas.py、image_access.py以及image_tags.py中的controller类的不同方法处理。 所以上面下载镜像的请求会被glance/api/v1/images.py中的Controller类的index()函数处理

        images_resource = images.create_resource()

        mapper.connect("/",
                       controller=images_resource,
                       action="index")
        mapper.connect("/images",
                       controller=images_resource,
                       action='index',
                       conditions={'method': ['GET']})
        mapper.connect("/images",
                       controller=images_resource,
                       action='create',
                       conditions={'method': ['POST']})
        mapper.connect("/images",
                       controller=reject_method_resource,
                       action='reject',
                       allowed_methods='GET, POST')
        mapper.connect("/images/detail",
                       controller=images_resource,
                       action='detail',
                       conditions={'method': ['GET', 'HEAD']})

 

根据图3我们可以看到,glance-registry要做的事情是向数据库去查询合适的镜像,Controller类的index()函数接到了这个任务,我们可以由注释看到这是在查询镜像列表,通过get_images_list函数,从数据库获取image元数据

    def index(self, req):
        """
        Returns the following information for all public, available images:

            * id -- The opaque image identifier
            * name -- The name of the image
            * disk_format -- The disk image format
            * container_format -- The "container" format of the image
            * checksum -- MD5 checksum of the image data
            * size -- Size of image data in bytes

        :param req: The WSGI/Webob Request object
        :returns: The response body is a mapping of the following form

        ::

            {'images': [
                {'id': <ID>,
                 'name': <NAME>,
                 'disk_format': <DISK_FORMAT>,
                 'container_format': <DISK_FORMAT>,
                 'checksum': <CHECKSUM>,
                 'size': <SIZE>}, {...}]
            }

        """
        self._enforce(req, 'get_images')
        params = self._get_query_params(req)
        try:
            images = registry.get_images_list(req.context, **params)
        except exception.Invalid as e:
            raise HTTPBadRequest(explanation=e.msg, request=req)

        return dict(images=images)

 

这里会返回一堆image的元数据,我们可以根据拿到的这些image检出需要的镜像信息, 然后去后端存储里面下载真正我们需要的镜像,下载镜像的操作又从openStack\glance-stable-queens\glance\api\v1\images.py文件里的def show开始分为4步

所以在 def show() 中我们可以看到

1. 先调用image_meta = self.get_active_image_meta_or_error(req, id)获得image_meta

image_meta = self.get_active_image_meta_or_error(req, id)

 

2.然后确认image_meta.get('size') 不等于0,调用 self._get_from_store 获取 该 image的数据

     def show(self, req, id):
        """
        Returns an iterator that can be used to retrieve an image's
        data along with the image metadata.

        :param req: The WSGI/Webob Request object
        :param id: The opaque image identifier

        :raises HTTPNotFound: if image is not available to user
        """

        self._enforce(req, 'get_image')

        try:
            image_meta = self.get_active_image_meta_or_error(req, id)
        except HTTPNotFound:
            # provision for backward-compatibility breaking issue
            # catch the 404 exception and raise it after enforcing
            # the policy
            with excutils.save_and_reraise_exception():
                self._enforce(req, 'download_image')
        else:
            target = utils.create_mashup_dict(image_meta)
            self._enforce(req, 'download_image', target=target)

        self._enforce_read_protected_props(image_meta, req)

        if image_meta.get('size') == 0:
            image_iterator = iter([])
        else:
            #通过_get_from_store获取image元数据
            image_iterator, size = self._get_from_store(req.context,
                                                        image_meta['location'])
            image_iterator = utils.cooperative_iter(image_iterator)
            image_meta['size'] = size or image_meta['size']
        image_meta = redact_loc(image_meta)
        return {
            'image_iterator': image_iterator,
            'image_meta': image_meta,
        }

 

拿到我们需要的这个image id 我们就可以确定出唯一的一个image,但是这并不能直接从后端存储里面下载镜像,还需要用这个id去查到这个image的地址,从后端存储下载镜像还需要store,如图

 

2.在 _get_from_store()函数中,又调用 glance_store.location.get_location_from_uri, 从 image uri 中获取 location,存入loc

这个glance-store 是images.py开始就导入的,glance-store 向 glance-api 提供文件 backend.py 作为 store 操作的统一入口

"""
/images endpoint for Glance v1 API
"""

import copy

import glance_store as store
import glance_store.location

 

3.调用get_store_from_uri, 获取所用的 store

4.通过store调用 store.get 获取 image data,最后返回镜像。就此完成了镜像下载操作

这里的store实际上是一个image_factory对象,在glance中,一般通过wsgi通过分发dispatch的请求,会生成一个image_factory对象和一个image_repo对象,image_factory对应后端store进行镜像的存储等操作

    def _get_from_store(context, where, dest=None):
        try
            #调用glance_store.location.get_location_from_uri,
            #从 image uri 中获取 location
            loc = glance_store.location.get_location_from_uri(where)

            #调用get_store_from_uri获取所需的image对象
            src_store = store.get_store_from_uri(where)

            if dest is not None:
                src_store.READ_CHUNKSIZE = dest.WRITE_CHUNKSIZE
            #调用src_store.get获取镜像image对象的image_data和image_size数据
            image_data, image_size = src_store.get(loc, context=context)

        except store.RemoteServiceUnavailable as e:
            raise HTTPServiceUnavailable(explanation=e.msg)
        except store.NotFound as e:
            raise HTTPNotFound(explanation=e.msg)
        except (store.StoreGetNotSupported,
                store.StoreRandomGetNotSupported,
                store.UnknownScheme) as e:
            raise HTTPBadRequest(explanation=e.msg)
        image_size = int(image_size) if image_size else None
        #返回镜像image_data以及镜像的size
        return image_data, image_size

 

ps:get_store_from_uri和store.get 这两个函数都无法进一步点进去看了,openstack官网文档里表示glance-store这部分代码已经提取出去了

但是他留下了函数说明:get_store_from_uri()通过给定一个URI,返回存储对象

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值