Skip to content

Commit 1a66962

Browse files
committed
Change RBAC relationship loading method to "joined"
This patch changes all RBAC relationship method to "joined". This change enforces that the RBAC associated registers are loaded along with the parent resource. The rationale of this change is to be able to control the SQL query executed; the subquery cannot be directly managed by Neutron. It is very usual to create the RBAC rules from one single project that is usually the adminitrator project. That means all RBAC rules will belong to it. Before this change, the SQL subquery performed to retrieve the RBAC entries was this (from a network query): SELECT networks.id AS networks_id FROM networks LEFT OUTER JOIN networkrbacs ON networks.id = networkrbacs.object_id WHERE networks.project_id = 'bd133e2c499c4bf8aeb16206e31c3c20' OR networkrbacs.action = 'access_as_external' AND networkrbacs.target_project = 'bd133e2c499c4bf8aeb16206e31c3c20' OR networkrbacs.target_project = '*' OR networks.project_id = 'bd133e2c499c4bf8aeb16206e31c3c20' OR networkrbacs.action IN ('access_as_shared', 'access_as_readonly') AND (networkrbacs.target_project = 'bd133e2c499c4bf8aeb16206e31c3c20' OR networkrbacs.target_project = '*'); This SQL result has a very low cardinality; that means there are many duplicated registers. For example, with 10 external network, 1000 projects and 2500 RBAC rules, this query returns 1.4 million rows. Instead if a "GROUP BY resource_id" (in this case network_id) clause is added, the number of rows is reduced to 10 (considering this project has a RBAC per network). In order to introduce this "GROUP BY" clause, this patch is changing the loading method. The clause is added in a neutron-lib patch [1]. This change by itself does not improve the query performance. The neutron-lib patch is needed too. Although this patch does not modify que SQL query results, the tests added will prove that the neutron-lib patch does not introduce any regression. [1]https://review.opendev.org/c/openstack/neutron-lib/+/884878 Closes-Bug: #1918145 Change-Id: Ic6001bd5a57493b8befdf81a41eb0bd1c8022df3 (cherry picked from commit e9da29d)
1 parent 0a590e5 commit 1a66962

File tree

11 files changed

+57
-8
lines changed

11 files changed

+57
-8
lines changed

neutron/db/models/address_group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ class AddressGroup(standard_attr.HasStandardAttributes,
4646
cascade='all, delete-orphan')
4747
rbac_entries = sa.orm.relationship(rbac_db_models.AddressGroupRBAC,
4848
backref='address_groups',
49-
lazy='subquery',
49+
lazy='joined',
5050
cascade='all, delete, delete-orphan')
5151
api_collections = [ag.ALIAS]

neutron/db/models/address_scope.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ class AddressScope(model_base.BASEV2, model_base.HasId, model_base.HasProject):
3737

3838
rbac_entries = sa.orm.relationship(rbac_db_models.AddressScopeRBAC,
3939
backref='address_scopes',
40-
lazy='subquery',
40+
lazy='joined',
4141
cascade='all, delete, delete-orphan')

neutron/db/models/securitygroup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class SecurityGroup(standard_attr.HasStandardAttributes, model_base.BASEV2,
3434
nullable=False)
3535
rbac_entries = sa.orm.relationship(rbac_db_models.SecurityGroupRBAC,
3636
backref='security_group',
37-
lazy='subquery',
37+
lazy='joined',
3838
cascade='all, delete, delete-orphan')
3939
api_collections = [sg.SECURITYGROUPS]
4040
collection_resource_map = {sg.SECURITYGROUPS: 'security_group'}

neutron/db/models_v2.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ class Subnet(standard_attr.HasStandardAttributes, model_base.BASEV2,
228228
# subnets don't have their own rbac_entries, they just inherit from
229229
# the network rbac entries
230230
rbac_entries = orm.relationship(
231-
rbac_db_models.NetworkRBAC, lazy='subquery', uselist=True,
231+
rbac_db_models.NetworkRBAC, lazy='joined', uselist=True,
232232
foreign_keys='Subnet.network_id',
233233
primaryjoin='Subnet.network_id==NetworkRBAC.object_id',
234234
viewonly=True)
@@ -282,7 +282,7 @@ class SubnetPool(standard_attr.HasStandardAttributes, model_base.BASEV2,
282282
lazy='subquery')
283283
rbac_entries = sa.orm.relationship(rbac_db_models.SubnetPoolRBAC,
284284
backref='subnetpools',
285-
lazy='subquery',
285+
lazy='joined',
286286
cascade='all, delete, delete-orphan')
287287
api_collections = [subnetpool_def.COLLECTION_NAME]
288288
collection_resource_map = {subnetpool_def.COLLECTION_NAME:
@@ -304,7 +304,7 @@ class Network(standard_attr.HasStandardAttributes, model_base.BASEV2,
304304
rbac_entries = orm.relationship(rbac_db_models.NetworkRBAC,
305305
backref=orm.backref('network',
306306
load_on_pending=True),
307-
lazy='subquery',
307+
lazy='joined',
308308
cascade='all, delete, delete-orphan')
309309
availability_zone_hints = sa.Column(sa.String(255))
310310
mtu = sa.Column(sa.Integer, nullable=False,

neutron/db/qos/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class QosPolicy(standard_attr.HasStandardAttributes, model_base.BASEV2,
2929
__tablename__ = 'qos_policies'
3030
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
3131
rbac_entries = sa.orm.relationship(rbac_db_models.QosPolicyRBAC,
32-
backref='qos_policy', lazy='subquery',
32+
backref='qos_policy', lazy='joined',
3333
cascade='all, delete, delete-orphan')
3434
api_collections = ['policies']
3535
collection_resource_map = {'policies': 'policy'}

neutron/tests/unit/objects/test_address_group.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class AddressGroupRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin,
5858
testlib_api.SqlTestCase):
5959

6060
_test_class = address_group.AddressGroupRBAC
61+
_parent_class = address_group.AddressGroup
6162

6263
def setUp(self):
6364
super(AddressGroupRBACDbObjectTestCase, self).setUp()

neutron/tests/unit/objects/test_address_scope.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class AddressScopeRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin,
3636
testlib_api.SqlTestCase):
3737

3838
_test_class = address_scope.AddressScopeRBAC
39+
_parent_class = address_scope.AddressScope
3940

4041
def setUp(self):
4142
super(AddressScopeRBACDbObjectTestCase, self).setUp()

neutron/tests/unit/objects/test_network.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
from unittest import mock
1414

15+
from neutron_lib.api.definitions import availability_zone as az_def
16+
1517
from neutron.db import rbac_db_models
1618
from neutron.objects import base as obj_base
1719
from neutron.objects import network
@@ -27,6 +29,7 @@ class NetworkRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin,
2729
testlib_api.SqlTestCase):
2830

2931
_test_class = network.NetworkRBAC
32+
_parent_class = network.Network
3033

3134
def setUp(self):
3235
self._mock_get_valid_actions = mock.patch.object(
@@ -50,6 +53,13 @@ def test_object_version_degradation_1_1_to_1_0_no_id_no_project_id(self):
5053
network_rbac_obj['versioned_object.data'])
5154
self.assertNotIn('id', network_rbac_obj['versioned_object.data'])
5255

56+
def _create_random_parent_object(self):
57+
objclass_fields = self.get_random_db_fields(self._parent_class)
58+
objclass_fields.pop(az_def.AZ_HINTS)
59+
_obj = self._parent_class(self.context, **objclass_fields)
60+
_obj.create()
61+
return _obj
62+
5363

5464
class NetworkRBACIfaceOjectTestCase(test_rbac.TestRBACObjectMixin,
5565
obj_test_base.BaseObjectIfaceTestCase):

neutron/tests/unit/objects/test_rbac.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1010
# License for the specific language governing permissions and limitations
1111
# under the License.
12-
import random
1312

13+
import random
1414
from unittest import mock
1515

16+
from neutron_lib import context
17+
18+
from neutron.db import rbac_db_models
1619
from neutron.objects import address_group
1720
from neutron.objects import address_scope
1821
from neutron.objects import network
@@ -26,6 +29,9 @@
2629

2730
class TestRBACObjectMixin(object):
2831

32+
_test_class = None
33+
_parent_class = None
34+
2935
def get_random_object_fields(self, obj_cls=None):
3036
fields = (super(TestRBACObjectMixin, self).
3137
get_random_object_fields(obj_cls))
@@ -34,6 +40,35 @@ def get_random_object_fields(self, obj_cls=None):
3440
fields['action'] = rnd_actions[idx]
3541
return fields
3642

43+
def _create_random_parent_object(self):
44+
objclass_fields = self.get_random_db_fields(self._parent_class)
45+
_obj = self._parent_class(self.context, **objclass_fields)
46+
_obj.create()
47+
return _obj
48+
49+
def test_rbac_shared_on_parent_object(self):
50+
if not self._test_class or not self._parent_class:
51+
self.skipTest('Mixin class, skipped test')
52+
project_id = self.objs[0].project_id
53+
_obj_shared = self._create_random_parent_object()
54+
# Create a second object that won't be shared and thus won't be
55+
# retrieved by the non-admin users.
56+
self._create_random_parent_object()
57+
for idx in range(3):
58+
project = 'project_%s' % idx
59+
rbac = self._test_class(
60+
self.context, project_id=project_id, target_project=project,
61+
action=rbac_db_models.ACCESS_SHARED,
62+
object_id=_obj_shared.id)
63+
rbac.create()
64+
65+
for idx in range(3):
66+
project = 'project_%s' % idx
67+
ctx_no_admin = context.Context(user_id='user', tenant_id=project,
68+
is_admin=False)
69+
objects = self._parent_class.get_objects(ctx_no_admin)
70+
self.assertEqual([_obj_shared.id], [_obj.id for _obj in objects])
71+
3772

3873
class RBACBaseObjectTestCase(neutron_test_base.BaseTestCase):
3974

neutron/tests/unit/objects/test_securitygroup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class SecurityGroupRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin,
2727
testlib_api.SqlTestCase):
2828

2929
_test_class = securitygroup.SecurityGroupRBAC
30+
_parent_class = securitygroup.SecurityGroup
3031

3132
def setUp(self):
3233
super(SecurityGroupRBACDbObjectTestCase, self).setUp()

0 commit comments

Comments
 (0)