本文主要是介绍Neutron中的对象objects,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
对象版本化是滚动升级的关键概念。自从被Nova社区初始实现后,版本化对象model被加入oslo库,以便其它的project可获得其带来的益处。
”Oslo VersionedObjects“ (即 OVO) 是数据库的前端,其中可定义软件与数据库schema之间的中间层。在这一层,每个数据库资源的版本化对象以严格的数据定义和版本号被创建。对于OVO,当你改变数据库schema时,对象的版本也改变,并且提供向后兼容的转换。这可允许不同版本的软件同另外的软件通信(通过RPC)。
OVO通常用于RPC的负载版本化。OVO通过定义严格的结构和保持强类型创建版本化字典消息。由此,你可确定发送的内容,及如何在接收端使用数据。
… Oslo VersionedObjects: https://docs.openstack.org/oslo.versionedobjects/latest/
对象用法 Usage of objects
CRUD 操作
对象支持CRUD操作:create()
,get_object()
和get_objects()(相当于
read),
update(),
delete(),
update_objects(), 和
delete_objects()。OVO的本质是追踪任何改变。在调用
create()或
update()`之后,OVO检测到,并改变存储在数据库中的字段。以DNSNameServer为例,看一下简单的对象用法场景:
# to create an object, you can pass the attributes in constructor:
dns = DNSNameServer(context, address='asd', subnet_id='xxx', order=1)
dns.create()# or you can create a dict and pass it as kwargs:
dns_data = {'address': 'asd', 'subnet_id': 'xxx', 'order': 1}
dns = DNSNameServer(context, **dns_data)
dns.create()# for fetching multiple objects:
dnses = DNSNameServer.get_objects(context)
# will return list of all dns name servers from DB# for fetching objects with substrings in a string field:
from neutron_lib.objects import utils as obj_utils
dnses = DNSNameServer.get_objects(context, address=obj_utils.StringContains('10.0.0'))
# will return list of all dns name servers from DB that has '10.0.0' in their addresses# to update fields:
dns = DNSNameServer.get_object(context, address='asd', subnet_id='xxx')
dns.order = 2
dns.update()# if you don't care about keeping the object, you can execute the update
# without fetch of the object state from the underlying persistent layer
count = DNSNameServer.update_objects(context, {'order': 3}, address='asd', subnet_id='xxx')# to remove object with filter arguments:
filters = {'address': 'asd', 'subnet_id': 'xxx'}
DNSNameServer.delete_objects(context, **filters)
过滤器,排序和分页
NeutronDbObject
类在那些字段可进行分类和过滤上有严格的验证。当调用get_objects()
, count()
, update_objects()
, delete_objects()
和 objects_exist()
方法时,将触发validate_filters()
,用于验证是否为支持的过滤准则(默认情况下,只有非合成字段)。额外的过滤器可使用register_filter_hook_on_model()
方法定义。这会将所请求的字符串添加到对象实现中的有效过滤器名称中。它是可选的。
为了禁用过滤器验证,需要将validate_filters=False`作为参数传递到以上提到的方法中。因为Neutron API的默认行为是在API层面接受所有,在DB层做过滤。这可以被代码树外部的扩展extensions使用。
register_filter_hook_on_model()
方法是NeutronDbObject
层中对DB层的register_model_query_hook()
的补充实现,增加了在构建SQL查询时对额外过滤器的支持。当扩展extensions定义额外的查询hook时,如果没有包含在对象的fields
中,它需要使用对象的register_filter_hook_on_model()
方法注册。
要对结果进行限制或分页,可使用Pager
对象。它接受sorts
((key, direction)
tuples列表), limit
, page_reverse
和 marker
关键字。
… code-block:: Python
# filtering# to get an object based on primary key filter
dns = DNSNameServer.get_object(context, address='asd', subnet_id='xxx')# to get multiple objects
dnses = DNSNameServer.get_objects(context, subnet_id='xxx')filters = {'subnet_id': ['xxx', 'yyy']}
dnses = DNSNameServer.get_objects(context, **filters)# do not validate filters
dnses = DNSNameServer.get_objects(context, validate_filters=False,fake_filter='xxx')# count the dns servers for given subnet
dns_count = DNSNameServer.count(context, subnet_id='xxx')# sorting
# direction True == ASC, False == DESC
direction = False
pager = Pager(sorts=[('order', direction)])
dnses = DNSNameServer.get_objects(context, _pager=pager, subnet_id='xxx')
定义你的对象
为了在Neutron中添加新的对象,你必须:
#. 创建由NeutronDbObject
(即基础对象)派生的对象
#. 添加/重用数据模型model
#. 定义字段
强制使用NeutronDbObject的属性
db_model`定义数据模型。
字段应使用oslo_versionobjects.fields
开放的类型定义。如果有创建新类型字段的特殊需求,你可使用neutron.objects
目录下的common_types.py
。示例如下:
fields = {'id': common_types.UUIDField(),'name': obj_fields.StringField(),'subnetpool_id': common_types.UUIDField(nullable=True),'ip_version': common_types.IPVersionEnumField()
}
VERSION
是强制的,定义了对象的版本号,初始时,VERSION
字段定义为1.0。如果字段或者它们的类型改变了,修改VERSION
。当你修改通过RPC开放的对象的版本时,添加方法:obj_make_compatible(self, primitive, target_version)
.
注:
标准属性自动添加到基础类的OVO字段. 诸如属性:description
, created_at
, updated_at
和 revision_number
.
primary_keys
用于定义唯一标识对象的字段列表。对于数据库支持的对象,通常映射到SQL的主键值。对于不能改变的对象字段,默认情况下fields_no_update
列表包含primary_keys
.
如果存在情况,对象中一个字段的命名需要与其在数据库schema中的不同,你可使用fields_need_translation
。此字典包含对象定义中的字段名称(key)和数据库中自动的名称(value)。这允许为数据库持久数据有不同的对象层表示。
如IP分配池的例子:
fields_need_translation = {'start': 'first_ip', # field_ovo: field_db'end': 'last_ip'
}
以上的字典使用在代码 modify_fields_from_db()
和modify_fields_to_db()
方法中,这两个方法实现在基础类中,将完成软件层到数据库schema命名的翻译,反之亦然。它也可被用于重命名orm.relationship
支持的对象类型(object-type)字段。
大多数对象字段通常是直接的映射到数据库model属性。有时开放没有定义在model表中的属性是有用的,像关系之类。在此情况下,synthetic_fields
也许可用。此对象特性可定义不属于对象数据库model的字段列表,可以自定义方式实现这些字段。其中一些字段映射到models上定义的orm.relationships
,其它一些字段完全与数据库层分开。
当以一个ObjectField-typed字段开放已经存在的orm.relationships
时,你可使用foreign_keys
对象特性,其定义了两种对象类型之间的联系。当使用时,它允许对象框架自动的实例化子对象,并基于定义在父对象models上的orm.relationships
填充相关父对象字段。为了自动填充synthetic_fields
,引入了foreign_keys
特性。NeutronDbObject中的load_synthetic_db_fields()
方法使用foreign_keys
匹配相关对象中的外部键(foreign key)以及外部键引用的本地字段。参见以下简化的示例:
class DNSNameServerSqlModel(model_base.BASEV2):address = sa.Column(sa.String(128), nullable=False, primary_key=True)subnet_id = sa.Column(sa.String(36),sa.ForeignKey('subnets.id', ondelete="CASCADE"),primary_key=True)class SubnetSqlModel(model_base.BASEV2, HasId, HasProject):name = sa.Column(sa.String(attr.NAME_MAX_LEN))allocation_pools = orm.relationship(IPAllocationPoolSqlModel)dns_nameservers = orm.relationship(DNSNameServerSqlModel,backref='subnet',cascade='all, delete, delete-orphan',lazy='subquery')class IPAllocationPoolSqlModel(model_base.BASEV2, HasId):subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id'))@obj_base.VersionedObjectRegistry.register
class DNSNameServerOVO(base.NeutronDbObject):VERSION = '1.0'db_model = DNSNameServerSqlModel# Created based on primary_key=True in model definition.# The object is uniquely identified by the pair of address and# subnet_id fields. Override the default 'id' 1-tuple.primary_keys = ['address', 'subnet_id']# Allow to link DNSNameServerOVO child objects into SubnetOVO parent# object fields via subnet_id child database model attribute.# Used during loading synthetic fields in SubnetOVO get_objects.foreign_keys = {'SubnetOVO': {'subnet_id': 'id'}}fields = {'address': obj_fields.StringField(),'subnet_id': common_types.UUIDField(),}@obj_base.VersionedObjectRegistry.register
class SubnetOVO(base.NeutronDbObject):VERSION = '1.0'db_model = SubnetSqlModelfields = {'id': common_types.UUIDField(), # HasId from model class'project_id': obj_fields.StringField(nullable=True), # HasProject from model class'subnet_name': obj_fields.StringField(nullable=True),'dns_nameservers': obj_fields.ListOfObjectsField('DNSNameServer',nullable=True),'allocation_pools': obj_fields.ListOfObjectsField('IPAllocationPoolOVO',nullable=True)}# Claim dns_nameservers field as not directly mapped into the object# database model table.synthetic_fields = ['allocation_pools', 'dns_nameservers']# Rename in-database subnet_name attribute into name object fieldfields_need_translation = {'name': 'subnet_name'}@obj_base.VersionedObjectRegistry.register
class IPAllocationPoolOVO(base.NeutronDbObject):VERSION = '1.0'db_model = IPAllocationPoolSqlModelfields = {'subnet_id': common_types.UUIDField()}foreign_keys = {'SubnetOVO': {'subnet_id': 'id'}}
foreign_keys
在SubnetOVO
中用于填充使用IPAllocationPoolOVO
类的allocation_pools
合成字段。单个对象类型可能被连接到多个父对象类型,因此foreign_keys
在字典中可能有多个键值。
注:
foreign_keys
声明在相关对象中(related object)
IPAllocationPoolOVO
, 与在SQL model中相同
IPAllocationPoolSqlModel
:sa.ForeignKey('subnets.id')
仅允许单一的外部键(foreign key)(通常为 parent ID), 你不能通过多个model属性进行连接。
非常重要的记得可为空的参数。在SQLAlchemy model中,可为空的参数默认值为True
,而对于OVO字段,可为空的参数默认值为False
。确保你正确的映射数据库列的可为空属性到相应的对象字段。
数据库会话激活
默认情况下,所有的对象使用老的oslo.db
引擎facade。为某个特定对象启用新的facade,设置new_facade
类属性值为True
:
@obj_base.VersionedObjectRegistry.register
class ExampleObject(base.NeutronDbObject):new_facade = True
它将是的所有的OVO动作 - get_object
, update
, count
等 - 使用新的reader.using
或 writer.using
修饰符管理数据库事务。
当你需要在OVO代码范围内打开一个新的子事务时,使用以下的数据库会话修饰符:
@obj_base.VersionedObjectRegistry.register
class ExampleObject(base.NeutronDbObject):@classmethoddef get_object(cls, context, **kwargs):with cls.db_context_reader(context):super(ExampleObject, cls).get_object(context, **kwargs)# fetch more data in the same transactiondef create(self):with self.db_context_writer(self.obj_context):super(ExampleObject, self).create()# apply more changes in the same transaction
db_context_reader
和 db_context_writer
修饰符从动作实现中抽取用于特定对象的引擎facade选择。
另外,你可在活动的reader.using
/ writer.using
上下文管理器(或 session.begin
)下调用所有的OVO动作。在此情况下,OVO将选取合适的方法打开子事务。
合成字段
synthetic_fields
是一个字段列表,并没有直接的相应对象SQL表属性支持。合成字段没有用于实现它们的类型限制。
fields = {'dhcp_agents': obj_fields.ObjectField('NetworkDhcpAgentBinding',nullable=True), # field that contains another single NeutronDbObject of NetworkDhcpAgentBinding type'shared': obj_fields.BooleanField(default=False),'subnets': obj_fields.ListOfObjectsField('Subnet', nullable=True)
}# All three fields do not belong to corresponding SQL table, and will be
# implemented in some object-specific way.
synthetic_fields = ['dhcp_agents', 'shared', 'subnets']
ObjectField
和 ListOfObjectsField
使用对象类的名称作为参数。
实现自定义合成字段
有时你可能想开放对象的一个字段,此字段没有映射到相应的数据库model属性,或者它的orm.relationship
; 或者想以非直接映射到子对象类型的格式开放orm.relationship
数据。在此情况下,这里有你需要做的实现自定义字段的getters和setters。
加载合成字段的自定义方法可有帮助,如果字段没有直接定义在数据库中,OVO类不适合加载此数据和相关的仅包含父对象ID和属性的对象,列如,subnet_id和 它的属性:
is_external`。
为了实现自定义方法加载合成字段,你需要提供OVO类中的加载方法,并重载基础类方法from_db_object()
和 obj_load_attr()
。前者负责在调用get_object()
, get_objects()
, create()
和update()
时,加载字段到对象属性。后者负责加载不在对象中设置的属性。同样,当你需要创建作为参数传入构造函数的相关对象与属性时,create()
和 update()
方法需要被覆写。另外,is_external
属性可开放为boolean类型,而不是作为object-typed字段。当字段改变时,但是不需要保存进数据库,可调用函数obj_reset_changes()
告知OVO库忽略它。让我们看以下示例:
@obj_base.VersionedObjectRegistry.register
class ExternalSubnet(base.NeutronDbObject):VERSION = '1.0'fields = {'subnet_id': common_types.UUIDField(),'is_external': obj_fields.BooleanField()}primary_keys = ['subnet_id']foreign_keys = {'Subnet': {'subnet_id': 'id'}}@obj_base.VersionedObjectRegistry.register
class Subnet(base.NeutronDbObject):VERSION = '1.0'fields = {'external': obj_fields.BooleanField(nullable=True),}synthetic_fields = ['external']# support new custom 'external=' filter for get_objects family of# objects APIdef __init__(self, context=None, **kwargs):super(Subnet, self).__init__(context, **kwargs)self.add_extra_filter_name('external')def create(self):fields = self.get_changes()with db_api.context_manager.writer.using(context):if 'external' in fields:ExternalSubnet(context, subnet_id=self.id,is_external=fields['external']).create()# Call to super() to create the SQL record for the object, and# reload its fields from the database, if needed.super(Subnet, self).create()def update(self):fields = self.get_changes()with db_api.context_manager.writer.using(context):if 'external' in fields:# delete the old ExternalSubnet record, if presentobj_db_api.delete_objects(self.obj_context, ExternalSubnet.db_model,subnet_id=self.id)# create the new intended ExternalSubnet objectExternalSubnet(context, subnet_id=self.id,is_external=fields['external']).create()# calling super().update() will reload the synthetic fields# and also will update any changed non-synthetic fields, if anysuper(Subnet, self).update()# this method is called when user of an object accesses the attribute# and requested attribute is not set.def obj_load_attr(self, attrname):if attrname == 'external':return self._load_external()# it is important to call super if attrname does not match# because the base implementation is handling the nullable casesuper(Subnet, self).obj_load_attr(attrname)def _load_external(self, db_obj=None):# do the loading hereif db_obj:# use DB model to fetch the data that may be side-loadedexternal = db_obj.external.is_external if db_obj.external else Noneelse:# perform extra operation to fetch the data from DBexternal_obj = ExternalSubnet.get_object(context,subnet_id=self.id)external = external_obj.is_external if external_obj else None# it is important to set the attribute and call obj_reset_changessetattr(self, 'external', external)self.obj_reset_changes(['external'])# this is defined in NeutronDbObject and is invoked during get_object(s)# and create/update.def from_db_object(self, obj):super(Subnet, self).from_db_object(obj)self._load_external(obj)
以上示例中,get_object(s)
方法不一定要覆写,因为from_db_object()
负责以自定义方式加载合成字段。
标准属性
标准属性自动添加在metaclass:DeclarativeObject
。如果添加标准属性,它必须添加在neutron/objects/extensions/standardattributes.py
文件中。它将被添加到所有使用standardattributesmodel的相关对象中。以上添加需要十分小心,因为它会触发对象
VERSION`的改变。
对象的RBAC处理
RBAC目前实现在资源:Subnet(*), Network 和 QosPolicy中。Subnet是一个特殊的例子,因为Subnet的访问控制依赖于Network RBAC表项。
对象的RBAC支持定义在neutron/objects/rbac_db.py
文件。它定义了新的基础类NeutronRbacObject
。此新类封装了标准NeutronDbObject
方法,像create()
, update()
和 to_dict()
。它检测shared
属性是否定义在fields
字典中,并添加它到synthetic_fields
。并且,rbac_db_model`要求定义在Network 和 QosPolicy类。
NeutronRbacObject
是通用的处理所有RBAC表项操作的地方,像是获取资源是否共享的信息,创建和更新它们。通过封装NeutronDbObject
方法,它管理当调用create()
和 update()
方法时的’shared’属性。
以下为定义Network OVO的示例:
class Network(standard_attr.HasStandardAttributes, model_base.BASEV2,model_base.HasId, model_base.HasProject):"""Represents a v2 neutron network."""name = sa.Column(sa.String(attr.NAME_MAX_LEN))rbac_entries = orm.relationship(rbac_db_models.NetworkRBAC,backref='network', lazy='joined',cascade='all, delete, delete-orphan')# Note the base class for Network OVO:
@obj_base.VersionedObjectRegistry.register
class Network(rbac_db.NeutronRbacObject):# Version 1.0: Initial versionVERSION = '1.0'# rbac_db_model is required to be added hererbac_db_model = rbac_db_models.NetworkRBACdb_model = models_v2.Networkfields = {'id': common_types.UUIDField(),'project_id': obj_fields.StringField(nullable=True),'name': obj_fields.StringField(nullable=True),# share is required to be added to fields'shared': obj_fields.BooleanField(default=False),}
注:
shared
字段没有添加到synthetic_fields
, 因为NeutronRbacObject
需要添加其自身,否则触发错误ObjectActionError
。
Neutron资源扩展
扩展Neutron资源的一种方法,通过提供extend_(subnet|port|network)_dict()
函数和定义加载方法,添加任意的值到表示数据的字典中。
从DB的角度看,所有的数据都将被加载,包括DB relationships中的所有申明字段。当前的核心资源实现(Port, Subnet, Network等)是,DB结果被make_<resource>_dict()
和extend_<resource>_dict()
解析。当使能扩展时,extend_<resource>_dict()
获取DB结果,并在结果字段中声明新字段。当扩展未使能时,获取数据,但是不添加到结果字典中,因为extend_<resource>_dict()
不会调用。
插件仍可使用对象完成一些工作,但是将在需要时转换它们到字典,或者在需要时扩展字典。
如下示例:
class TestSubnetExtension(model_base.BASEV2):subnet_id = sa.Column(sa.String(36),sa.ForeignKey('subnets.id', ondelete="CASCADE"),primary_key=True)value = sa.Column(sa.String(64))subnet = orm.relationship(models_v2.Subnet,# here is the definition of loading the extension with Subnet model:backref=orm.backref('extension', cascade='delete', uselist=False))@oslo_obj_base.VersionedObjectRegistry.register_if(False)
class TestSubnetExtensionObject(obj_base.NeutronDbObject):# Version 1.0: Initial versionVERSION = '1.0'db_model = TestSubnetExtensionfields = {'subnet_id': common_types.UUIDField(),'value': obj_fields.StringField(nullable=True)}primary_keys = ['subnet_id']foreign_keys = {'Subnet': {'subnet_id': 'id'}}@obj_base.VersionedObjectRegistry.register
class Subnet(base.NeutronDbObject):# Version 1.0: Initial versionVERSION = '1.0'fields = {'id': common_types.UUIDField(),'extension': obj_fields.ObjectField(TestSubnetExtensionObject.__name__,nullable=True),}synthetic_fields = ['extension']# when defining the extend_subnet_dict function:
def extend_subnet_dict(self, session, subnet_ovo, result):value = subnet_ovo.extension.value if subnet_ovo.extension else ''result['subnet_extension'] = value
以上示例是理想情况,核心Neutron资源的所有扩展对象都被采用和使能。
通过在代码树中引入OVO,基础插件代码和注册的扩展函数间的接口没有改变。这些仍接受SQLAlchemy model,而不是对象。这是通过捕获get_***/create/update
上的相应数据库model,并通过<object>.db_obj
开放它实现的。
tenant_id的向后兼容
所有的对象可同时支持tenant_id
和 project_id
过滤器和字段;它自动为具有project_id
字段的所有对象使能。基础NeutronDbObject
类支持在访问对象字段(subnet['tenant_id']
)和方法to_dict()
中开放字典的tenant_id
。每个在fields
中具有project_id
的对象都有一个只读的tenant_id
属性。它没有开放在obj_to_primitive()
方法,所以意味着tenant_id
不会通过RPC回调发送。当讨论tenant_id
的过滤/分类时,过滤器应转换为开放project_id
字段。这意味着长期来看,API层应翻译它,但是作为临时方案,可在传递过滤器到对象的get_objects()
方法前,在DB层完成,例如:
def convert_filters(result):if 'tenant_id' in result:result['project_id'] = result.pop('tenant_id')return resultdef get_subnets(context, filters):filters = convert_filters(**filters)return subnet_obj.Subnet.get_objects(context, **filters)
convert_filters
方法在neutron_lib.objects.utils
中可用。
这篇关于Neutron中的对象objects的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!