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_reversemarker关键字。

… 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_atrevision_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 =  SubnetSqlModel

    fields = {
        '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 field
    fields_need_translation = {
        'name': 'subnet_name'
    }


@obj_base.VersionedObjectRegistry.register
class IPAllocationPoolOVO(base.NeutronDbObject):
    VERSION = '1.0'
    db_model = IPAllocationPoolSqlModel

    fields = {
        'subnet_id': common_types.UUIDField()
    }

    foreign_keys = {'SubnetOVO': {'subnet_id': 'id'}}

foreign_keysSubnetOVO中用于填充使用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.usingwriter.using修饰符管理数据库事务。

当你需要在OVO代码范围内打开一个新的子事务时,使用以下的数据库会话修饰符:

@obj_base.VersionedObjectRegistry.register
class ExampleObject(base.NeutronDbObject):

    @classmethod
    def get_object(cls, context, **kwargs):
        with cls.db_context_reader(context):
            super(ExampleObject,  cls).get_object(context, **kwargs)
            # fetch more data in the same transaction

    def create(self):
        with self.db_context_writer(self.obj_context):
            super(ExampleObject, self).create()
            # apply more changes in the same transaction

db_context_readerdb_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']

ObjectFieldListOfObjectsField 使用对象类的名称作为参数。

实现自定义合成字段

有时你可能想开放对象的一个字段,此字段没有映射到相应的数据库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 API
    def __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 present
                obj_db_api.delete_objects(
                    self.obj_context, ExternalSubnet.db_model,
                    subnet_id=self.id)
                # create the new intended ExternalSubnet object
                ExternalSubnet(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 any
            super(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 case
        super(Subnet, self).obj_load_attr(attrname)

    def _load_external(self, db_obj=None):
        # do the loading here
        if db_obj:
            # use DB model to fetch the data that may be side-loaded
            external = db_obj.external.is_external if db_obj.external else None
        else:
            # perform extra operation to fetch the data from DB
            external_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_changes
        setattr(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 version
    VERSION = '1.0'

    # rbac_db_model is required to be added here
    rbac_db_model = rbac_db_models.NetworkRBAC
    db_model = models_v2.Network

    fields = {
        '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 version
    VERSION = '1.0'

    db_model = TestSubnetExtension

    fields = {
        '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 version
    VERSION = '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_idproject_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 result

def get_subnets(context, filters):
    filters = convert_filters(**filters)
    return subnet_obj.Subnet.get_objects(context, **filters)

convert_filters方法在neutron_lib.objects.utils中可用。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页