Browse Source

Merge "Allow sharing of subnet pools via RBAC mechanism"

tags/16.0.0.0rc1
Zuul 3 months ago
committed by Gerrit Code Review
parent
commit
b5e96c49bf
20 changed files with 440 additions and 40 deletions
  1. +88
    -1
      doc/source/admin/config-rbac.rst
  2. +3
    -2
      neutron/db/db_base_plugin_common.py
  3. +4
    -4
      neutron/db/db_base_plugin_v2.py
  4. +1
    -1
      neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD
  5. +81
    -0
      neutron/db/migration/alembic_migrations/versions/ussuri/expand/e88badaa9591_add_rbac_support_for_subnetpool.py
  6. +13
    -1
      neutron/db/models_v2.py
  7. +11
    -0
      neutron/db/rbac_db_models.py
  8. +5
    -0
      neutron/extensions/rbac.py
  9. +22
    -0
      neutron/extensions/rbac_subnetpool.py
  10. +0
    -17
      neutron/objects/address_scope.py
  11. +4
    -6
      neutron/objects/rbac.py
  12. +9
    -1
      neutron/objects/rbac_db.py
  13. +62
    -1
      neutron/objects/subnetpool.py
  14. +2
    -0
      neutron/plugins/ml2/plugin.py
  15. +1
    -0
      neutron/tests/contrib/hooks/api_all_extensions
  16. +2
    -2
      neutron/tests/unit/db/test_db_base_plugin_v2.py
  17. +2
    -1
      neutron/tests/unit/objects/test_objects.py
  18. +3
    -1
      neutron/tests/unit/objects/test_rbac.py
  19. +122
    -2
      neutron/tests/unit/objects/test_subnetpool.py
  20. +5
    -0
      releasenotes/notes/add-subnetpool-rbac-2eb2008bd1b27b11.yaml

+ 88
- 1
doc/source/admin/config-rbac.rst View File

@@ -19,6 +19,7 @@ is supported by:
* Attaching router gateways to networks (since Mitaka).
* Binding security groups to ports (since Stein).
* Assigning address scopes to subnet pools (since Ussuri).
* Assigning subnet pools to subnets (since Ussuri).


Sharing an object with specific projects
@@ -357,12 +358,98 @@ the address scope is no longer in use:
This process can be repeated any number of times to share an address scope
with an arbitrary number of projects.

Sharing a subnet pool with specific projects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Create a subnet pool to share:

.. code-block:: console

$ openstack subnet pool create my_subnetpool --pool-prefix 203.0.113.0/24
+-------------------+--------------------------------------+
| Field | Value |
+-------------------+--------------------------------------+
| address_scope_id | None |
| created_at | 2020-03-16T14:23:01Z |
| default_prefixlen | 8 |
| default_quota | None |
| description | |
| id | 11f79287-bc17-46b2-bfd0-2562471eb631 |
| ip_version | 4 |
| is_default | False |
| location | ... |
| max_prefixlen | 32 |
| min_prefixlen | 8 |
| name | my_subnetpool |
| project_id | 290ccedbcf594ecc8e76eff06f964f7e |
| revision_number | 0 |
| shared | False |
| tags | |
| updated_at | 2020-03-16T14:23:01Z |
+-------------------+--------------------------------------+


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 subnetpool 11f79287-bc17-46b2-bfd0-2562471eb631
+-------------------+--------------------------------------+
| Field | Value |
+-------------------+--------------------------------------+
| action | access_as_shared |
| id | d54b1482-98c4-44aa-9115-ede80387ffe0 |
| location | ... |
| name | None |
| object_id | 11f79287-bc17-46b2-bfd0-2562471eb631 |
| object_type | subnetpool |
| project_id | 290ccedbcf594ecc8e76eff06f964f7e |
| target_project_id | 32016615de5d43bb88de99e7f2e26a1e |
+-------------------+--------------------------------------+


The ``target-project`` parameter specifies the project that requires
access to the subnet pool. The ``action`` parameter specifies what
the project is allowed to do. The ``type`` parameter says
that the target object is a subnet pool. The final parameter is the ID of
the subnet pool we are granting access to.

Project ``32016615de5d43bb88de99e7f2e26a1e`` will now be able to see
the subnet pool when running :command:`openstack subnet pool list` and
:command:`openstack subnet pool show` and will also be able to assign
it to its subnets. No other users (other than admins and the owner)
will be able to see the subnet pool.

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 subnets with the subnet pool applied to them,
the server will not delete the RBAC policy until
the subnet pool is no longer in use:

.. code-block:: console

$ openstack network rbac delete d54b1482-98c4-44aa-9115-ede80387ffe0
RBAC policy on object 11f79287-bc17-46b2-bfd0-2562471eb631
cannot be removed because other objects depend on it.

This process can be repeated any number of times to share a subnet pool
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 (``address-scope``, ``network``, ``qos-policy``,
``security-group``) available to every project.
``security-group``, ``subnetpool``) available to every project.
This is accomplished using the ``shared`` flag on the supported object:

.. code-block:: console


+ 3
- 2
neutron/db/db_base_plugin_common.py View File

@@ -207,12 +207,13 @@ class DbBasePluginCommon(object):
'max_prefixlen': max_prefixlen,
'is_default': subnetpool['is_default'],
'shared': subnetpool['shared'],
'prefixes': [prefix.cidr for prefix in subnetpool['prefixes']],
'prefixes': [str(prefix.cidr)
for prefix in subnetpool['prefixes']],
'ip_version': subnetpool['ip_version'],
'default_quota': subnetpool['default_quota'],
'address_scope_id': subnetpool['address_scope_id']}
resource_extend.apply_funcs(
subnetpool_def.COLLECTION_NAME, res, subnetpool)
subnetpool_def.COLLECTION_NAME, res, subnetpool.db_obj)
return db_utils.resource_fields(res, fields)

def _make_port_dict(self, port, fields=None,


+ 4
- 4
neutron/db/db_base_plugin_v2.py View File

@@ -90,7 +90,7 @@ def _check_subnet_not_used(context, subnet_id):

def _update_subnetpool_dict(orig_pool, new_pool):
updated = dict((k, v) for k, v in orig_pool.to_dict().items()
if k not in orig_pool.synthetic_fields)
if k not in orig_pool.synthetic_fields or k == 'shared')

new_pool = new_pool.copy()
new_prefixes = new_pool.pop('prefixes', constants.ATTR_NOT_SPECIFIED)
@@ -1253,7 +1253,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
subnetpool = subnetpool_obj.SubnetPool(context, **pool_args)
subnetpool.create()

return self._make_subnetpool_dict(subnetpool.db_obj)
return self._make_subnetpool_dict(subnetpool)

@db_api.retry_if_session_inactive()
def update_subnetpool(self, context, id, subnetpool):
@@ -1296,7 +1296,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
@db_api.retry_if_session_inactive()
def get_subnetpool(self, context, id, fields=None):
subnetpool = self._get_subnetpool(context, id)
return self._make_subnetpool_dict(subnetpool.db_obj, fields)
return self._make_subnetpool_dict(subnetpool, fields)

@db_api.retry_if_session_inactive()
def get_subnetpools(self, context, filters=None, fields=None,
@@ -1307,7 +1307,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
subnetpools = subnetpool_obj.SubnetPool.get_objects(
context, _pager=pager, validate_filters=False, **filters)
return [
self._make_subnetpool_dict(pool.db_obj, fields)
self._make_subnetpool_dict(pool, fields)
for pool in subnetpools
]



+ 1
- 1
neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD View File

@@ -1 +1 @@
e4e236b0e1ff
e88badaa9591

+ 81
- 0
neutron/db/migration/alembic_migrations/versions/ussuri/expand/e88badaa9591_add_rbac_support_for_subnetpool.py View File

@@ -0,0 +1,81 @@
# 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 subnetpool

Revision ID: e88badaa9591
Revises: e4e236b0e1ff
Create Date: 2020-02-10 12:30:30.060646

"""

# revision identifiers, used by Alembic.
revision = 'e88badaa9591'
down_revision = 'e4e236b0e1ff'
depends_on = ('7d9d8eeec6ad',)


def upgrade():
subnetpool_rbacs = op.create_table(
'subnetpoolrbacs', 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'], ['subnetpools.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('target_tenant', 'object_id', 'action',
name='uniq_subnetpools_rbacs0'
'target_tenant0object_id0action')
)

op.alter_column('subnetpools', 'shared', server_default=sql.false())

op.bulk_insert(
subnetpool_rbacs,
get_rbac_policies_for_shared_subnetpools()
)

op.create_index(op.f('ix_subnetpoolrbacs_project_id'),
'subnetpoolrbacs', ['project_id'], unique=False)


def get_rbac_policies_for_shared_subnetpools():
# A simple model of the subnetpools table with only the fields needed for
# the migration.
subnetpool = sa.Table(
'subnetpools', 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())
values = []
for row in session.query(subnetpool).filter(subnetpool.c.shared).all():
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

+ 13
- 1
neutron/db/models_v2.py View File

@@ -235,7 +235,15 @@ class SubnetPool(standard_attr.HasStandardAttributes, model_base.BASEV2,
default_prefixlen = sa.Column(sa.Integer, nullable=False)
min_prefixlen = sa.Column(sa.Integer, nullable=False)
max_prefixlen = sa.Column(sa.Integer, 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())

is_default = sa.Column(sa.Boolean, nullable=False,
server_default=sql.false())
default_quota = sa.Column(sa.Integer, nullable=True)
@@ -245,6 +253,10 @@ class SubnetPool(standard_attr.HasStandardAttributes, model_base.BASEV2,
backref='subnetpools',
cascade='all, delete, delete-orphan',
lazy='subquery')
rbac_entries = sa.orm.relationship(rbac_db_models.SubnetPoolRBAC,
backref='subnetpools',
lazy='subquery',
cascade='all, delete, delete-orphan')
api_collections = [subnetpool_def.COLLECTION_NAME]
collection_resource_map = {subnetpool_def.COLLECTION_NAME:
subnetpool_def.RESOURCE_NAME}


+ 11
- 0
neutron/db/rbac_db_models.py View File

@@ -137,3 +137,14 @@ class AddressScopeRBAC(RBACColumns, model_base.BASEV2):
@staticmethod
def get_valid_actions():
return (ACCESS_SHARED,)


class SubnetPoolRBAC(RBACColumns, model_base.BASEV2):
"""RBAC table for subnetpool."""

object_id = _object_id_column('subnetpools.id')
object_type = 'subnetpool'

@staticmethod
def get_valid_actions():
return (ACCESS_SHARED,)

+ 5
- 0
neutron/extensions/rbac.py View File

@@ -39,6 +39,11 @@ class DuplicateRbacPolicy(n_exc.Conflict):
message = _("An RBAC policy already exists with those values.")


class RbacPolicyInitError(n_exc.PolicyInitError):
message = _("Failed to create RBAC policy on object %(object_id)s "
"because %(reason)s.")


def convert_valid_object_type(otype):
normalized = otype.strip().lower()
if normalized in rbac_db_models.get_type_model_map():


+ 22
- 0
neutron/extensions/rbac_subnetpool.py View File

@@ -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_subnetpool
from neutron_lib.api import extensions


class Rbac_subnetpool(extensions.APIExtensionDescriptor):
"""Extension class supporting subnetpool RBAC."""

api_definition = rbac_subnetpool

+ 0
- 17
neutron/objects/address_scope.py View File

@@ -14,7 +14,6 @@

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
@@ -32,22 +31,6 @@ class AddressScopeRBAC(rbac.RBACBaseObject):

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):


+ 4
- 6
neutron/objects/rbac.py View File

@@ -20,7 +20,6 @@ from oslo_versionedobjects import fields as obj_fields
from six import add_metaclass
from sqlalchemy import and_

from neutron.db import rbac_db_models as models
from neutron.objects import base


@@ -45,13 +44,12 @@ class RBACBaseObject(base.NeutronDbObject):
target_tenant=None):
clauses = []
if object_id:
clauses.append(models.NetworkRBAC.object_id == object_id)
clauses.append(cls.db_model.object_id == object_id)
if action:
clauses.append(models.NetworkRBAC.action == action)
clauses.append(cls.db_model.action == action)
if target_tenant:
clauses.append(models.NetworkRBAC.target_tenant ==
target_tenant)
query = context.session.query(models.NetworkRBAC.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(and_(*clauses))
return [data[0] for data in query]


+ 9
- 1
neutron/objects/rbac_db.py View File

@@ -156,6 +156,13 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
obj_id=policy['object_id'],
target_tenant=target_tenant)

@classmethod
def validate_rbac_policy_create(cls, resource, event, trigger,
payload=None):
"""Callback to handle RBAC_POLICY, BEFORE_CREATE callback.
"""
pass

@classmethod
def validate_rbac_policy_update(cls, resource, event, trigger,
payload=None):
@@ -201,7 +208,8 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
msg = _("Only admins can manipulate policies on objects "
"they do not own")
raise exceptions.InvalidInput(error_message=msg)
callback_map = {events.BEFORE_UPDATE: cls.validate_rbac_policy_update,
callback_map = {events.BEFORE_CREATE: cls.validate_rbac_policy_create,
events.BEFORE_UPDATE: cls.validate_rbac_policy_update,
events.BEFORE_DELETE: cls.validate_rbac_policy_delete}
if event in callback_map:
return callback_map[event](resource, event, trigger,


+ 62
- 1
neutron/objects/subnetpool.py View File

@@ -14,18 +14,39 @@
# under the License.

import netaddr
from neutron_lib.db import model_query
from neutron_lib.objects import common_types
from oslo_versionedobjects import fields as obj_fields
import sqlalchemy as sa

from neutron._i18n import _
from neutron.db import models_v2 as models
from neutron.db import rbac_db_models
from neutron.extensions import rbac as ext_rbac
from neutron.objects import base
from neutron.objects.db import api as obj_db_api
from neutron.objects import rbac
from neutron.objects import rbac_db
from neutron.objects import subnet


@base.NeutronObjectRegistry.register
class SubnetPool(base.NeutronDbObject):
class SubnetPoolRBAC(rbac.RBACBaseObject):
# Version 1.0: Initial version
VERSION = '1.0'

db_model = rbac_db_models.SubnetPoolRBAC


@base.NeutronObjectRegistry.register
class SubnetPool(rbac_db.NeutronRbacObject):
# Version 1.0: Initial version
# Version 1.1: Add RBAC support
VERSION = '1.1'

# required by RbacNeutronMetaclass
rbac_db_cls = SubnetPoolRBAC

db_model = models.SubnetPool

fields = {
@@ -83,6 +104,46 @@ class SubnetPool(base.NeutronDbObject):
if 'prefixes' in fields:
self._attach_prefixes(fields['prefixes'])

@classmethod
def get_bound_tenant_ids(cls, context, obj_id):
sn_objs = subnet.Subnet.get_objects(context, subnetpool_id=obj_id)
return {snp.project_id for snp in sn_objs}

@classmethod
def validate_rbac_policy_create(cls, resource, event, trigger,
payload=None):
context = payload.context
policy = payload.request_body

db_obj = obj_db_api.get_object(
cls, context.elevated(), id=policy['object_id'])

if not db_obj["address_scope_id"]:
# Nothing to validate
return

rbac_as_model = rbac_db_models.AddressScopeRBAC

# Ensure that target project has access to AS
shared_to_target_project_or_to_all = (
sa.and_(
rbac_as_model.target_tenant.in_(
["*", policy['target_tenant']]
),
rbac_as_model.object_id == db_obj["address_scope_id"]
)
)

matching_policies = model_query.query_with_hooks(
context, rbac_db_models.AddressScopeRBAC
).filter(shared_to_target_project_or_to_all).count()

if matching_policies == 0:
raise ext_rbac.RbacPolicyInitError(
object_id=policy['object_id'],
reason=_("target project doesn't have access to "
"associated address scope."))


@base.NeutronObjectRegistry.register
class SubnetPoolPrefix(base.NeutronDbObject):


+ 2
- 0
neutron/plugins/ml2/plugin.py View File

@@ -45,6 +45,7 @@ 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 rbac_subnetpool
from neutron_lib.api.definitions import security_groups_port_filtering
from neutron_lib.api.definitions import stateful_security_group
from neutron_lib.api.definitions import subnet as subnet_def
@@ -184,6 +185,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"quotas", "security-group",
rbac_address_scope.ALIAS,
rbac_sg_apidef.ALIAS,
rbac_subnetpool.ALIAS,
agent_apidef.ALIAS,
dhcpagentscheduler.ALIAS,
multiprovidernet.ALIAS,


+ 1
- 0
neutron/tests/contrib/hooks/api_all_extensions View File

@@ -47,6 +47,7 @@ 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+=",rbac-subnetpool""
NETWORK_API_EXTENSIONS+=",router"
NETWORK_API_EXTENSIONS+=",router-admin-state-down-before-update"
NETWORK_API_EXTENSIONS+=",router_availability_zone"


+ 2
- 2
neutron/tests/unit/db/test_db_base_plugin_v2.py View File

@@ -6767,7 +6767,7 @@ class DbModelTenantTestCase(DbModelMixin, testlib_api.SqlTestCase):
with db_api.CONTEXT_WRITER.using(ctx):
subnetpool = models_v2.SubnetPool(
ip_version=constants.IP_VERSION_4, default_prefixlen=4,
min_prefixlen=4, max_prefixlen=4, shared=False,
min_prefixlen=4, max_prefixlen=4,
default_quota=4, address_scope_id='f', tenant_id='dbcheck',
is_default=False
)
@@ -6807,7 +6807,7 @@ class DbModelProjectTestCase(DbModelMixin, testlib_api.SqlTestCase):
with db_api.CONTEXT_WRITER.using(ctx):
subnetpool = models_v2.SubnetPool(
ip_version=constants.IP_VERSION_4, default_prefixlen=4,
min_prefixlen=4, max_prefixlen=4, shared=False,
min_prefixlen=4, max_prefixlen=4,
default_quota=4, address_scope_id='f', project_id='dbcheck',
is_default=False
)


+ 2
- 1
neutron/tests/unit/objects/test_objects.py View File

@@ -108,8 +108,9 @@ object_data = {
'StandardAttribute': '1.0-617d4f46524c4ce734a6fc1cc0ac6a0b',
'Subnet': '1.1-5b7e1789a1732259d1e28b4bd87eb1c2',
'SubnetDNSPublishFixedIP': '1.0-db22af6fa20b143986f0cbe06cbfe0ea',
'SubnetPool': '1.0-a0e03895d1a6e7b9d4ab7b0ca13c3867',
'SubnetPool': '1.1-a0e03895d1a6e7b9d4ab7b0ca13c3867',
'SubnetPoolPrefix': '1.0-13c15144135eb869faa4a76dc3ee3b6c',
'SubnetPoolRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
'SubnetServiceType': '1.0-05ae4cdb2a9026a697b143926a1add8c',
'SubPort': '1.0-72c8471068db1f0491b5480fe49b52bb',
'Tag': '1.0-1a0d20379920ffa3cebfd3e016d2f7a0',


+ 3
- 1
neutron/tests/unit/objects/test_rbac.py View File

@@ -18,6 +18,7 @@ from neutron.objects import network
from neutron.objects.qos import policy
from neutron.objects import rbac
from neutron.objects import securitygroup
from neutron.objects import subnetpool
from neutron.tests import base as neutron_test_base
from neutron.tests.unit.objects import test_base

@@ -39,7 +40,8 @@ class RBACBaseObjectTestCase(neutron_test_base.BaseTestCase):
class_map = {'address_scope': address_scope.AddressScopeRBAC,
'qos_policy': policy.QosPolicyRBAC,
'network': network.NetworkRBAC,
'security_group': securitygroup.SecurityGroupRBAC}
'security_group': securitygroup.SecurityGroupRBAC,
'subnetpool': subnetpool.SubnetPoolRBAC}
self.assertEqual(class_map, rbac.RBACBaseObject.get_type_class_map())




+ 122
- 2
neutron/tests/unit/objects/test_subnetpool.py View File

@@ -12,20 +12,29 @@
# 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 mock

from neutron_lib import constants
from neutron_lib.db import model_query
from oslo_utils import uuidutils

from neutron.extensions import rbac as ext_rbac
from neutron.objects.db import api as obj_db_api
from neutron.objects import subnetpool
from neutron.tests.unit.objects import test_base as obj_test_base
from neutron.tests.unit.objects import test_rbac
from neutron.tests.unit import testlib_api


class SubnetPoolTestMixin(object):
def _create_test_subnetpool(self):
def _create_test_subnetpool(self, snp_id=None):

if not snp_id:
snp_id = uuidutils.generate_uuid()

obj = subnetpool.SubnetPool(
self.context,
id=uuidutils.generate_uuid(),
id=snp_id,
ip_version=constants.IP_VERSION_4,
default_prefixlen=24,
min_prefixlen=0,
@@ -70,6 +79,95 @@ class SubnetPoolDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
# values fo this. To be reworked in follow-up patch.
pass

@mock.patch.object(model_query, 'query_with_hooks')
@mock.patch.object(obj_db_api, 'get_object')
def test_rbac_policy_create_no_address_scope(self, mock_get_object,
mock_query_with_hooks):
context = mock.Mock(is_admin=False, tenant_id='db_obj_owner_id')
payload = mock.Mock(
context=context, request_body=dict(object_id="fake_id")
)
mock_get_object.return_value = dict(address_scope_id=None)

subnetpool.SubnetPool.validate_rbac_policy_create(
None, None, None, payload=payload
)

mock_query_with_hooks.assert_not_called()

def _validate_rbac_filter_mock(self, filter_mock, project_id,
address_scope_id):
filter_mock.assert_called_once()
self.assertEqual(
"addressscoperbacs.target_tenant IN ('*', '%(project_id)s') "
"AND addressscoperbacs.object_id = '%(address_scope_id)s'" % {
"project_id": project_id,
"address_scope_id": address_scope_id,
},
filter_mock.call_args[0][0].compile(
compile_kwargs={"literal_binds": True}
).string
)

@mock.patch.object(model_query, 'query_with_hooks')
@mock.patch.object(obj_db_api, 'get_object')
def test_rbac_policy_create_no_matching_policies(self, mock_get_object,
mock_query_with_hooks):
context = mock.Mock(is_admin=False, tenant_id='db_obj_owner_id')
fake_project_id = "fake_target_tenant_id"
payload = mock.Mock(
context=context, request_body=dict(
object_id="fake_id",
target_tenant=fake_project_id
)
)
fake_address_scope_id = "fake_as_id"
mock_get_object.return_value = dict(
address_scope_id=fake_address_scope_id
)
filter_mock = mock.Mock(
return_value=mock.Mock(count=mock.Mock(return_value=0))
)
mock_query_with_hooks.return_value = mock.Mock(filter=filter_mock)

self.assertRaises(
ext_rbac.RbacPolicyInitError,
subnetpool.SubnetPool.validate_rbac_policy_create,
resource=None, event=None, trigger=None,
payload=payload
)

self._validate_rbac_filter_mock(
filter_mock, fake_project_id, fake_address_scope_id
)

@mock.patch.object(model_query, 'query_with_hooks')
@mock.patch.object(obj_db_api, 'get_object')
def test_rbac_policy_create_valid(self, mock_get_object,
mock_query_with_hooks):
context = mock.Mock(is_admin=False, tenant_id='db_obj_owner_id')
fake_project_id = "fake_target_tenant_id"
payload = mock.Mock(
context=context, request_body=dict(
object_id="fake_id",
target_tenant=fake_project_id
)
)
fake_address_scope_id = "fake_as_id"
mock_get_object.return_value = dict(
address_scope_id=fake_address_scope_id
)
filter_mock = mock.Mock(count=1)
mock_query_with_hooks.return_value = mock.Mock(filter=filter_mock)

subnetpool.SubnetPool.validate_rbac_policy_create(
None, None, None, payload=payload
)

self._validate_rbac_filter_mock(
filter_mock, fake_project_id, fake_address_scope_id
)


class SubnetPoolPrefixIfaceObjectTestCase(
obj_test_base.BaseObjectIfaceTestCase):
@@ -88,3 +186,25 @@ class SubnetPoolPrefixDbObjectTestCase(
super(SubnetPoolPrefixDbObjectTestCase, self).setUp()
self.update_obj_fields(
{'subnetpool_id': lambda: self._create_test_subnetpool().id})


class SubnetPoolRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin,
obj_test_base.BaseDbObjectTestCase,
testlib_api.SqlTestCase,
SubnetPoolTestMixin):

_test_class = subnetpool.SubnetPoolRBAC

def setUp(self):
super(SubnetPoolRBACDbObjectTestCase, self).setUp()
for obj in self.db_objs:
self._create_test_subnetpool(obj['object_id'])

def _create_test_subnetpool_rbac(self):
self.objs[0].create()
return self.objs[0]


class SubnetPoolRBACIfaceObjectTestCase(test_rbac.TestRBACObjectMixin,
obj_test_base.BaseObjectIfaceTestCase):
_test_class = subnetpool.SubnetPoolRBAC

+ 5
- 0
releasenotes/notes/add-subnetpool-rbac-2eb2008bd1b27b11.yaml View File

@@ -0,0 +1,5 @@
---
features:
- |
Subnetpool is now supported via the network RBAC mechanism.
Please refer to the admin guide for further details.

Loading…
Cancel
Save