From 635581912f2da69ac178a8779c2374619801dd19 Mon Sep 17 00:00:00 2001 From: Hong Hui Xiao Date: Sun, 21 Feb 2016 22:18:39 -0500 Subject: [PATCH] Correlate address scope with network With address scope being enabled, networks now are in one ipv4 address scope and one ipv6 address scope. This patch adds derived attributes to the network as part of the address scopes extension that will show related address scopes when viewing a network through the API. APIImpact Change-Id: Ib1657636033ad2c0009d50ebe7c5ae4f72f6b175 Closes-Bug: #1547380 --- neutron/db/address_scope_db.py | 21 +++++++++ neutron/db/models_v2.py | 7 +++ neutron/extensions/address_scope.py | 10 ++++ .../unit/extensions/test_address_scope.py | 46 +++++++++++++++++++ ...s-scope-with-network-ea16e16b0154ac21.yaml | 4 ++ 5 files changed, 88 insertions(+) create mode 100644 releasenotes/notes/correlate-address-scope-with-network-ea16e16b0154ac21.yaml diff --git a/neutron/db/address_scope_db.py b/neutron/db/address_scope_db.py index 5152f74b6cb..0ec0bc9458b 100644 --- a/neutron/db/address_scope_db.py +++ b/neutron/db/address_scope_db.py @@ -18,6 +18,8 @@ from sqlalchemy.orm import exc from neutron._i18n import _ from neutron.api.v2 import attributes as attr +from neutron.common import constants +from neutron.db import db_base_plugin_v2 from neutron.db import model_base from neutron.extensions import address_scope as ext_address_scope @@ -124,3 +126,22 @@ class AddressScopeDbMixin(ext_address_scope.AddressScopePluginBase): raise ext_address_scope.AddressScopeInUse(address_scope_id=id) address_scope = self._get_address_scope(context, id) context.session.delete(address_scope) + + def _extend_network_dict_address_scope(self, network_res, network_db): + network_res[ext_address_scope.IPV4_ADDRESS_SCOPE] = None + network_res[ext_address_scope.IPV6_ADDRESS_SCOPE] = None + subnetpools = {subnet.subnetpool for subnet in network_db.subnets + if subnet.subnetpool} + for subnetpool in subnetpools: + # A network will be constrained to only one subnetpool per address + # family. Retrieve the address scope of subnetpools as the address + # scopes of network. + as_id = subnetpool[ext_address_scope.ADDRESS_SCOPE_ID] + if subnetpool['ip_version'] == constants.IP_VERSION_4: + network_res[ext_address_scope.IPV4_ADDRESS_SCOPE] = as_id + if subnetpool['ip_version'] == constants.IP_VERSION_6: + network_res[ext_address_scope.IPV6_ADDRESS_SCOPE] = as_id + return network_res + + db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs( + attr.NETWORKS, ['_extend_network_dict_address_scope']) diff --git a/neutron/db/models_v2.py b/neutron/db/models_v2.py index 03a3a82dc3f..3451902bbe2 100644 --- a/neutron/db/models_v2.py +++ b/neutron/db/models_v2.py @@ -183,6 +183,13 @@ class Subnet(model_base.HasStandardAttributes, model_base.BASEV2, name = sa.Column(sa.String(attr.NAME_MAX_LEN)) network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id')) subnetpool_id = sa.Column(sa.String(36), index=True) + # NOTE: Explicitly specify join conditions for the relationship because + # subnetpool_id in subnet might be 'prefix_delegation' when the IPv6 Prefix + # Delegation is enabled + subnetpool = orm.relationship( + 'SubnetPool', lazy='joined', + foreign_keys='Subnet.subnetpool_id', + primaryjoin='Subnet.subnetpool_id==SubnetPool.id') ip_version = sa.Column(sa.Integer, nullable=False) cidr = sa.Column(sa.String(64), nullable=False) gateway_ip = sa.Column(sa.String(64)) diff --git a/neutron/extensions/address_scope.py b/neutron/extensions/address_scope.py index 392cf16f004..5898078f81e 100644 --- a/neutron/extensions/address_scope.py +++ b/neutron/extensions/address_scope.py @@ -26,6 +26,8 @@ from neutron import manager ADDRESS_SCOPE = 'address_scope' ADDRESS_SCOPES = '%ss' % ADDRESS_SCOPE ADDRESS_SCOPE_ID = 'address_scope_id' +IPV4_ADDRESS_SCOPE = 'ipv4_%s' % ADDRESS_SCOPE +IPV6_ADDRESS_SCOPE = 'ipv6_%s' % ADDRESS_SCOPE # Attribute Map RESOURCE_ATTRIBUTE_MAP = { @@ -63,6 +65,14 @@ RESOURCE_ATTRIBUTE_MAP = { 'default': attr.ATTR_NOT_SPECIFIED, 'validate': {'type:uuid_or_none': None}, 'is_visible': True} + }, + attr.NETWORKS: { + IPV4_ADDRESS_SCOPE: {'allow_post': False, + 'allow_put': False, + 'is_visible': True}, + IPV6_ADDRESS_SCOPE: {'allow_post': False, + 'allow_put': False, + 'is_visible': True}, } } diff --git a/neutron/tests/unit/extensions/test_address_scope.py b/neutron/tests/unit/extensions/test_address_scope.py index 1bff73ed1d9..75291d55f6e 100644 --- a/neutron/tests/unit/extensions/test_address_scope.py +++ b/neutron/tests/unit/extensions/test_address_scope.py @@ -430,6 +430,52 @@ class TestSubnetPoolsWithAddressScopes(AddressScopeTestCase): def test_not_update_subnetpool_address_scope_not_notify(self): self._test_update_subnetpool_address_scope_notify(False) + def test_network_create_contain_address_scope_attr(self): + with self.network() as network: + result = self._show('networks', network['network']['id']) + keys = [ext_address_scope.IPV4_ADDRESS_SCOPE, + ext_address_scope.IPV6_ADDRESS_SCOPE] + for k in keys: + # Correlated address scopes should initially be None + self.assertIsNone(result['network'][k]) + + def test_correlate_network_with_address_scope(self): + with self.address_scope(name='v4-as') as v4_addr_scope, \ + self.address_scope( + name='v6-as', + ip_version=constants.IP_VERSION_6) as v6_addr_scope, \ + self.network() as network: + v4_as_id = v4_addr_scope['address_scope']['id'] + subnet = netaddr.IPNetwork('10.10.10.0/24') + v4_subnetpool = self._test_create_subnetpool( + [subnet.cidr], name='v4-sp', + min_prefixlen='24', address_scope_id=v4_as_id) + v4_subnetpool_id = v4_subnetpool['subnetpool']['id'] + v6_as_id = v6_addr_scope['address_scope']['id'] + subnet = netaddr.IPNetwork('fd5c:6ee1:c7ae::/64') + v6_subnetpool = self._test_create_subnetpool( + [subnet.cidr], name='v6-sp', + min_prefixlen='64', address_scope_id=v6_as_id) + v6_subnetpool_id = v6_subnetpool['subnetpool']['id'] + data = {'subnet': { + 'network_id': network['network']['id'], + 'subnetpool_id': v4_subnetpool_id, + 'ip_version': 4, + 'tenant_id': network['network']['tenant_id']}} + req = self.new_create_request('subnets', data) + self.deserialize(self.fmt, req.get_response(self.api)) + data['subnet']['subnetpool_id'] = v6_subnetpool_id + data['subnet']['ip_version'] = 6 + req = self.new_create_request('subnets', data) + self.deserialize(self.fmt, req.get_response(self.api)) + result = self._show('networks', network['network']['id']) + self.assertEqual( + v4_as_id, + result['network'][ext_address_scope.IPV4_ADDRESS_SCOPE]) + self.assertEqual( + v6_as_id, + result['network'][ext_address_scope.IPV6_ADDRESS_SCOPE]) + def test_delete_address_scope_in_use(self): with self.address_scope(name='foo-address-scope') as addr_scope: address_scope_id = addr_scope['address_scope']['id'] diff --git a/releasenotes/notes/correlate-address-scope-with-network-ea16e16b0154ac21.yaml b/releasenotes/notes/correlate-address-scope-with-network-ea16e16b0154ac21.yaml new file mode 100644 index 00000000000..c8888ee26ea --- /dev/null +++ b/releasenotes/notes/correlate-address-scope-with-network-ea16e16b0154ac21.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add derived attributes to the network to tell users which address scopes + the network is in.