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.
这里先以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功能:
可以借助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 方法:
查看_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实际操作:
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)