diff --git a/etc/neutron/plugins/cisco/cisco_plugins.ini b/etc/neutron/plugins/cisco/cisco_plugins.ini index 17eae7378a..a93bc7f1d9 100644 --- a/etc/neutron/plugins/cisco/cisco_plugins.ini +++ b/etc/neutron/plugins/cisco/cisco_plugins.ini @@ -98,3 +98,10 @@ # (IntOpt) Timeout duration in seconds for the http request # Default value: 15 # http_timeout = 15 + +# (BoolOpt) Specify whether tenants are restricted from accessing network +# profiles belonging to other tenants. +# Default value: True, indicating other tenants cannot access network +# profiles belonging to a tenant. +# +# restrict_network_profiles = True diff --git a/neutron/plugins/cisco/common/config.py b/neutron/plugins/cisco/common/config.py index dcba6b64da..359dd11d26 100644 --- a/neutron/plugins/cisco/common/config.py +++ b/neutron/plugins/cisco/common/config.py @@ -70,6 +70,10 @@ cisco_n1k_opts = [ help=_("Number of threads to use to make HTTP requests")), cfg.IntOpt('http_timeout', default=15, help=_("N1K http timeout duration in seconds")), + cfg.BoolOpt('restrict_network_profiles', default=True, + help=_("Restrict tenants from accessing network profiles " + "belonging to some other tenant")), + ] cfg.CONF.register_opts(cisco_opts, "CISCO") diff --git a/neutron/plugins/cisco/db/n1kv_db_v2.py b/neutron/plugins/cisco/db/n1kv_db_v2.py index fe6443ed95..44c51fbb2d 100644 --- a/neutron/plugins/cisco/db/n1kv_db_v2.py +++ b/neutron/plugins/cisco/db/n1kv_db_v2.py @@ -516,7 +516,7 @@ def reserve_vxlan(db_session, network_profile): raise n_exc.NoNetworkAvailable() -def alloc_network(db_session, network_profile_id): +def alloc_network(db_session, network_profile_id, tenant_id): """ Allocate network using first available free segment ID in segment range. @@ -525,7 +525,7 @@ def alloc_network(db_session, network_profile_id): """ with db_session.begin(subtransactions=True): network_profile = get_network_profile(db_session, - network_profile_id) + network_profile_id, tenant_id) if network_profile.segment_type == c_const.NETWORK_TYPE_VLAN: return reserve_vlan(db_session, network_profile) if network_profile.segment_type == c_const.NETWORK_TYPE_OVERLAY: @@ -785,12 +785,12 @@ def create_network_profile(db_session, network_profile): return net_profile -def delete_network_profile(db_session, id): +def delete_network_profile(db_session, id, tenant_id=None): """Delete Network Profile.""" LOG.debug("delete_network_profile()") with db_session.begin(subtransactions=True): try: - network_profile = get_network_profile(db_session, id) + network_profile = get_network_profile(db_session, id, tenant_id) db_session.delete(network_profile) (db_session.query(n1kv_models_v2.ProfileBinding). filter_by(profile_id=id).delete()) @@ -799,21 +799,27 @@ def delete_network_profile(db_session, id): raise c_exc.ProfileTenantBindingNotFound(profile_id=id) -def update_network_profile(db_session, id, network_profile): +def update_network_profile(db_session, id, network_profile, tenant_id=None): """Update Network Profile.""" LOG.debug("update_network_profile()") with db_session.begin(subtransactions=True): - profile = get_network_profile(db_session, id) + profile = get_network_profile(db_session, id, tenant_id) profile.update(network_profile) return profile -def get_network_profile(db_session, id): +def get_network_profile(db_session, id, tenant_id=None): """Get Network Profile.""" LOG.debug("get_network_profile()") + if tenant_id and c_conf.CISCO_N1K.restrict_network_profiles: + if _profile_binding_exists(db_session=db_session, + tenant_id=tenant_id, + profile_id=id, + profile_type=c_const.NETWORK) is None: + raise c_exc.ProfileTenantBindingNotFound(profile_id=id) try: - return db_session.query( - n1kv_models_v2.NetworkProfile).filter_by(id=id).one() + return db_session.query(n1kv_models_v2.NetworkProfile).filter_by( + id=id).one() except exc.NoResultFound: raise c_exc.NetworkProfileNotFound(profile=id) @@ -1085,10 +1091,12 @@ class NetworkProfile_db_mixin(object): """ # Check whether the network profile is in use. if self._segment_in_use(context.session, - get_network_profile(context.session, id)): + get_network_profile(context.session, id, + context.tenant_id)): raise c_exc.NetworkProfileInUse(profile=id) # Delete and return the network profile if it is not in use. - _profile = delete_network_profile(context.session, id) + _profile = delete_network_profile(context.session, id, + context.tenant_id) return self._make_network_profile_dict(_profile) def update_network_profile(self, context, id, network_profile): @@ -1105,7 +1113,8 @@ class NetworkProfile_db_mixin(object): # Flag to check whether network profile is updated or not. is_updated = False p = network_profile["network_profile"] - original_net_p = get_network_profile(context.session, id) + original_net_p = get_network_profile(context.session, id, + context.tenant_id) # Update network profile to tenant id binding. if context.is_admin and c_const.ADD_TENANTS in p: profile_bindings = _get_profile_bindings_by_uuid(context.session, @@ -1138,7 +1147,8 @@ class NetworkProfile_db_mixin(object): p.get("segment_range") != original_net_p.segment_range): if not self._segment_in_use(context.session, original_net_p): delete_segment_allocations(context.session, original_net_p) - updated_net_p = update_network_profile(context.session, id, p) + updated_net_p = update_network_profile(context.session, id, p, + context.tenant_id) self._validate_segment_range_uniqueness(context, updated_net_p, id) if original_net_p.segment_type == c_const.NETWORK_TYPE_VLAN: @@ -1163,7 +1173,8 @@ class NetworkProfile_db_mixin(object): # Return network profile if it is successfully updated. if is_updated: return self._make_network_profile_dict( - update_network_profile(context.session, id, p)) + update_network_profile(context.session, id, p, + context.tenant_id)) def get_network_profile(self, context, id, fields=None): """ @@ -1175,7 +1186,7 @@ class NetworkProfile_db_mixin(object): profile dictionary. Only these fields will be returned :returns: network profile dictionary """ - profile = get_network_profile(context.session, id) + profile = get_network_profile(context.session, id, context.tenant_id) return self._make_network_profile_dict(profile, fields) def get_network_profiles(self, context, filters=None, fields=None): @@ -1230,7 +1241,7 @@ class NetworkProfile_db_mixin(object): :returns: true if network profile exist else False """ try: - get_network_profile(context.session, id) + get_network_profile(context.session, id, context.tenant_id) return True except c_exc.NetworkProfileNotFound(profile=id): return False diff --git a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py index 22bcf17e6c..7da67da525 100644 --- a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py +++ b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py @@ -683,7 +683,7 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, LOG.debug('_send_update_network_request: %s', network['id']) db_session = context.session profile = n1kv_db_v2.get_network_profile( - db_session, network[n1kv.PROFILE_ID]) + db_session, network[n1kv.PROFILE_ID], context.tenant_id) n1kvclient = n1kv_client.Client() body = {'description': network['name'], 'id': network['id'], @@ -882,7 +882,8 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, # tenant network (physical_network, network_type, segmentation_id, multicast_ip) = n1kv_db_v2.alloc_network(session, - profile_id) + profile_id, + context.tenant_id) LOG.debug('Physical_network %(phy_net)s, ' 'seg_type %(net_type)s, ' 'seg_id %(seg_id)s, ' diff --git a/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py b/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py index d0b272292b..3f3a7d8da1 100644 --- a/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py +++ b/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py @@ -44,6 +44,8 @@ from neutron.tests.unit import test_l3_schedulers PHYS_NET = 'some-phys-net' VLAN_MIN = 100 VLAN_MAX = 110 +TENANT_NOT_ADMIN = 'not_admin' +TENANT_TEST = 'test' class FakeResponse(object): @@ -159,6 +161,12 @@ class N1kvPluginTestCase(test_plugin.NeutronDbPluginV2TestCase): profile['physical_network'] = PHYS_NET net_p = n1kv_db_v2.create_network_profile(db_session, profile) n1kv_db_v2.sync_vlan_allocations(db_session, net_p) + n1kv_db_v2.create_profile_binding(db_session, self.tenant_id, + net_p['id'], c_const.NETWORK) + n1kv_db_v2.create_profile_binding(db_session, TENANT_NOT_ADMIN, + net_p['id'], c_const.NETWORK) + n1kv_db_v2.create_profile_binding(db_session, TENANT_TEST, + net_p['id'], c_const.NETWORK) return net_p def setUp(self, ext_mgr=NetworkProfileTestExtensionManager()): @@ -557,7 +565,7 @@ class TestN1kvNetworkProfiles(N1kvPluginTestCase): self.new_create_request('network_profiles', net_p_dict) bindings = (db_session.query(n1kv_models_v2.ProfileBinding).filter_by( profile_type="network")) - self.assertEqual(bindings.count(), 0) + self.assertEqual(3, bindings.count()) def test_create_network_profile_with_old_add_tenant_fail(self): data = self._prepare_net_profile_data('vlan') @@ -657,6 +665,52 @@ class TestN1kvNetworkProfiles(N1kvPluginTestCase): net_p['network_profile']['id']) self.assertIsNotNone(tenant4) + def test_get_network_profile_restricted(self): + c_conf.CONF.set_override('restrict_network_profiles', True, + 'CISCO_N1K') + ctx1 = context.Context(user_id='admin', + tenant_id='tenant1', + is_admin=True) + sess1 = db.get_session() + net_p = self._make_test_profile(name='netp1') + n1kv_db_v2.create_profile_binding(sess1, ctx1.tenant_id, + net_p['id'], c_const.NETWORK) + #network profile binding with creator tenant should always exist + profile = n1kv_db_v2.get_network_profile(sess1, net_p['id'], + ctx1.tenant_id) + self.assertIsNotNone(profile) + ctx2 = context.Context(user_id='non_admin', + tenant_id='tenant2', + is_admin=False) + sess2 = db.get_session() + self.assertRaises(c_exc.ProfileTenantBindingNotFound, + n1kv_db_v2.get_network_profile, + sess2, net_p['id'], ctx2.tenant_id) + + def test_get_network_profile_unrestricted(self): + c_conf.CONF.set_override('restrict_network_profiles', False, + 'CISCO_N1K') + ctx1 = context.Context(user_id='admin', + tenant_id='tenant1', + is_admin=True) + sess1 = db.get_session() + net_p = self._make_test_profile(name='netp1') + n1kv_db_v2.create_profile_binding(sess1, ctx1.tenant_id, + net_p['id'], c_const.NETWORK) + # network profile binding with creator tenant should always exist + profile = n1kv_db_v2.get_network_profile(sess1, net_p['id'], + ctx1.tenant_id) + self.assertIsNotNone(profile) + ctx2 = context.Context(user_id='non_admin', + tenant_id='tenant2', + is_admin=False) + sess2 = db.get_session() + profile = n1kv_db_v2.get_network_profile(sess2, net_p['id'], + ctx2.tenant_id) + #network profile will be returned even though the profile is + #not bound to tenant of sess2 + self.assertIsNotNone(profile) + class TestN1kvBasicGet(test_plugin.TestBasicGet, N1kvPluginTestCase):