openstack-nova源码分析(三)flavor

nova flavor
Flavor是nova非常重要的模块, flavor规定了主机的内存,cpu等的大小, 并且可以用来限制虚拟机的一系列参数, 就相当于一个生产模具,使用同一个flavor创建出来的虚拟机,在规格上基本保持一致。
openstack api 官网介绍如下:
flavor : Flavors are a way to describe the basic dimensions of a server to be created including how much cpu, ram, and disk space are allocated to a server built with this flavor.

一. 前言(源码分析的一些参考方法)

在源码分析过程中,有时候我们可以参考openstack nova api 的url来寻找对应的映射关系:
api的官网地址如下:

https://developer.openstack.org/api-ref/compute/

nova源码分析一个非常重要的参考文件是setup.cfg文件, 该文件列出了nova中所有功能的处理方法的路径,依据路径找到对应文件对应的处理方法即可

flavor相关的处理方法在setup.cfg如下:

	flavors = nova.api.openstack.compute.flavors:Flavors
	flavors_extraspecs = nova.api.openstack.compute.flavors_extraspecs:FlavorsExtraSpecs
	flavor_access = nova.api.openstack.compute.flavor_access:FlavorAccess
	flavor_rxtx = nova.api.openstack.compute.flavor_rxtx:FlavorRxtx
	flavor_manage = nova.api.openstack.compute.flavor_manage:FlavorManage

command 命令行工具:
例如查看flavor功能相关的命令行

[root@cnsz92vl00945 ~]# nova help | grep flavor
    flavor-access-add           Add flavor access for the given tenant.
    flavor-access-list          Print access information about the given
                                flavor.
    flavor-access-remove        Remove flavor access for the given tenant.
    flavor-create               Create a new flavor.
    flavor-delete               Delete a specific flavor
    flavor-key                  Set or unset extra_spec for a flavor.
    flavor-list                 Print a list of available 'flavors' (sizes of
    flavor-show                 Show details about the given flavor.

二. Flavor源码分析

这里先以flavors = nova.api.openstack.compute.flavors:Flavors 为例,查找Flavors
在源码文件nova/api/openstack/compute/flavors.py 中找到 Flavors

找到Flavors源码定义如下:

class Flavors(extensions.V21APIExtensionBase):
    """Flavors Extension."""
    name = "Flavors"
    alias = ALIAS
    version = 1

    def get_resources(self):
        collection_actions = {'detail': 'GET'}
        member_actions = {'action': 'POST'}

        resources = [
            extensions.ResourceExtension(ALIAS,
                                         FlavorsController(),
                                         member_name='flavor',
                                         collection_actions=collection_actions,
                                         member_actions=member_actions)
            ]
        return resources
        

nova的url与处理方法的映射是通过urlmap中urlmap_factory进行统一处理映射的(这里暂不对urlmap进行分析)

通过基类 extensions.V21APIExtensionBase 可以看出flavor这里的功能映射到了/v2.1版本的url上

extensions.ResourceExtension(ALIAS,
                            FlavorsController(),
                            member_name='flavor',
                            collection_actions=collection_actions,
                            member_actions=member_actions)
                            

Flavors 通过上面的方法将功能映射到FlavorsController类上
并且通过collection_actions = {‘detail’: ‘GET’} 可以看出新增了一个GET请求映射到detail方法上

在api文档Flavor部分也可以找到 对应url: /flavors/detail GET

openstack nova url采用restful api, 因此在源码分析中我们可以参考ÇET , POST , PUT, DELET 方法的含义进行分析,查找对应的处理函数

flavor功能:

  1. 先分析GET 方法 List Flavors 查询flavor列表

可以借助nova flavor-list 发送调用请求,也可以直接通过api进行调用进行调试

开启远程调试debug

    @extensions.expected_errors(400)
    def detail(self, req):
        """Return all flavors in detail."""
        limited_flavors = self._get_flavors(req)
        #debug
        import pydevd
        pydevd.settrace('192.168.137.1', port=56789, stdoutToServer=True, stderrToServer=True)
        req.cache_db_flavors(limited_flavors)
        return self._view_builder.detail(req, limited_flavors)

命令行中输入nova flavor-list

[root@nova ~]# nova flavor-list
+--------------------------------------+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| ID                                   | Name      | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public |
+--------------------------------------+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| 70bf53ec-5570-4cb2-96b4-5e0135bc2dca | 2c-4g-50g | 4         | 50   | 0         |      | 2     | 1.0         | True      |
| f35936d6-8e16-4f92-b8b8-1d31ace8f33b | 1c-2g-50g | 2         | 50   | 0         |      | 1     | 1.0         | True      |
+--------------------------------------+-----------+-----------+------+-----------+------+-------+-------------+-----------+
[root@nova ~]# 

进入断点,因此 命令行flavor-list最终调用的是detail进行处理

detail 方法:

  1. 调用limited_flavors = self._get_flavors(req)查询
  2. 然后cache到wsgi中
  3. _view_builder格式化返回数据

查看_get_flavors 查询函数(不同版本这里可以能会有不同, 但真实调用的基本一致)

    def _get_flavors(self, req):
        """Helper function that returns a list of flavor dicts."""
        filters = {}
        sort_key = req.params.get('sort_key') or 'flavorid'
        sort_dir = req.params.get('sort_dir') or 'asc'
        limit, marker = common.get_limit_and_marker(req)
        
        ....
        
		# 获取查询条件,limit, maker进行返回行数限制及分页操作

        try:
            limited_flavors = objects.FlavorList.get_all(context,
                filters=filters, sort_key=sort_key, sort_dir=sort_dir,
                limit=limit, marker=marker)
        except exception.MarkerNotFound:
            msg = _('marker [%s] not found') % marker
            raise webob.exc.HTTPBadRequest(explanation=msg)

最后调用objects.FlavorList的get_all

在FlavorList 中找到 get_all 定义如下:

    @base.remotable_classmethod
    def get_all(cls, context, inactive=False, filters=None,
                sort_key='flavorid', sort_dir='asc', limit=None, marker=None):
        try:
            api_db_flavors = _flavor_get_all_from_db(context,
                                                     inactive=inactive,
                                                     filters=filters,
                                                     sort_key=sort_key,
                                                     sort_dir=sort_dir,
                                                     limit=limit,
                                                     marker=marker)
            # NOTE(danms): If we were asked for a marker and found it in
            # results from the API DB, we must continue our pagination with
            # just the limit (if any) to the main DB.
            marker = None
					
				....
				
        return base.obj_make_list(context, cls(context), objects.Flavor,
                                  api_db_flavors + db_flavors,
                                  expected_attrs=['extra_specs'])

通过obj_make_list 将查询到的数据重新映射到Flavor对象上

@db_api.api_context_manager.reader
def _flavor_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
                            limit, marker):
    """Returns all flavors.
    """
    filters = filters or {}

    query = Flavor._flavor_get_query_from_db(context)

    ....

    query = sqlalchemyutils.paginate_query(query, api_models.Flavors,
                                           limit,
                                           [sort_key, 'id'],
                                           marker=marker_row,
                                           sort_dir=sort_dir)
    return [_dict_with_extra_specs(i) for i in query.all()]

_flavor_get_query_from_db 定义如下:

    @staticmethod
    @db_api.api_context_manager.reader
    def _flavor_get_query_from_db(context):
        query = context.session.query(api_models.Flavors).\
                options(joinedload('extra_specs'))
        if not context.is_admin:
            the_filter = [api_models.Flavors.is_public == true()]
            the_filter.extend([
                api_models.Flavors.projects.any(project_id=context.project_id)
            ])
            query = query.filter(or_(*the_filter))
        return query

api_models.Flavors是最终的数据库model定义,封装db操作

class Flavors(API_BASE):
    """Represents possible flavors for instances"""
    __tablename__ = 'flavors'
    __table_args__ = (
        schema.UniqueConstraint("flavorid", name="uniq_flavors0flavorid"),
        schema.UniqueConstraint("name", name="uniq_flavors0name"))
        ....
        

可以看出Flavor的操作,大概涉及到3层操作:

FLAVOR 映射到FlavorsController --> object 对象封装操作 --> db model 数据库操作层


2. 创建 create
flavor 的创建API 接口 /flavors
通过 FlavorManage 映射 最终映射到FlavorManageController进行处理

class FlavorManage(extensions.V21APIExtensionBase):
    """Flavor create/delete API support."""

    name = "FlavorManage"
    alias = ALIAS
    version = 1

    def get_controller_extensions(self):
        controller = FlavorManageController()
        extension = extensions.ControllerExtension(self, 'flavors', controller)

FlavorManageController找到create 操作的对应函数为_create

class FlavorManageController(wsgi.Controller):
    """The Flavor Lifecycle API controller for the OpenStack API."""
    _view_builder_class = flavors_view.ViewBuilderV21
    
    ....

    @wsgi.action("create")
    @extensions.expected_errors((400, 409))
    @validation.schema(flavor_manage.create_v20, '2.0', '2.0')
    @validation.schema(flavor_manage.create, '2.1')
    def _create(self, req, body):
        context = req.environ['nova.context']
        context.can(fm_policies.BASE_POLICY_NAME)

        vals = body['flavor']

        name = vals['name']
        flavorid = vals.get('id')
        memory = vals['ram']
        vcpus = vals['vcpus']
        root_gb = vals['disk']
        ephemeral_gb = vals.get('OS-FLV-EXT-DATA:ephemeral', 0)
        swap = vals.get('swap', 0)
        rxtx_factor = vals.get('rxtx_factor', 1.0)
        is_public = vals.get('os-flavor-access:is_public', True)

		#获取data 请求参数数据


        try:
            flavor = flavors.create(name, memory, vcpus, root_gb,
                                    ephemeral_gb=ephemeral_gb,
                                    flavorid=flavorid, swap=swap,
                                    rxtx_factor=rxtx_factor,
                                    is_public=is_public)
            # NOTE(gmann): For backward compatibility, non public flavor
            # access is not being added for created tenant. Ref -bug/1209101
            req.cache_db_flavor(flavor)
        except (exception.FlavorExists,
                exception.FlavorIdExists) as err:
            raise webob.exc.HTTPConflict(explanation=err.format_message())
        except exception.ObjectActionError:
            raise webob.exc.HTTPConflict(explanation=_(
                'Not all flavors have been migrated to the API database'))

        return self._view_builder.show(req, flavor)

调用flavors.create 创建新的flavor
创建完成后,刷新flavor cache

create 定义如下:

def create(name, memory, vcpus, root_gb, ephemeral_gb=0, flavorid=None,
           swap=0, rxtx_factor=1.0, is_public=True):
    """Creates flavors."""
    if not flavorid:
        flavorid = uuid.uuid4()

   ....

    flavor = objects.Flavor(context=context.get_admin_context(), **kwargs)
    flavor.create()
    return flavor

create函授首先进行一系列的参数合法性校验。

flavorid = six.text_type(flavorid) 注: 这里将flavor id 由字符串转换生成一个int 类型的内部id进行存储

调用objects 层 Flavor create 进行处理
create 定义

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')

				....
        db_flavor = self._flavor_create(self._context, updates)
        self._from_db_object(self._context, self, db_flavor,
                             expected_attrs=expected_attrs)
                             

校验id是否存在
updates = self.obj_get_changes() 依据部分数据调整相应参数
调用 _flavor_create创建

@db_api.api_context_manager.writer
def _flavor_create(context, values):
    specs = values.get('extra_specs')
    db_specs = []
    if specs:
        for k, v in specs.items():
            db_spec = api_models.FlavorExtraSpecs()
            db_spec['key'] = k
            db_spec['value'] = v
            db_specs.append(db_spec)

    projects = values.get('projects')
    db_projects = []
    if projects:
        for project in set(projects):
            db_project = api_models.FlavorProjects()
            db_project['project_id'] = project
            db_projects.append(db_project)

    values['extra_specs'] = db_specs
    values['projects'] = db_projects
    db_flavor = api_models.Flavors()
    db_flavor.update(values)

    try:
        db_flavor.save(context.session)
    except db_exc.DBDuplicateEntry as e:
        if 'flavorid' in e.columns:
            raise exception.FlavorIdExists(flavor_id=values['flavorid'])
        raise exception.FlavorExists(name=values['name'])
    except Exception as e:
        raise db_exc.DBError(e)

    return _dict_with_extra_specs(db_flavor)

这里可能会涉及到三个表数据:

1.extra_specs 扩展的特殊属性。调用 api_models.FlavorExtraSpecs()记录在flavor_extra_specs
2.如果为私有flavor 或者 限制flavor到指定的一些project 可用, 调用api_models.FlavorProjects()记录在flavor_projects
3.最后api_models.Flavors() 创建flavor


3. update:
该版本不支持flavor update


4. flavor show 查看flavor详情:

flavor详情的查看处理方法定义在show函数中:

class FlavorsController(wsgi.Controller):
    """Flavor controller for the OpenStack API."""
    
    ....
    
    @extensions.expected_errors(404)
    def show(self, req, id):
        """Return data about the given flavor id."""
        context = req.environ['nova.context']
        try:
            flavor = flavors.get_flavor_by_flavor_id(id, ctxt=context)
            req.cache_db_flavor(flavor)
        except exception.FlavorNotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.format_message())

        return self._view_builder.show(req, flavor)

flavors.get_flavor_by_flavor_id依据flavor id查询flavor,cache_db_flavor然后cache到wsgi request层

在get_flavor_by_flavor_id查询到调用的是object层的 objects.Flavor.get_by_flavor_id

object层调用;_flavor_get_by_flavor_id_from_db 在db中查询flavor

    @require_context
    def _flavor_get_by_flavor_id_from_db(context, flavor_id):
        """Returns a dict describing specific flavor_id."""
        result = Flavor._flavor_get_query_from_db(context).\
                        filter_by(flavorid=flavor_id).\
                        order_by(asc(api_models.Flavors.id)).\
                        first()
        if not result:
            raise exception.FlavorNotFound(flavor_id=flavor_id)
        return db_api._dict_with_extra_specs(result)

filter_by(flavorid=flavor_id) 依据 flavorid字段进行查询

_flavor_get_query_from_db定义

    def _flavor_get_query_from_db(context):
        query = context.session.query(api_models.Flavors).\
                options(joinedload('extra_specs'))
        if not context.is_admin:
            the_filter = [api_models.Flavors.is_public == true()]
            the_filter.extend([
                api_models.Flavors.projects.any(project_id=context.project_id)
            ])
            query = query.filter(or_(*the_filter))
        return query

该方法封装了 三个表操作的join, 返回 extra_specs 以及依据project过滤没有权限访问的用户

flavor delete 删除flavor操作

删除操作对应的处理方法定义在FlavorManageController

class FlavorManageController(wsgi.Controller):
    """The Flavor Lifecycle API controller for the OpenStack API."""
    _view_builder_class = flavors_view.ViewBuilderV21

    def __init__(self):
        super(FlavorManageController, self).__init__()

    # NOTE(oomichi): Return 202 for backwards compatibility but should be
    # 204 as this operation complete the deletion of aggregate resource and
    # return no response body.
    @wsgi.response(202)
    @extensions.expected_errors((404))
    @wsgi.action("delete")
    def _delete(self, req, id):
        context = req.environ['nova.context']
        context.can(fm_policies.BASE_POLICY_NAME)

        flavor = objects.Flavor(context=context, flavorid=id)
        try:
            flavor.destroy()
        except exception.FlavorNotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.format_message())

直接调用 object层的 Flavor destroy进行删除

    def destroy(self):
        # NOTE(danms): Historically the only way to delete a flavor
        # is via name, which is not very precise. We need to be able to
        # support the light construction of a flavor object and subsequent
        # delete request with only our name filled out. However, if we have
        # our id property, we should instead delete with that since it's
        # far more specific.
        try:
            if 'id' in self:
                self._flavor_destroy(self._context, flavor_id=self.id)
            else:
                self._flavor_destroy(self._context, flavorid=self.flavorid)
        except exception.FlavorNotFound:
            db.flavor_destroy(self._context, self.flavorid)

这里实际调用的是self._flavor_destroy(self._context, flavorid=self.flavorid)

_flavor_destroy实际操作:

  1. 校验flavorid是否存在,不存在,直接抛出exception.FlavorNotFound 资源不存在的异常
  2. 删除关联表的数据, project信息, extra_specs信息
  3. 最后删除flavor
    session这里做了重用
    context.session.query(api_models.FlavorProjects).\
        filter_by(flavor_id=result.id).delete()
    context.session.query(api_models.FlavorExtraSpecs).\
        filter_by(flavor_id=result.id).delete()
    context.session.delete(result)

你可能感兴趣的:(openstack源码分析)