diff --git a/doc/source/admin/config-rbac.rst b/doc/source/admin/config-rbac.rst index 7dfb94ecb4c..cedfdf815ed 100644 --- a/doc/source/admin/config-rbac.rst +++ b/doc/source/admin/config-rbac.rst @@ -18,6 +18,7 @@ is supported by: * Binding QoS policies permissions to networks or ports (since Mitaka). * Attaching router gateways to networks (since Mitaka). * Binding security groups to ports (since Stein). +* Assigning address scopes to subnet pools (since Ussuri). Sharing an object with specific projects @@ -281,12 +282,87 @@ This process can be repeated any number of times to share a security-group with an arbitrary number of projects. +Sharing an address scope with specific projects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create an address scope to share: + +.. code-block:: console + + $ openstack address scope create my_address_scope + +-------------------+--------------------------------------+ + | Field | Value | + +-------------------+--------------------------------------+ + | id | c19cb654-3489-4160-9c82-8a3015483643 | + | ip_version | 4 | + | location | ... | + | name | my_address_scope | + | project_id | 34304bc4f233470fa4a2448d153b6324 | + | shared | False | + +-------------------+--------------------------------------+ + + +Create the RBAC policy entry using the :command:`openstack network rbac create` +command (in this example, the ID of the project we want to share with is +``32016615de5d43bb88de99e7f2e26a1e``): + +.. code-block:: console + + $ openstack network rbac create --target-project \ + 32016615de5d43bb88de99e7f2e26a1e --action access_as_shared \ + --type address_scope c19cb654-3489-4160-9c82-8a3015483643 + +-------------------+--------------------------------------+ + | Field | Value | + +-------------------+--------------------------------------+ + | action | access_as_shared | + | id | d54b1482-98c4-44aa-9115-ede80387ffe0 | + | location | ... | + | name | None | + | object_id | c19cb654-3489-4160-9c82-8a3015483643 | + | object_type | address_scope | + | project_id | 34304bc4f233470fa4a2448d153b6324 | + | target_project_id | 32016615de5d43bb88de99e7f2e26a1e | + +-------------------+--------------------------------------+ + + +The ``target-project`` parameter specifies the project that requires +access to the address scope. The ``action`` parameter specifies what +the project is allowed to do. The ``type`` parameter says +that the target object is an address scope. The final parameter is the ID of +the address scope we are granting access to. + +Project ``32016615de5d43bb88de99e7f2e26a1e`` will now be able to see +the address scope when running :command:`openstack address scope list` and +:command:`openstack address scope show` and will also be able to assign +it to its subnet pools. No other users (other than admins and the owner) +will be able to see the address scope. + +To remove access for that project, delete the RBAC policy that allows +it using the :command:`openstack network rbac delete` command: + +.. code-block:: console + + $ openstack network rbac delete d54b1482-98c4-44aa-9115-ede80387ffe0 + +If that project has subnet pools with the address scope applied to them, +the server will not delete the RBAC policy until +the address scope is no longer in use: + +.. code-block:: console + + $ openstack network rbac delete d54b1482-98c4-44aa-9115-ede80387ffe0 + RBAC policy on object c19cb654-3489-4160-9c82-8a3015483643 + cannot be removed because other objects depend on it. + +This process can be repeated any number of times to share an address scope +with an arbitrary number of projects. + How the 'shared' flag relates to these entries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As introduced in other guide entries, neutron provides a means of -making an object (``network``, ``qos-policy``, ``security-group``) available -to every project. +making an object (``address-scope``, ``network``, ``qos-policy``, +``security-group``) available to every project. This is accomplished using the ``shared`` flag on the supported object: .. code-block:: console diff --git a/neutron/db/address_scope_db.py b/neutron/db/address_scope_db.py index e502dd37152..e1c1108039a 100644 --- a/neutron/db/address_scope_db.py +++ b/neutron/db/address_scope_db.py @@ -36,11 +36,7 @@ class AddressScopeDbMixin(ext_address_scope.AddressScopePluginBase): @staticmethod def _make_address_scope_dict(address_scope, fields=None): - res = {'id': address_scope['id'], - 'name': address_scope['name'], - 'tenant_id': address_scope['tenant_id'], - 'shared': address_scope['shared'], - 'ip_version': address_scope['ip_version']} + res = address_scope.to_dict() return db_utils.resource_fields(res, fields) def _get_address_scope(self, context, id): diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index cb7d903f694..66760d6ca35 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -59,6 +59,7 @@ from neutron import ipam from neutron.ipam import exceptions as ipam_exc from neutron.ipam import subnet_alloc from neutron import neutron_plugin_base_v2 +from neutron.objects import address_scope as address_scope_obj from neutron.objects import base as base_obj from neutron.objects import network as network_obj from neutron.objects import ports as port_obj @@ -1112,7 +1113,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, Subnetpool can associate with an address scope if - the tenant user is the owner of both the subnetpool and address scope - - the admin is associating the subnetpool with the shared + - the user is associating the subnetpool with a shared address scope - there is no prefix conflict with the existing subnetpools associated with the address scope. @@ -1122,8 +1123,14 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, if not validators.is_attr_set(address_scope_id): return - if not self.is_address_scope_owned_by_tenant(context, - address_scope_id): + address_scope = self._get_address_scope(context, address_scope_id) + is_accessible = ( + address_scope_obj.AddressScope.is_accessible( + context, address_scope + ) + ) + + if not is_accessible: raise exc.IllegalSubnetPoolAssociationToAddressScope( subnetpool_id=subnetpool_id, address_scope_id=address_scope_id) diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index e75b3acafed..b56399ec76c 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -18a7e90ae768 +e4e236b0e1ff diff --git a/neutron/db/migration/alembic_migrations/versions/ussuri/expand/e4e236b0e1ff_add_rbac_support_for_address_scope.py b/neutron/db/migration/alembic_migrations/versions/ussuri/expand/e4e236b0e1ff_add_rbac_support_for_address_scope.py new file mode 100644 index 00000000000..c5362521207 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/ussuri/expand/e4e236b0e1ff_add_rbac_support_for_address_scope.py @@ -0,0 +1,82 @@ +# Copyright 2020 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from alembic import op +from oslo_utils import uuidutils +import sqlalchemy as sa +from sqlalchemy import sql + + +"""add_rbac_support_for_address_scope + +Revision ID: e4e236b0e1ff +Revises: 18a7e90ae768 +Create Date: 2020-03-12 11:24:07.435031 + +""" + +# revision identifiers, used by Alembic. +revision = 'e4e236b0e1ff' +down_revision = '18a7e90ae768' +depends_on = ('7d9d8eeec6ad',) + + +def upgrade(): + address_scope_rbacs = op.create_table( + 'addressscoperbacs', sa.MetaData(), + sa.Column('project_id', sa.String(length=255), nullable=True), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('target_tenant', sa.String(length=255), nullable=False), + sa.Column('action', sa.String(length=255), nullable=False), + sa.Column('object_id', sa.String(length=36), nullable=False), + sa.ForeignKeyConstraint(['object_id'], ['address_scopes.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('target_tenant', 'object_id', 'action', + name='uniq_address_scopes_rbacs0' + 'target_tenant0object_id0action') + ) + + op.alter_column('address_scopes', 'shared', server_default=sql.false()) + + op.bulk_insert(address_scope_rbacs, + get_rbac_policies_for_shared_address_scopes()) + + op.create_index(op.f('ix_addressscoperbacs_project_id'), + 'addressscoperbacs', ['project_id'], unique=False) + + +def get_rbac_policies_for_shared_address_scopes(): + # A simple model of the address_scopes table with only the fields needed + # for the migration. + address_scope = sa.Table( + 'address_scopes', sa.MetaData(), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('project_id', sa.String(length=255)), + sa.Column('shared', sa.Boolean(), nullable=False) + ) + + session = sa.orm.Session(bind=op.get_bind()) + shared_address_scopes = session.query(address_scope).filter( + address_scope.c.shared).all() + values = [] + + for row in shared_address_scopes: + values.append({'id': uuidutils.generate_uuid(), 'object_id': row[0], + 'project_id': row[1], 'target_tenant': '*', + 'action': 'access_as_shared'}) + # this commit appears to be necessary to allow further operations + session.commit() + return values diff --git a/neutron/db/models/address_scope.py b/neutron/db/models/address_scope.py index e3f08e668ef..18c80ce7d74 100644 --- a/neutron/db/models/address_scope.py +++ b/neutron/db/models/address_scope.py @@ -13,6 +13,9 @@ from neutron_lib.db import constants as db_const from neutron_lib.db import model_base import sqlalchemy as sa +from sqlalchemy import sql + +from neutron.db import rbac_db_models class AddressScope(model_base.BASEV2, model_base.HasId, model_base.HasProject): @@ -21,5 +24,18 @@ class AddressScope(model_base.BASEV2, model_base.HasId, model_base.HasProject): __tablename__ = "address_scopes" name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE), nullable=False) - shared = sa.Column(sa.Boolean, nullable=False) + + # TODO(imalinovskiy): drop this field when contract migrations will be + # allowed again + # NOTE(imalinovskiy): this field cannot be removed from model due to + # functional test test_models_sync, trailing underscore is required to + # prevent conflicts with RBAC code + shared_ = sa.Column("shared", sa.Boolean, nullable=False, + server_default=sql.false()) + ip_version = sa.Column(sa.Integer(), nullable=False) + + rbac_entries = sa.orm.relationship(rbac_db_models.AddressScopeRBAC, + backref='address_scopes', + lazy='subquery', + cascade='all, delete, delete-orphan') diff --git a/neutron/db/rbac_db_models.py b/neutron/db/rbac_db_models.py index c3e8a163680..1a26276b42e 100644 --- a/neutron/db/rbac_db_models.py +++ b/neutron/db/rbac_db_models.py @@ -126,3 +126,14 @@ class SecurityGroupRBAC(RBACColumns, model_base.BASEV2): @staticmethod def get_valid_actions(): return (ACCESS_SHARED,) + + +class AddressScopeRBAC(RBACColumns, model_base.BASEV2): + """RBAC table for address_scope.""" + + object_id = _object_id_column('address_scopes.id') + object_type = 'address_scope' + + @staticmethod + def get_valid_actions(): + return (ACCESS_SHARED,) diff --git a/neutron/extensions/rbac_address_scope.py b/neutron/extensions/rbac_address_scope.py new file mode 100644 index 00000000000..80b9a1e7c00 --- /dev/null +++ b/neutron/extensions/rbac_address_scope.py @@ -0,0 +1,22 @@ +# Copyright (c) 2020 Cloudification GmbH. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib.api.definitions import rbac_address_scope +from neutron_lib.api import extensions + + +class Rbac_address_scope(extensions.APIExtensionDescriptor): + """Extension class supporting address scope RBAC.""" + + api_definition = rbac_address_scope diff --git a/neutron/objects/address_scope.py b/neutron/objects/address_scope.py index ff8d0e7ce1a..22cc21a1c09 100644 --- a/neutron/objects/address_scope.py +++ b/neutron/objects/address_scope.py @@ -14,17 +14,50 @@ from neutron_lib.objects import common_types from oslo_versionedobjects import fields as obj_fields +import sqlalchemy as sa from neutron.db.models import address_scope as models from neutron.db import models_v2 +from neutron.db import rbac_db_models from neutron.objects import base +from neutron.objects import rbac +from neutron.objects import rbac_db +from neutron.objects import subnetpool @base.NeutronObjectRegistry.register -class AddressScope(base.NeutronDbObject): +class AddressScopeRBAC(rbac.RBACBaseObject): # Version 1.0: Initial version VERSION = '1.0' + db_model = rbac_db_models.AddressScopeRBAC + + @classmethod + def get_projects(cls, context, object_id=None, action=None, + target_tenant=None): + clauses = [] + + if object_id: + clauses.append(cls.db_model.object_id == object_id) + if action: + clauses.append(cls.db_model.action == action) + if target_tenant: + clauses.append(cls.db_model.target_tenant == target_tenant) + query = context.session.query(cls.db_model.target_tenant) + if clauses: + query = query.filter(sa.and_(*clauses)) + return [data[0] for data in query] + + +@base.NeutronObjectRegistry.register +class AddressScope(rbac_db.NeutronRbacObject): + # Version 1.0: Initial version + # Version 1.1: Add RBAC support + VERSION = '1.1' + + # required by RbacNeutronMetaclass + rbac_db_cls = AddressScopeRBAC + db_model = models.AddressScope fields = { @@ -51,3 +84,10 @@ class AddressScope(base.NeutronDbObject): return cls._load_object(context, scope_model_obj) return None + + @classmethod + def get_bound_tenant_ids(cls, context, obj_id): + snp_objs = subnetpool.SubnetPool.get_objects( + context, address_scope_id=obj_id + ) + return {snp.project_id for snp in snp_objs} diff --git a/neutron/objects/rbac_db.py b/neutron/objects/rbac_db.py index a735caefebc..8c7e4601ff1 100644 --- a/neutron/objects/rbac_db.py +++ b/neutron/objects/rbac_db.py @@ -236,6 +236,37 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin, self._validate_rbac_policy_delete(self.obj_context, obj_id, '*') return self.obj_context.session.delete(shared_prev) + def from_db_object(self, db_obj): + self._load_shared(db_obj) + super(RbacNeutronDbObjectMixin, self).from_db_object(db_obj) + + def obj_load_attr(self, attrname): + if attrname == 'shared': + return self._load_shared() + super(RbacNeutronDbObjectMixin, self).obj_load_attr(attrname) + + def _load_shared(self, db_obj=None): + # Do not override 'shared' attribute on create() or update() + if 'shared' in self.obj_get_changes(): + return + + if db_obj: + # NOTE(korzen) db_obj is passed when object is loaded from DB + rbac_entries = db_obj.get('rbac_entries') or {} + shared = self.is_network_shared(self.obj_context, rbac_entries) + else: + # NOTE(korzen) this case is used when object was + # instantiated and without DB interaction (get_object(s), update, + # create), it should be rare case to load 'shared' by that method + shared = self.get_shared_with_tenant( + self.obj_context.elevated(), + self.rbac_db_cls, + self.id, + self.project_id + ) + setattr(self, 'shared', shared) + self.obj_reset_changes(['shared']) + def _update_post(self, obj_changes): if "shared" in obj_changes: @@ -249,6 +280,7 @@ def _update_hook(self, update_orig): obj_changes = self.obj_get_changes() update_orig(self) _update_post(self, obj_changes) + self._load_shared(db_obj=self.db_obj) def _create_post(self): @@ -260,6 +292,7 @@ def _create_hook(self, orig_create): with self.db_context_writer(self.obj_context): orig_create(self) _create_post(self) + self._load_shared(db_obj=self.db_obj) def _to_dict_hook(self, to_dict_orig): diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 07c328f974d..b0ae2ad8f25 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -43,6 +43,7 @@ from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings_extended as pbe_ext from neutron_lib.api.definitions import provider_net +from neutron_lib.api.definitions import rbac_address_scope from neutron_lib.api.definitions import rbac_security_groups as rbac_sg_apidef from neutron_lib.api.definitions import security_groups_port_filtering from neutron_lib.api.definitions import stateful_security_group @@ -181,6 +182,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, _supported_extension_aliases = [provider_net.ALIAS, external_net.ALIAS, portbindings.ALIAS, "quotas", "security-group", + rbac_address_scope.ALIAS, rbac_sg_apidef.ALIAS, agent_apidef.ALIAS, dhcpagentscheduler.ALIAS, diff --git a/neutron/tests/contrib/hooks/api_all_extensions b/neutron/tests/contrib/hooks/api_all_extensions index bf1f0a3a7be..b303f71ea67 100644 --- a/neutron/tests/contrib/hooks/api_all_extensions +++ b/neutron/tests/contrib/hooks/api_all_extensions @@ -45,6 +45,7 @@ NETWORK_API_EXTENSIONS+=",qos-gateway-ip" NETWORK_API_EXTENSIONS+=",quotas" NETWORK_API_EXTENSIONS+=",quota_details" NETWORK_API_EXTENSIONS+=",rbac-policies" +NETWORK_API_EXTENSIONS+=",rbac-address-scope"" NETWORK_API_EXTENSIONS+=",rbac-security-groups"" NETWORK_API_EXTENSIONS+=",router" NETWORK_API_EXTENSIONS+=",router-admin-state-down-before-update" diff --git a/neutron/tests/unit/extensions/test_address_scope.py b/neutron/tests/unit/extensions/test_address_scope.py index d98a26ecc31..6fb2021b73b 100644 --- a/neutron/tests/unit/extensions/test_address_scope.py +++ b/neutron/tests/unit/extensions/test_address_scope.py @@ -98,9 +98,8 @@ class AddressScopeTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): expected=None, tenant_id=None): update_req = self.new_update_request( 'address-scopes', data, addr_scope_id) - if not admin: - neutron_context = context.Context('', tenant_id or self._tenant_id) - update_req.environ['neutron.context'] = neutron_context + update_req.environ['neutron.context'] = context.Context( + '', tenant_id or self._tenant_id, is_admin=admin) update_res = update_req.get_response(self.ext_api) if expected: diff --git a/neutron/tests/unit/objects/test_address_scope.py b/neutron/tests/unit/objects/test_address_scope.py index f4e0e4bf2c0..77d88f408a8 100644 --- a/neutron/tests/unit/objects/test_address_scope.py +++ b/neutron/tests/unit/objects/test_address_scope.py @@ -12,17 +12,48 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib import constants as lib_constants + from neutron.objects import address_scope -from neutron.tests.unit.objects import test_base as obj_test_base +from neutron.tests.unit.objects import test_base +from neutron.tests.unit.objects import test_rbac from neutron.tests.unit import testlib_api -class AddressScopeIfaceObjectTestCase(obj_test_base.BaseObjectIfaceTestCase): +class AddressScopeIfaceObjectTestCase(test_base.BaseObjectIfaceTestCase): _test_class = address_scope.AddressScope -class AddressScopeDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, +class AddressScopeDbObjectTestCase(test_base.BaseDbObjectTestCase, testlib_api.SqlTestCase): _test_class = address_scope.AddressScope + + +class AddressScopeRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin, + test_base.BaseDbObjectTestCase, + testlib_api.SqlTestCase): + + _test_class = address_scope.AddressScopeRBAC + + def setUp(self): + super(AddressScopeRBACDbObjectTestCase, self).setUp() + for obj in self.db_objs: + as_obj = address_scope.AddressScope( + self.context, + id=obj['object_id'], + name="test_as_%s_%s" % (obj['object_id'], obj['project_id']), + project_id=obj['project_id'], + ip_version=lib_constants.IP_ALLOWED_VERSIONS[0], + ) + as_obj.create() + + def _create_test_address_scope_rbac(self): + self.objs[0].create() + return self.objs[0] + + +class AddressScopeRBACIfaceObjectTestCase(test_rbac.TestRBACObjectMixin, + test_base.BaseObjectIfaceTestCase): + _test_class = address_scope.AddressScopeRBAC diff --git a/neutron/tests/unit/objects/test_base.py b/neutron/tests/unit/objects/test_base.py index ea2ca15bc46..07bc4fd59d2 100644 --- a/neutron/tests/unit/objects/test_base.py +++ b/neutron/tests/unit/objects/test_base.py @@ -746,7 +746,7 @@ class BaseObjectIfaceTestCase(_BaseObjectTestCase, test_base.BaseTestCase): 'is_shared_with_tenant', return_value=False).start() mock.patch.object( rbac_db.RbacNeutronDbObjectMixin, - 'get_shared_with_tenant').start() + 'get_shared_with_tenant', return_value=False).start() def fake_get_object(self, context, model, **kwargs): objs = self.model_map[model] diff --git a/neutron/tests/unit/objects/test_network.py b/neutron/tests/unit/objects/test_network.py index 070a060ace3..2f604255426 100644 --- a/neutron/tests/unit/objects/test_network.py +++ b/neutron/tests/unit/objects/test_network.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import random - import mock from neutron.db import rbac_db_models @@ -24,18 +22,7 @@ from neutron.tests.unit.objects import test_rbac from neutron.tests.unit import testlib_api -class _NetworkRBACBase(object): - - def get_random_object_fields(self, obj_cls=None): - fields = (super(_NetworkRBACBase, self). - get_random_object_fields(obj_cls)) - rnd_actions = self._test_class.db_model.get_valid_actions() - idx = random.randint(0, len(rnd_actions) - 1) - fields['action'] = rnd_actions[idx] - return fields - - -class NetworkRBACDbObjectTestCase(_NetworkRBACBase, +class NetworkRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin, obj_test_base.BaseDbObjectTestCase, testlib_api.SqlTestCase): @@ -64,7 +51,7 @@ class NetworkRBACDbObjectTestCase(_NetworkRBACBase, self.assertNotIn('id', network_rbac_obj['versioned_object.data']) -class NetworkRBACIfaceOjectTestCase(_NetworkRBACBase, +class NetworkRBACIfaceOjectTestCase(test_rbac.TestRBACObjectMixin, obj_test_base.BaseObjectIfaceTestCase): _test_class = network.NetworkRBAC diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index c5c5235f4e4..b4318118ea2 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -26,7 +26,8 @@ from neutron.tests import base as test_base # corresponding version bump in the affected objects. Please keep the list in # alphabetic order. object_data = { - 'AddressScope': '1.0-dd0dfdb67775892d3adc090e28e43bd8', + 'AddressScope': '1.1-dd0dfdb67775892d3adc090e28e43bd8', + 'AddressScopeRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d', 'Agent': '1.1-64b670752d57b3c7602cb136e0338507', 'AllowedAddressPair': '1.0-9f9186b6f952fbf31d257b0458b852c0', 'AutoAllocatedTopology': '1.0-74642e58c53bf3610dc224c59f81b242', diff --git a/neutron/tests/unit/objects/test_rbac.py b/neutron/tests/unit/objects/test_rbac.py index a2a5f568a00..0dd9cb1912d 100644 --- a/neutron/tests/unit/objects/test_rbac.py +++ b/neutron/tests/unit/objects/test_rbac.py @@ -9,9 +9,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import random import mock +from neutron.objects import address_scope from neutron.objects import network from neutron.objects.qos import policy from neutron.objects import rbac @@ -20,10 +22,22 @@ from neutron.tests import base as neutron_test_base from neutron.tests.unit.objects import test_base +class TestRBACObjectMixin(object): + + def get_random_object_fields(self, obj_cls=None): + fields = (super(TestRBACObjectMixin, self). + get_random_object_fields(obj_cls)) + rnd_actions = self._test_class.db_model.get_valid_actions() + idx = random.randint(0, len(rnd_actions) - 1) + fields['action'] = rnd_actions[idx] + return fields + + class RBACBaseObjectTestCase(neutron_test_base.BaseTestCase): def test_get_type_class_map(self): - class_map = {'qos_policy': policy.QosPolicyRBAC, + class_map = {'address_scope': address_scope.AddressScopeRBAC, + 'qos_policy': policy.QosPolicyRBAC, 'network': network.NetworkRBAC, 'security_group': securitygroup.SecurityGroupRBAC} self.assertEqual(class_map, rbac.RBACBaseObject.get_type_class_map()) diff --git a/neutron/tests/unit/objects/test_securitygroup.py b/neutron/tests/unit/objects/test_securitygroup.py index 08b196575e4..86accc9bbce 100644 --- a/neutron/tests/unit/objects/test_securitygroup.py +++ b/neutron/tests/unit/objects/test_securitygroup.py @@ -12,7 +12,6 @@ import collections import itertools -import random from oslo_utils import uuidutils @@ -22,18 +21,7 @@ from neutron.tests.unit.objects import test_rbac from neutron.tests.unit import testlib_api -class _SecurityGroupRBACBase(object): - - def get_random_object_fields(self, obj_cls=None): - fields = (super(_SecurityGroupRBACBase, self). - get_random_object_fields(obj_cls)) - rnd_actions = self._test_class.db_model.get_valid_actions() - idx = random.randint(0, len(rnd_actions) - 1) - fields['action'] = rnd_actions[idx] - return fields - - -class SecurityGroupRBACDbObjectTestCase(_SecurityGroupRBACBase, +class SecurityGroupRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin, test_base.BaseDbObjectTestCase, testlib_api.SqlTestCase): @@ -59,7 +47,7 @@ class SecurityGroupRBACDbObjectTestCase(_SecurityGroupRBACBase, security_group_rbac_dict['versioned_object.data']) -class SecurityGroupRBACIfaceObjectTestCase(_SecurityGroupRBACBase, +class SecurityGroupRBACIfaceObjectTestCase(test_rbac.TestRBACObjectMixin, test_base.BaseObjectIfaceTestCase): _test_class = securitygroup.SecurityGroupRBAC diff --git a/releasenotes/notes/add-address-scope-rbac-a903ff28f6457606.yaml b/releasenotes/notes/add-address-scope-rbac-a903ff28f6457606.yaml new file mode 100644 index 00000000000..db7f4b090f5 --- /dev/null +++ b/releasenotes/notes/add-address-scope-rbac-a903ff28f6457606.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Address scope is now supported via the network RBAC mechanism. + Please refer to the admin guide for further details. \ No newline at end of file