您的位置:首页 > 运维架构

OpenStack建立实例完整过程源码详细分析(6)

2013-06-25 01:00 645 查看
感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!

如果转载,请保留作者信息。

博客地址:http://blog.csdn.net/gaoxingnengjisuan

邮箱地址:dong.liu@siat.ac.cn

继续看/nova/compute/api.py中的creat方法:

def create(self, context, instance_type,  
                   image_href, kernel_id=None, ramdisk_id=None,  
                   min_count=None, max_count=None,  
                   display_name=None, display_description=None,  
                   key_name=None, key_data=None, security_group=None,  
                   availability_zone=None, user_data=None, metadata=None,  
                   injected_files=None, admin_password=None,  
                   block_device_mapping=None, access_ip_v4=None,  
                   access_ip_v6=None, requested_networks=None, config_drive=None,  
                   auto_disk_config=None, scheduler_hints=None):  
            """ 
            准备实例,并且发送实例的信息和要运行实例的请求消息到远程调度器scheduler; 
            实现实例的简历和运行,由调度器完成,这部分代码实际上只是实现请求消息的发送; 
            返回一个元组(实例或者是reservation_id的元组),元组里面的实例可以是“None”或者是实例字典的一个列表,这要取决于是否等待scheduler返回的信息; 
            """  
      
            self._check_create_policies(context, availability_zone,requested_networks, block_device_mapping)  
      
            # 验证所有的输入实例参数;  
            # 发送要运行实例('run_instance')的请求消息到远程调度器;  
            return self._create_instance(  
                                   context, instance_type,  
                                   image_href, kernel_id, ramdisk_id,  
                                   min_count, max_count,  
                                   display_name, display_description,  
                                   key_name, key_data, security_group,  
                                   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,  
                                   scheduler_hints=scheduler_hints)
语句self._check_create_policies(context, availability_zone,requested_networks, block_device_mapping)实现的是验证是否有资格执行creat这个方法,policy是nova中的一个资格验证机制,我会在其他博文中进行总结;

然后就进入了方法_create_instance,/nova/compute/api.py----def _create_instance(......):

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_group,
               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,
               reservation_id=None, scheduler_hints=None):
        """
        验证所有的输入实例参数;
        发送要运行实例('run_instance')的请求消息到远程调度器;
        """

        # generate_uid:随机生成一个uid值赋值给reservation_id;
        if reservation_id is None:
            reservation_id = utils.generate_uid('r')
        
        # _validate_and_provision_instance:验证所有的输入参数;
        # 返回要建立实例的各类信息;
        # 这个方法中做了很多事,稍后会好好总结;
        (instances, request_spec, filter_properties) = \
                self._validate_and_provision_instance(context, instance_type,
                        image_href, kernel_id, ramdisk_id, min_count,
                        max_count, display_name, display_description,
                        key_name, key_data, security_group, availability_zone,
                        user_data, metadata, injected_files, access_ip_v4,
                        access_ip_v6, requested_networks, config_drive,
                        block_device_mapping, auto_disk_config,
                        reservation_id, scheduler_hints)

        # 循环获取instances中每个实例action的一些相关信息(包括启动时间等);
        # _record_action_start获取要启动的实例action的一些相关信息(包括启动时间等);
        for instance in instances:
            self._record_action_start(context, instance,instance_actions.CREATE)

        # run_instance:实现了发送要运行实例('run_instance')的请求消息到远程节点;
        # 远程节点会在队列中提取这条消息,然后调用相应资源,实现运行实例的这个请求;
        # 这个方法只是实现了请求信息的发送;
        self.scheduler_rpcapi.run_instance(context,
                request_spec=request_spec,
                admin_password=admin_password, injected_files=injected_files,
                requested_networks=requested_networks, is_first_time=True,
                filter_properties=filter_properties)

        return (instances, reservation_id)
这个方法中共有四条语句:

reservation_id = utils.generate_uid('r')

(instances, request_spec, filter_properties) = self._validate_and_provision_instance(......)

self._record_action_start(context, instance,instance_actions.CREATE)

self.scheduler_rpcapi.run_instance(......)

我们逐条进行解析。

A.reservation_id = utils.generate_uid('r'):

随机生成一个长度固定的uid值,赋值给reservation_id;

B.(instances,request_spec,filter_properties)=self._validate_and_provision_instance(......):

这条语句非常重要,它主要完成的是对输入各个参数进行检查验证,其中完成了一个非常重要的任务,即资源的配额管理。现在对这条语句进行解析,首先来看_validate_and_provision_instance这个方法:

def _validate_and_provision_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,
                                         access_ip_v4, access_ip_v6,
                                         requested_networks, config_drive,
                                         block_device_mapping,
                                         auto_disk_config, reservation_id,
                                         scheduler_hints):
        """
        验证所有的输入参数;
        返回要建立实例的各类信息;
        """

        if not metadata:
            metadata = {}
        if not security_groups:
            security_groups = ['default']

        if not instance_type:
            instance_type = instance_types.get_default_instance_type()
        if not min_count:
            min_count = 1
        if not max_count:
            max_count = min_count

        block_device_mapping = block_device_mapping or []
        if min_count > 1 or max_count > 1:
            if any(map(lambda bdm: 'volume_id' in bdm, block_device_mapping)):
                msg = _('Cannot attach one or more volumes to multiple' ' instances')
                raise exception.InvalidRequest(msg)
        if instance_type['disabled']:
            raise exception.InstanceTypeNotFound(
                    instance_type_id=instance_type['id'])

        if user_data:
            l = len(user_data)
            if l > MAX_USERDATA_SIZE:
                # NOTE(mikal): user_data is stored in a text column, and
                # the database might silently truncate if its over length.
                raise exception.InstanceUserDataTooLarge(
                    length=l, maxsize=MAX_USERDATA_SIZE)

            try:
                base64.decodestring(user_data)
            except base64.binascii.Error:
                raise exception.InstanceUserDataMalformed()

        # 根据配额资源限制计算所要建立实例的数目,并获取了分配好的资源(块存储)的UUID的列表;
        # _check_num_instances_quota:根据磁盘配额资源限制确定所要建立实例的数目;
        # num_instances:返回值max_count表示建立实例的最大数目;
        # quota_reservations: 返回值reservations表示建立的预定(分配)的资源的UUID的列表;
        num_instances, quota_reservations = self._check_num_instances_quota(context, instance_type, min_count, max_count)

        try:
            instances = []
            instance_uuids = []

            # 对磁盘配额的元数据属性、磁盘配额的注入文件以及安全组和网络进行检测;
            self._check_metadata_properties_quota(context, metadata)
            """
            检测metadata参数;                   
            强制对磁盘配额的元数据属性的限制进行检测;
            进行了简单的磁盘配额限制的检测,并构造了一个符合要求的资源列表;
            """
            self._check_injected_file_quota(context, injected_files)
            """
            检测injected_files参数;
            强制对磁盘配额注入文件的限制进行检测,如果超出任何限制会引发异常;
            具体限制包括注入文件数目、注入文件路径长度和注入文件内容长度三项指标;
            """
            self._check_requested_secgroups(context, security_groups)
            """
            检测security_groups参数;                    
            检测所要求的安全组是否存在,并且属于指定的对象,如果不存在则会引发异常;
            """
            self._check_requested_networks(context, requested_networks)
            """
            检测requested_networks参数;                    
            检测所需求的网络是否属于指定的对象(工程),
            并且为每个网络提供的固定的IP地址是否处在同一个网段中;
            """

            # 检测image_href参数;
            # 如果image_href值为真,则创建image_service并从给定的image_href解析它的ID值,赋给image_id;
            # 并建立一个glance的客户端,具体方法是应用相关参数实例化一个新的glanceclient.Client对象;
            # 获取image_service的状态,以此判断image_service是否创建成功;
            if image_href:
                (image_service, image_id) = glance.get_remote_image_service(context, image_href)
                image = image_service.show(context, image_id)
                if image['status'] != 'active':
                    raise exception.ImageNotActive(image_id=image_id)
            else:
                image = {}

            # 检测instance_type['memory_mb']和instance_type['root_gb']参数;
            if instance_type['memory_mb'] < int(image.get('min_ram') or 0):
                raise exception.InstanceTypeMemoryTooSmall()
            if instance_type['root_gb'] < int(image.get('min_disk') or 0):
                raise exception.InstanceTypeDiskTooSmall()

            # 为实例获取合适的内核和ramdisk的值;
            kernel_id, ramdisk_id = self._handle_kernel_and_ramdisk(context, kernel_id, ramdisk_id, image)
            """
            为实例选择合适的内核和ramdisk,返回获取到的内核和ramdisk的值kernel_id和ramdisk_id;
            内核和ramdisk可以选择以下三种中的一种:
            1.通过建立实例的要求来决定;
            2.从image(镜像)来继承;
            3.通过应用  ‘null_kernel’标志强制不使用;
            注:如果是第三种情况,则还建立了image_service服务。而且建立一个glance的客户端,具体方法是应用相关参数实例化一个新的glanceclient.Client对象。
            """

            # 处理config_drive这个参数;
            config_drive_id = None
            if config_drive and not utils.is_valid_boolstr(config_drive):
                # config_drive is volume id
                config_drive_id = config_drive
                config_drive = None

                # Ensure config_drive image exists
                # 检测config_drive参数,确保config_drive镜像是存在的;
                # 如果config_drive值是正确无误的,则创建cd_image_service并从给定的config_drive_id解析它的ID值,赋给config_drive_id;
                # 并建立一个glance的客户端,具体方法是应用相关参数实例化一个新的glanceclient.Client对象;
                # 获取cd_image_service的状态,以此判断cd_image_service是否创建成功;
                cd_image_service, config_drive_id = glance.get_remote_image_service(context, config_drive_id)
                cd_image_service.show(context, config_drive_id)

            # 处理key_data这个参数;
            if key_data is None and key_name:
                key_pair = self.db.key_pair_get(context, context.user_id,key_name)
                key_data = key_pair['public_key']

            # 从镜像元数据中获取root_device_name值,如果这个值没有被指定,则返回None值;
            root_device_name = block_device.properties_root_device_name(image.get('properties', {}))

            # 从参数availability_zone中分离出availability_zone值和forced_host值;
            availability_zone, forced_host = self._handle_availability_zone(availability_zone)

            # 从instance_type这个参数中保存instance type属性到实例的system_metadata数据中,格式如下所示:
            # [prefix]instance_type_[key]
            # 保存instance type信息到system_metadata当中,按照一定的格式;
            system_metadata = instance_types.save_instance_type_info(dict(), instance_type)

            # 保存所有参数选项的字典;
            base_options = {
                'reservation_id': reservation_id,
                'image_ref': image_href,
                'kernel_id': kernel_id or '',
                'ramdisk_id': ramdisk_id or '',
                'power_state': power_state.NOSTATE,
                'vm_state': vm_states.BUILDING,
                'config_drive_id': config_drive_id or '',
                'config_drive': config_drive or '',
                'user_id': context.user_id,
                'project_id': context.project_id,
                'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),
                'instance_type_id': instance_type['id'],
                'memory_mb': instance_type['memory_mb'],
                'vcpus': instance_type['vcpus'],
                'root_gb': instance_type['root_gb'],
                'ephemeral_gb': instance_type['ephemeral_gb'],
                'display_name': display_name,
                'display_description': display_description or '',
                'user_data': user_data,
                'key_name': key_name,
                'key_data': key_data,
                'locked': False,
                'metadata': metadata,
                'access_ip_v4': access_ip_v4,
                'access_ip_v6': access_ip_v6,
                'availability_zone': availability_zone,
                'root_device_name': root_device_name,
                'progress': 0,
                'system_metadata': system_metadata}

            # 从image镜像中继承相关属性信息;
            # 包括os_type、architecture、vm_mode和auto_disk_config;
            options_from_image = self._inherit_properties_from_image(image, auto_disk_config)

            # 相关属性更新到字典base_options当中;
            base_options.update(options_from_image)

            # num_instances表示要建立实例的最大数目;
            LOG.debug(_("Going to run %s instances...") % num_instances)

            # 从调度提示信息scheduler_hints中获取过滤器信息;
            filter_properties = dict(scheduler_hints=scheduler_hints)
            
            # 获取filter_properties['force_hosts']的值;(注:这里forced_host是怎么来的还需要进一步研究明白;)
            if forced_host:
                check_policy(context, 'create:forced_host', {})
                filter_properties['force_hosts'] = [forced_host]

            # num_instances表示要建立实例的最大数目;
            # xrange(num_instances)表示从0到num_instances-1的序列;
            for i in xrange(num_instances):
                # 拷贝base_options对象;
                options = base_options.copy()              
                # 为每一个新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等);
                instance = self.create_db_entry_for_new_instance(context, instance_type, image, options, security_groups, block_device_mapping, num_instances, i)

                # 实例instance添加到instances中;
                instances.append(instance)
                # instance['uuid']添加到instance_uuids中;
                instance_uuids.append(instance['uuid'])
                
                # 验证一个实例instance中的所有的块设备映射;
                self._validate_bdm(context, instance)
                # send a state update notification for the initial create to
                # show it going from non-existent to BUILDING
                # 为实例的初始化建立发送一个状态更新通知,表示实例状态从不存在到建立;
                # send_update_with_states:在一个实例中,发送compute.instance.update来通知实例的任何的改变信息;
                notifications.send_update_with_states(context, instance, None, vm_states.BUILDING, None, None, service="api")

        # In the case of any exceptions, attempt DB cleanup and rollback the
        # quota reservations.
        # 在引发异常的情况下
        except Exception:
            # 先保存当前的异常Exception,运行一些代码以后,再引发这个异常;
            # 这个挺有意思的;
            with excutils.save_and_reraise_exception():
                try:
                    # 循环取出每个实例的UUID值;
                    # 根据每个实例的UUID值,删除具体的实例;
                    # instance_destroy:销毁当前要建立的实例,如果实例不存在,则引发异常;
                    # 返回删除后的实例对象;
                    for instance_uuid in instance_uuids:
                        self.db.instance_destroy(context, instance_uuid)
                finally:
                    # 配额quota_reservations预约信息的删除;
                    QUOTAS.rollback(context, quota_reservations)

        # Commit the reservations
        # 提交设定好的reservations预约信息;
        QUOTAS.commit(context, quota_reservations)

        # 生成包含请求建立实例的信息的字典;
        request_spec = {
            'image': jsonutils.to_primitive(image), # 转换镜像image对象到primitives格式;
            'instance_properties': base_options, # 实例属性;
            'instance_type': instance_type, # 实例类型;
            'instance_uuids': instance_uuids, # 实例UUID;
            'block_device_mapping': block_device_mapping, # 块设备映射信息;
            'security_group': security_groups, # 安全组信息;
        }

        # 返回要建立实例的各类信息;
        return (instances, request_spec, filter_properties)
这个方法中要完成很多东西,下面来逐条进行解析:

a.metadata、security_groups、instance_type、min_count、max_count、block_device_mapping、user_data等参数的验证和初始化:

if not metadata:
            metadata = {}
        if not security_groups:
            security_groups = ['default']
        if not instance_type:
            instance_type = instance_types.get_default_instance_type()
        if not min_count:
            min_count = 1
        if not max_count:
            max_count = min_count
        block_device_mapping = block_device_mapping or []
        
        if min_count > 1 or max_count > 1:
            if any(map(lambda bdm: 'volume_id' in bdm, block_device_mapping)):
                msg = _('Cannot attach one or more volumes to multiple' ' instances')
                raise exception.InvalidRequest(msg)
        if instance_type['disabled']:
            raise exception.InstanceTypeNotFound(instance_type_id=instance_type['id'])

        if user_data:
            l = len(user_data)
            if l > MAX_USERDATA_SIZE:
                raise exception.InstanceUserDataTooLarge(length=l, maxsize=MAX_USERDATA_SIZE)
            try:
                base64.decodestring(user_data)
            except base64.binascii.Error:
                raise exception.InstanceUserDataMalformed()
b.num_instances, quota_reservations = self._check_num_instances_quota(context, instance_type, min_count, max_count)资源配额管理,根据磁盘配额资源限制确定所要建立实例的数目,并获取了分配好的资源(块存储)的UUID的列表;

进入方法_check_num_instances_quota:

def _check_num_instances_quota(self, context, instance_type, min_count,max_count):
        """
        根据配额资源限制所要建立实例的数目;
        返回值max_count表示建立实例的最大数目;
        返回值reservations表示建立的预定(分配)的资源的UUID的列表;
        """

        # 确定请求分配的内核数和RAM;
        req_cores = max_count * instance_type['vcpus']
        req_ram = max_count * instance_type['memory_mb']

        # reserve:检查配额并且分配存储资源;
        # 如果没有错误,方法返回所建立的预定(分配)的资源的UUID的列表给reservations;
        try:
            reservations = QUOTAS.reserve(context, instances=max_count,cores=req_cores, ram=req_ram)
        except exception.OverQuota as exc:

            # 查找超出配额限制的原因;
            quotas = exc.kwargs['quotas']
            usages = exc.kwargs['usages']
            overs = exc.kwargs['overs']

            headroom = dict((res, quotas[res] -
                             (usages[res]['in_use'] + usages[res]['reserved']))
                            for res in quotas.keys())

            allowed = headroom['instances']
            # Reduce 'allowed' instances in line with the cores & ram headroom
            if instance_type['vcpus']:
                allowed = min(allowed,headroom['cores'] // instance_type['vcpus'])
            if instance_type['memory_mb']:
                allowed = min(allowed,headroom['ram'] // instance_type['memory_mb'])

            # Convert to the appropriate exception message
            if allowed <= 0:
                msg = _("Cannot run any more instances of this type.")
                allowed = 0
            elif min_count <= allowed <= max_count:
                # We're actually OK, but still need reservations
                return self._check_num_instances_quota(context, instance_type,min_count, allowed)
            else:
                msg = (_("Can only run %s more instances of this type.") %
                       allowed)

            resource = overs[0]
            used = quotas[resource] - headroom[resource]
            total_allowed = used + headroom[resource]
            overs = ','.join(overs)

            pid = context.project_id
            LOG.warn(_("%(overs)s quota exceeded for %(pid)s,"
                       " tried to run %(min_count)s instances. %(msg)s"),locals())
            requested = dict(instances=min_count, cores=req_cores, ram=req_ram)
            raise exception.TooManyInstances(overs=overs,req=requested[resource],used=used, allowed=total_allowed,resource=resource)

        return max_count, reservations
b.1 确定请求分配的内核数和RAM:

req_cores = max_count * instance_type['vcpus']
req_ram = max_count * instance_type['memory_mb']
注:instance_type示例:

instance_type = {'memory_mb': 2048L, 'root_gb': 20L, 'deleted_at': None, 'name': u'm1.small', 'deleted': 0L, 'created_at': None, 'ephemeral_gb': 0L, 'updated_at': None, 'disabled': False, 'vcpus':
1L, 'extra_specs': {}, 'swap': 0L, 'rxtx_factor': 1.0, 'is_public': True, 'flavorid': u'2', 'vcpu_weight': None, 'id': 5L}

b.2 语句reservations = QUOTAS.reserve(context, instances=max_count,cores=req_cores, ram=req_ram)分析:

这条语句实现了对资源配额的检测、管理和分配,如果没有错误,则返回所建立的预定(分配)的资源的UUID的列表;这里调用了方法/nova/quota.py----def reserve(self, context, expire=None, project_id=None, **deltas):

def reserve(self, context, expire=None, project_id=None, **deltas):
        """
        检查配额并且分配存储资源;
        如果没有错误,方法返回所建立的预定(分配)的资源的UUID的列表;
        """

        reservations = self._driver.reserve(context, self._resources, deltas,expire=expire,project_id=project_id)

        LOG.debug(_("Created reservations %(reservations)s") % locals())

        return reservations
我们先来看方法_driver的定义:

def _driver(self):
        if self.__driver:
            return self.__driver
        # quota_driver:这个参数定义了配额管理默认的驱动类;
        # 参数的默认值为'nova.quota.DbQuotaDriver';
        if not self._driver_cls:
            self._driver_cls = CONF.quota_driver
        if isinstance(self._driver_cls, basestring):
            self._driver_cls = importutils.import_object(self._driver_cls)
        self.__driver = self._driver_cls
        return self.__driver
可以看到返回的self.__driver值,默认为'nova.quota.DbQuotaDriver';所以上一个方法中调用的就是类nova.quota.DbQuotaDriver下的方法reserve----def reserve(self, context, resources, deltas, expire=None, project_id=None):

def reserve(self, context, resources, deltas, expire=None, project_id=None):
        """
        @@@@检测配额和储备资源;
        """

        # 如果expire没有指定,则采用默认参数的值;
        # reservation_expire:这个参数定义了预约(资源配额)的到期时间长度;
        # 参数的默认值为86400;
        if expire is None:
            expire = CONF.reservation_expire
        if isinstance(expire, (int, long)):
            expire = datetime.timedelta(seconds=expire)
        if isinstance(expire, datetime.timedelta):
            expire = timeutils.utcnow() + expire
        if not isinstance(expire, datetime.datetime):
            raise exception.InvalidReservationExpiration(expire=expire)

        # 如果没有定义project_id,则应用context中的project_id值;
        if project_id is None:
            project_id = context.project_id

        # 获取适用的配额信息;
        quotas = self._get_quotas(context, resources, deltas.keys(), has_sync=True, project_id=project_id)

        return db.quota_reserve(context, resources, quotas, deltas, expire,
                                CONF.until_refresh, CONF.max_age,
                                project_id=project_id)


首先,为参数expire赋值,expire定义了备份(预约)(资源配额)的到期时间长度,这里把当前时间加上预约时间长度,得到的就是到期时间:

if expire is None:
    expire = CONF.reservation_expire
if isinstance(expire, (int, long)):
    expire = datetime.timedelta(seconds=expire)
if isinstance(expire, datetime.timedelta):
    expire = timeutils.utcnow() + expire
if not isinstance(expire, datetime.datetime):
    raise exception.InvalidReservationExpiration(expire=expire)
b.2.1 接下来调用方法_get_quotas,来获取给定对象id值,即project_id确定的对象的配额信息。

quotas = self._get_quotas(context, resources, deltas.keys(), has_sync=True, project_id=project_id)

在分析具体的方法之前,我们来看看传入参数的值都是什么,进行程序的调试运行,得到如下结果:

context = <nova.context.RequestContext object at 0x3f74dd0>

resources = {
'metadata_items':              <nova.quota.AbsoluteResource object at 0x208c790>, 
'injected_file_content_bytes': <nova.quota.AbsoluteResource object at 0x208c810>, 
'injected_files':              <nova.quota.AbsoluteResource object at 0x208c7d0>, 
'injected_file_path_bytes':    <nova.quota.AbsoluteResource object at 0x208c850>, 

'ram':                         <nova.quota.ReservableResource object at 0x208c6d0>, 
'floating_ips':                <nova.quota.ReservableResource object at 0x208c710>,
'instances':                   <nova.quota.ReservableResource object at 0x208c650>, 
'cores':                       <nova.quota.ReservableResource object at 0x208c690>, 
'fixed_ips':                   <nova.quota.ReservableResource object at 0x208c750>, 
'security_groups':             <nova.quota.ReservableResource object at 0x208c890>,

'security_group_rules':        <nova.quota.CountableResource object at 0x208c8d0>, 
'key_pairs':                   <nova.quota.CountableResource object at 0x208c910>, 
}

deltas = {'instances': 1, 'ram': 2048L, 'cores': 1L}

deltas.keys() = ['instances', 'ram', 'cores']

project_id = 0e492e86f22e4d19bd523f1e7ca64566
稍微解释一下各个参数:

1)context:上下文运行环境信息;

2)deltas = {'instances': 1, 'ram': 2048L, 'cores': 1L}

我们向前看一下,这个参数是由方法_check_num_instances_quota中的语句:

reservations = QUOTAS.reserve(context, instances=max_count,cores=req_cores, ram=req_ram)

注:req_cores = max_count * instance_type['vcpus']

req_ram = max_count * instance_type['memory_mb']

其中的三个参数instances=max_count,cores=req_cores, ram=req_ram集合成了目前的参数deltas;

3)resources

参数集合了已经登记注册的资源对象信息;(翻译是这么翻译的,呵呵!)

我们可以看到resources中的12个元素,分别是三个类AbsoluteResource、ReservableResource和CountableResource的对象;

为什么会这样呢,我们看看参数resources的定义,看看它是怎么来的:

resources = [
    ReservableResource('instances', _sync_instances, 'quota_instances'),
    ReservableResource('cores', _sync_instances, 'quota_cores'),
    ReservableResource('ram', _sync_instances, 'quota_ram'),
    ReservableResource('floating_ips', _sync_floating_ips, 'quota_floating_ips'),
    ReservableResource('fixed_ips', _sync_fixed_ips, 'quota_fixed_ips'),
    ReservableResource('security_groups', _sync_security_groups, 'quota_security_groups'),
    
    AbsoluteResource('metadata_items', 'quota_metadata_items'),
    AbsoluteResource('injected_files', 'quota_injected_files'),
    AbsoluteResource('injected_file_content_bytes', 'quota_injected_file_content_bytes'),
    AbsoluteResource('injected_file_path_bytes', 'quota_injected_file_path_bytes'),

    CountableResource('security_group_rules', db.security_group_rule_count_by_group, 'quota_security_group_rules'),
    CountableResource('key_pairs', db.key_pair_count_by_user, 'quota_key_pairs'),
    ]
这里我们先简单看看这三个类:

首先来看类ReservableResource,它从类BaseResource继承:

class ReservableResource(BaseResource):
    """定义了一个保留资源的类。"""

    def __init__(self, name, sync, flag=None):
        """
        初始化这个类;
        """
        super(ReservableResource, self).__init__(name, flag=flag)
        self.sync = sync
可见,初始化方法中又调用了父类BaseResource的初始化方法,所以我们在看看其父类BaseResource:

class BaseResource(object):
    """为配额检测定义一个资源类;"""

    def __init__(self, name, flag=None):
        """
        初始化这个类。
        """
        self.name = name
        self.flag = flag
所以我们能看到,参数resources的定义中的语句ReservableResource('instances', _sync_instances, 'quota_instances')实现了:

i. 初始化ReservableResource类,实例化这个类的对象;

ii. name = 'instances';

flag = 'quota_instances';

sync = _sync_instances;

我们注意一下这个方法_sync_instances,它作为一个参数传递赋值给sync;(当然这里并没有执行这个方法!)

def _sync_instances(context, project_id, session):
    return dict(zip(('instances', 'cores', 'ram'),
                    db.instance_data_get_for_project(
                context, project_id, session=session)))
我们可以看见这个方法实现的是从数据库中查询获取一些实例信息:instances数目、cores数目和ram值;(进一步的方法这里先不说了,好累,哈哈!)其他语句,如ReservableResource('cores', _sync_instances, 'quota_cores')等等实现方法都是类似的;

其他两个类AbsoluteResource和CountableResource实现的功能都是类似的,这里就不多说了。

到这里应该对参数resources有了一些理解,也就是我们可以通过这个参数从数据库中获取实例的相应的资源的信息;

好吧,现在我们进入_get_quotas这个方法:

def _get_quotas(self, context, resources, keys, has_sync, project_id=None):
       
        # 筛选资源;
        if has_sync:
            sync_filt = lambda x: hasattr(x, 'sync')#判断对象x是否包含sync的特性;
        else:
            sync_filt = lambda x: not hasattr(x, 'sync')#判断对象x是否不包含sync的特性;
        desired = set(keys)
        sub_resources = dict((k, v) for k, v in resources.items()
                             if k in desired and sync_filt(v))

        # 确保所有磁盘配额资源都是已知的,否则引发异常,提示某些磁盘配额资源是未知的;
        if len(keys) != len(sub_resources):
            unknown = desired - set(sub_resources.keys())
            raise exception.QuotaResourceUnknown(unknown=sorted(unknown))

        # 获取并返回磁盘配额;
        quotas = self.get_project_quotas(context, sub_resources,project_id,context.quota_class, usages=False)

        return dict((k, v['limit']) for k, v in quotas.items())
我们结合前面得到的获取的传入参数值来看这个方法,传入的参数值:

context = <nova.context.RequestContext object at 0x3f74dd0>
resources = {
'metadata_items':              <nova.quota.AbsoluteResource object at 0x208c790>, 
'injected_file_content_bytes': <nova.quota.AbsoluteResource object at 0x208c810>, 
'injected_files':              <nova.quota.AbsoluteResource object at 0x208c7d0>, 
'injected_file_path_bytes':    <nova.quota.AbsoluteResource object at 0x208c850>, 

'ram':                         <nova.quota.ReservableResource object at 0x208c6d0>, 
'floating_ips':                <nova.quota.ReservableResource object at 0x208c710>,
'instances':                   <nova.quota.ReservableResource object at 0x208c650>, 
'cores':                       <nova.quota.ReservableResource object at 0x208c690>, 
'fixed_ips':                   <nova.quota.ReservableResource object at 0x208c750>, 
'security_groups':             <nova.quota.ReservableResource object at 0x208c890>,

'security_group_rules':        <nova.quota.CountableResource object at 0x208c8d0>, 
'key_pairs':                   <nova.quota.CountableResource object at 0x208c910>, 
}
deltas = {'instances': 1, 'ram': 2048L, 'cores': 1L}
deltas.keys() = ['instances', 'ram', 'cores']
project_id = 0e492e86f22e4d19bd523f1e7ca64566
我们可以知道,语句:

sub_resources = dict((k, v) for k, v in resources.items()
                             if k in desired and sync_filt(v))
实现的就是,从字典resources中,获取'instances'、'ram'和'cores'三部分,赋值给字典sub_resources。

下面来看下一条语句:

quotas = self.get_project_quotas(context, sub_resources,project_id,context.quota_class, usages=False)
我们先来看看传入get_project_quotas这个方法的具体参数的值:

context = <nova.context.RequestContext object at 0x4e06d90>
sub_resources = {'instances': <nova.quota.ReservableResource object at 0x2f1e610>, 'ram': <nova.quota.ReservableResource object at 0x2f1e690>, 'cores': <nova.quota.ReservableResource object at 0x2f1e650>}
project_id = 0e492e86f22e4d19bd523f1e7ca64566
context.quota_class = None
进入方法get_project_quotas:

def get_project_quotas(self, context, resources, project_id,
                           quota_class=None, defaults=True,
                           usages=True):
        """
        根据给定的资源列表,对于给定的对象进行磁盘配额检索;返回磁盘配额;
        """

        quotas = {}

        # 对于一个给定的项目,检索它的所有的磁盘配额;
        # 根据project_id查询数据库中相应项目的数据库信息,获取其中的hard_limit值,也就是获取规定的资源最大限额值;
        project_quotas = db.quota_get_all_by_project(context, project_id)
        
        # 检索一个给定资源的当前的所有相关的使用情况;
        if usages:
            project_usages = db.quota_usage_get_all_by_project(context,project_id)

        # 通过适当的类获取配额信息。如果project ID匹配context中的一个,那么使用context中的quota_class类,否则,使用所提供的quota_class类(如果有的话)。
        # 选取合适的类,为后面做准备;
        if project_id == context.project_id:
            quota_class = context.quota_class
        if quota_class:
            class_quotas = db.quota_class_get_all_by_name(context, quota_class)
        else:
            class_quotas = {}

        for resource in resources.values():
            # Omit default/quota class values
            if not defaults and resource.name not in project_quotas:
                continue

            quotas[resource.name] = dict(
                limit=project_quotas.get(resource.name, class_quotas.get(resource.name, resource.default)),
                )

            # Include usages if desired.  This is optional because one
            # internal consumer of this interface wants to access the
            # usages directly from inside a transaction.
            if usages:
                usage = project_usages.get(resource.name, {})
                quotas[resource.name].update(
                    in_use=usage.get('in_use', 0),
                    reserved=usage.get('reserved', 0),
                    )

        return quotas
这个方法get_project_quotas也是很重要的,它完成了根据给定的资源选项,对于给定的对象(project_id)进行磁盘配额检索;返回磁盘配额信息;

明天再继续解析这个方法;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: