]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Support Basic Address Scope CRUD as extensions
authorvikram.choudhary <vikram.choudhary@huawei.com>
Tue, 9 Jun 2015 14:25:59 +0000 (19:55 +0530)
committerNuman Siddique <nusiddiq@redhat.com>
Thu, 2 Jul 2015 08:19:06 +0000 (13:49 +0530)
This patch adds the support for basic address scope CRUD.
Subsequent patches will be added to use this address scope
on subnet pools.

DocImpact
APIImpact

Co-Authored-By: Ryan Tidwell <rktidwell85@gmail.com>
Co-Authored-By: Numan Siddique <nusiddiq@redhat.com>
Change-Id: Icabdd22577cfda0e1fbf6042e4b05b8080e54fdb
Partially-implements:  blueprint address-scopes

etc/policy.json
neutron/db/address_scope_db.py [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/52c5312f6baf_address_scopes.py [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/HEAD
neutron/extensions/address_scope.py [new file with mode: 0644]
neutron/plugins/ml2/plugin.py
neutron/tests/etc/policy.json
neutron/tests/unit/extensions/test_address_scope.py [new file with mode: 0644]

index 87f6b266897acb5c47292a9bce68c5682e84852f..eaf6d685ffecb5d51332134fc6656e766afbd14b 100644 (file)
@@ -9,6 +9,7 @@
     "shared_firewalls": "field:firewalls:shared=True",
     "shared_firewall_policies": "field:firewall_policies:shared=True",
     "shared_subnetpools": "field:subnetpools:shared=True",
+    "shared_address_scopes": "field:address_scopes:shared=True",
     "external": "field:networks:router:external=True",
     "default": "rule:admin_or_owner",
 
     "update_subnetpool": "rule:admin_or_owner",
     "delete_subnetpool": "rule:admin_or_owner",
 
+    "create_address_scope": "",
+    "create_address_scope:shared": "rule:admin_only",
+    "get_address_scope": "rule:admin_or_owner or rule:shared_address_scopes",
+    "update_address_scope": "rule:admin_or_owner",
+    "update_address_scope:shared": "rule:admin_only",
+    "delete_address_scope": "rule:admin_or_owner",
+
     "create_network": "",
     "get_network": "rule:admin_or_owner or rule:shared or rule:external or rule:context_is_advsvc",
     "get_network:router:external": "rule:regular_user",
diff --git a/neutron/db/address_scope_db.py b/neutron/db/address_scope_db.py
new file mode 100644 (file)
index 0000000..c5c7a84
--- /dev/null
@@ -0,0 +1,105 @@
+# Copyright (c) 2015 Huawei Technologies Co.,LTD.
+#
+#    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 oslo_log import log as logging
+from oslo_utils import uuidutils
+import sqlalchemy as sa
+from sqlalchemy.orm import exc
+
+from neutron.db import model_base
+from neutron.db import models_v2
+from neutron.extensions import address_scope as ext_address_scope
+
+LOG = logging.getLogger(__name__)
+
+
+class AddressScope(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
+    """Represents a neutron address scope."""
+
+    __tablename__ = "address_scopes"
+
+    name = sa.Column(sa.String(255), nullable=False)
+    shared = sa.Column(sa.Boolean, nullable=False)
+
+
+class AddressScopeDbMixin(ext_address_scope.AddressScopePluginBase):
+    """Mixin class to add address scope to db_base_plugin_v2."""
+
+    __native_bulk_support = True
+
+    def _make_address_scope_dict(self, address_scope, fields=None):
+        res = {'id': address_scope['id'],
+               'name': address_scope['name'],
+               'tenant_id': address_scope['tenant_id'],
+               'shared': address_scope['shared']}
+        return self._fields(res, fields)
+
+    def _get_address_scope(self, context, id):
+        try:
+            return self._get_by_id(context, AddressScope, id)
+        except exc.NoResultFound:
+            raise ext_address_scope.AddressScopeNotFound(address_scope_id=id)
+
+    def create_address_scope(self, context, address_scope):
+        """Create a address scope."""
+        a_s = address_scope['address_scope']
+        tenant_id = self._get_tenant_id_for_create(context, a_s)
+        address_scope_id = a_s.get('id') or uuidutils.generate_uuid()
+        with context.session.begin(subtransactions=True):
+            pool_args = {'tenant_id': tenant_id,
+                         'id': address_scope_id,
+                         'name': a_s['name'],
+                         'shared': a_s['shared']}
+            address_scope = AddressScope(**pool_args)
+            context.session.add(address_scope)
+
+        return self._make_address_scope_dict(address_scope)
+
+    def update_address_scope(self, context, id, address_scope):
+        a_s = address_scope['address_scope']
+        with context.session.begin(subtransactions=True):
+            address_scope = self._get_address_scope(context, id)
+            if address_scope.shared and not a_s.get('shared', True):
+                reason = _("Shared address scope can't be unshared")
+                raise ext_address_scope.AddressScopeUpdateError(
+                    address_scope_id=id, reason=reason)
+            address_scope.update(a_s)
+
+        return self._make_address_scope_dict(address_scope)
+
+    def get_address_scope(self, context, id, fields=None):
+        address_scope = self._get_address_scope(context, id)
+        return self._make_address_scope_dict(address_scope, fields)
+
+    def get_address_scopes(self, context, filters=None, fields=None,
+                           sorts=None, limit=None, marker=None,
+                           page_reverse=False):
+        marker_obj = self._get_marker_obj(context, 'addrscope', limit, marker)
+        collection = self._get_collection(context, AddressScope,
+                                          self._make_address_scope_dict,
+                                          filters=filters, fields=fields,
+                                          sorts=sorts,
+                                          limit=limit,
+                                          marker_obj=marker_obj,
+                                          page_reverse=page_reverse)
+        return collection
+
+    def get_address_scopes_count(self, context, filters=None):
+        return self._get_collection_count(context, AddressScope,
+                                          filters=filters)
+
+    def delete_address_scope(self, context, id):
+        with context.session.begin(subtransactions=True):
+            address_scope = self._get_address_scope(context, id)
+            context.session.delete(address_scope)
diff --git a/neutron/db/migration/alembic_migrations/versions/52c5312f6baf_address_scopes.py b/neutron/db/migration/alembic_migrations/versions/52c5312f6baf_address_scopes.py
new file mode 100644 (file)
index 0000000..9fa1466
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright (c) 2015 Red Hat, Inc.
+#
+#    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.
+#
+
+"""Initial operations in support of address scopes
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '52c5312f6baf'
+down_revision = '599c6a226151'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.create_table(
+        'address_scopes',
+        sa.Column('id', sa.String(length=36), nullable=False),
+        sa.Column('name', sa.String(length=255), nullable=False),
+        sa.Column('tenant_id', sa.String(length=255), nullable=True,
+                  index=True),
+        sa.Column('shared', sa.Boolean(), nullable=False),
+        sa.PrimaryKeyConstraint('id'))
index 054926f3afd391695c5bc57a06c986106e858821..5d2bcdc22c2ea5a88a2a4bb949e4feeb234182d3 100644 (file)
@@ -1 +1 @@
-599c6a226151
+52c5312f6baf
diff --git a/neutron/extensions/address_scope.py b/neutron/extensions/address_scope.py
new file mode 100644 (file)
index 0000000..6382992
--- /dev/null
@@ -0,0 +1,138 @@
+# Copyright (c) 2015 Huawei Technologies Co.,LTD.
+#
+#    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.
+
+import abc
+
+from neutron.api import extensions
+from neutron.api.v2 import attributes as attr
+from neutron.api.v2 import base
+from neutron.common import exceptions as nexception
+from neutron import manager
+import six
+
+ADDRESS_SCOPE = 'address_scope'
+ADDRESS_SCOPES = '%ss' % ADDRESS_SCOPE
+
+
+# Attribute Map
+RESOURCE_ATTRIBUTE_MAP = {
+    ADDRESS_SCOPES: {
+        'id': {'allow_post': False,
+               'allow_put': False,
+               'validate': {'type:uuid': None},
+               'is_visible': True,
+               'primary_key': True},
+        'name': {'allow_post': True,
+                 'allow_put': True,
+                 'default': '',
+                 'validate': {'type:string': attr.NAME_MAX_LEN},
+                 'is_visible': True},
+        'tenant_id': {'allow_post': True,
+                      'allow_put': False,
+                      'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
+                      'required_by_policy': True,
+                      'is_visible': True},
+        attr.SHARED: {'allow_post': True,
+                      'allow_put': True,
+                      'default': False,
+                      'convert_to': attr.convert_to_boolean,
+                      'is_visible': True,
+                      'required_by_policy': True,
+                      'enforce_policy': True},
+    }
+}
+
+
+class AddressScopeNotFound(nexception.NotFound):
+    message = _("Address scope %(address_scope_id)s could not be found")
+
+
+class AddressScopeDeleteError(nexception.BadRequest):
+    message = _("Unable to delete address scope %(address_scope_id)s : "
+                "%(reason)s")
+
+
+class AddressScopeUpdateError(nexception.BadRequest):
+    message = _("Unable to update address scope %(address_scope_id)s : "
+                "%(reason)s")
+
+
+class Address_scope(extensions.ExtensionDescriptor):
+    """Extension class supporting Address Scopes."""
+
+    @classmethod
+    def get_name(cls):
+        return "Address scope"
+
+    @classmethod
+    def get_alias(cls):
+        return "address-scope"
+
+    @classmethod
+    def get_description(cls):
+        return "Address scopes extension."
+
+    @classmethod
+    def get_updated(cls):
+        return "2015-07-26T10:00:00-00:00"
+
+    @classmethod
+    def get_resources(cls):
+        """Returns Ext Resources."""
+        my_plurals = [(key, key[:-1]) for key in RESOURCE_ATTRIBUTE_MAP.keys()]
+        attr.PLURALS.update(dict(my_plurals))
+        plugin = manager.NeutronManager.get_plugin()
+        collection_name = ADDRESS_SCOPES.replace('_', '-')
+        params = RESOURCE_ATTRIBUTE_MAP.get(ADDRESS_SCOPES, dict())
+        controller = base.create_resource(collection_name,
+                                          ADDRESS_SCOPE,
+                                          plugin, params, allow_bulk=True,
+                                          allow_pagination=True,
+                                          allow_sorting=True)
+
+        ex = extensions.ResourceExtension(collection_name, controller,
+                                          attr_map=params)
+        return [ex]
+
+    def get_extended_resources(self, version):
+        return {}
+
+
+@six.add_metaclass(abc.ABCMeta)
+class AddressScopePluginBase(object):
+
+    @abc.abstractmethod
+    def create_address_scope(self, context, adress_scope):
+        pass
+
+    @abc.abstractmethod
+    def update_address_scope(self, context, id, address_scope):
+        pass
+
+    @abc.abstractmethod
+    def get_address_scope(self, context, id, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def get_address_scopes(self, context, filters=None, fields=None,
+                           sorts=None, limit=None, marker=None,
+                           page_reverse=False):
+        pass
+
+    @abc.abstractmethod
+    def delete_address_scope(self, context, id):
+        pass
+
+    def get_address_scopes_count(self, context, filters=None):
+        raise NotImplementedError()
index a56039d4548119039d8ad5b4538e902454baea61..c61a717e3933288fc179c272367174198cc164ba 100644 (file)
@@ -44,6 +44,7 @@ from neutron.common import log as neutron_log
 from neutron.common import rpc as n_rpc
 from neutron.common import topics
 from neutron.common import utils
+from neutron.db import address_scope_db
 from neutron.db import agents_db
 from neutron.db import agentschedulers_db
 from neutron.db import allowedaddresspairs_db as addr_pair_db
@@ -88,7 +89,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
                 addr_pair_db.AllowedAddressPairsMixin,
                 vlantransparent_db.Vlantransparent_db_mixin,
                 extradhcpopt_db.ExtraDhcpOptMixin,
-                netmtu_db.Netmtu_db_mixin):
+                netmtu_db.Netmtu_db_mixin,
+                address_scope_db.AddressScopeDbMixin):
 
     """Implement the Neutron L2 abstractions using modules.
 
@@ -112,7 +114,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
                                     "dhcp_agent_scheduler",
                                     "multi-provider", "allowed-address-pairs",
                                     "extra_dhcp_opt", "subnet_allocation",
-                                    "net-mtu", "vlan-transparent"]
+                                    "net-mtu", "vlan-transparent",
+                                    "address-scope"]
 
     @property
     def supported_extension_aliases(self):
index 87f6b266897acb5c47292a9bce68c5682e84852f..eaf6d685ffecb5d51332134fc6656e766afbd14b 100644 (file)
@@ -9,6 +9,7 @@
     "shared_firewalls": "field:firewalls:shared=True",
     "shared_firewall_policies": "field:firewall_policies:shared=True",
     "shared_subnetpools": "field:subnetpools:shared=True",
+    "shared_address_scopes": "field:address_scopes:shared=True",
     "external": "field:networks:router:external=True",
     "default": "rule:admin_or_owner",
 
     "update_subnetpool": "rule:admin_or_owner",
     "delete_subnetpool": "rule:admin_or_owner",
 
+    "create_address_scope": "",
+    "create_address_scope:shared": "rule:admin_only",
+    "get_address_scope": "rule:admin_or_owner or rule:shared_address_scopes",
+    "update_address_scope": "rule:admin_or_owner",
+    "update_address_scope:shared": "rule:admin_only",
+    "delete_address_scope": "rule:admin_or_owner",
+
     "create_network": "",
     "get_network": "rule:admin_or_owner or rule:shared or rule:external or rule:context_is_advsvc",
     "get_network:router:external": "rule:regular_user",
diff --git a/neutron/tests/unit/extensions/test_address_scope.py b/neutron/tests/unit/extensions/test_address_scope.py
new file mode 100644 (file)
index 0000000..df46e6b
--- /dev/null
@@ -0,0 +1,239 @@
+# Copyright (c) 2015 Red Hat, Inc.
+#
+#    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.
+
+import contextlib
+
+import webob.exc
+
+from neutron.api.v2 import attributes as attr
+from neutron import context
+from neutron.db import address_scope_db
+from neutron.db import db_base_plugin_v2
+from neutron.extensions import address_scope as ext_address_scope
+from neutron.tests.unit.db import test_db_base_plugin_v2
+
+DB_PLUGIN_KLASS = ('neutron.tests.unit.extensions.test_address_scope.'
+                   'AddressScopeTestPlugin')
+
+
+class AddressScopeTestExtensionManager(object):
+
+    def get_resources(self):
+        # Add the resources to the global attribute map
+        # This is done here as the setup process won't
+        # initialize the main API router which extends
+        # the global attribute map
+        attr.RESOURCE_ATTRIBUTE_MAP.update(
+            ext_address_scope.RESOURCE_ATTRIBUTE_MAP)
+        return ext_address_scope.Address_scope.get_resources()
+
+    def get_actions(self):
+        return []
+
+    def get_request_extensions(self):
+        return []
+
+
+class AddressScopeTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
+
+    def _create_address_scope(self, fmt, expected_res_status=None,
+                              admin=False, **kwargs):
+        address_scope = {'address_scope': {}}
+        for k, v in kwargs.items():
+            address_scope['address_scope'][k] = str(v)
+
+        address_scope_req = self.new_create_request('address-scopes',
+                                                    address_scope, fmt)
+
+        if not admin:
+            neutron_context = context.Context('', kwargs.get('tenant_id',
+                                                             self._tenant_id))
+            address_scope_req.environ['neutron.context'] = neutron_context
+
+        address_scope_res = address_scope_req.get_response(self.ext_api)
+        if expected_res_status:
+            self.assertEqual(address_scope_res.status_int, expected_res_status)
+        return address_scope_res
+
+    def _make_address_scope(self, fmt, admin=False, **kwargs):
+        res = self._create_address_scope(fmt, admin=admin, **kwargs)
+        if res.status_int >= webob.exc.HTTPClientError.code:
+            raise webob.exc.HTTPClientError(code=res.status_int)
+        return self.deserialize(fmt, res)
+
+    @contextlib.contextmanager
+    def address_scope(self, admin=False, **kwargs):
+        addr_scope = self._make_address_scope(self.fmt, admin, **kwargs)
+        yield addr_scope
+
+    def _test_create_address_scope(self, admin=False, expected=None, **kwargs):
+        keys = kwargs.copy()
+        keys.setdefault('tenant_id', self._tenant_id)
+        with self.address_scope(admin=admin, **keys) as addr_scope:
+            self._validate_resource(addr_scope, keys, 'address_scope')
+            if expected:
+                self._compare_resource(addr_scope, expected, 'address_scope')
+        return addr_scope
+
+    def _test_update_address_scope(self, addr_scope_id, data, admin=False,
+                                   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_res = update_req.get_response(self.ext_api)
+        if expected:
+            addr_scope = self.deserialize(self.fmt, update_res)
+            self._compare_resource(addr_scope, expected, 'address_scope')
+            return addr_scope
+
+        return update_res
+
+
+class AddressScopeTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
+                             address_scope_db.AddressScopeDbMixin):
+    __native_pagination_support = True
+    __native_sorting_support = True
+
+    supported_extension_aliases = ["address-scope"]
+
+
+class TestAddressScope(AddressScopeTestCase):
+
+    def setUp(self):
+        plugin = DB_PLUGIN_KLASS
+        ext_mgr = AddressScopeTestExtensionManager()
+        super(TestAddressScope, self).setUp(plugin=plugin, ext_mgr=ext_mgr)
+
+    def test_create_address_scope(self):
+        expected_addr_scope = {'name': 'foo-address-scope',
+                               'tenant_id': self._tenant_id,
+                               'shared': False}
+        self._test_create_address_scope(name='foo-address-scope',
+                                        expected=expected_addr_scope)
+
+    def test_create_address_scope_empty_name(self):
+        expected_addr_scope = {'name': '',
+                               'tenant_id': self._tenant_id,
+                               'shared': False}
+        self._test_create_address_scope(name='', expected=expected_addr_scope)
+
+        # no name specified
+        self._test_create_address_scope(expected=expected_addr_scope)
+
+    def test_create_address_scope_shared_admin(self):
+        expected_addr_scope = {'name': 'foo-address-scope', 'shared': True}
+        self._test_create_address_scope(name='foo-address-scope', admin=True,
+                                        shared=True,
+                                        expected=expected_addr_scope)
+
+    def test_created_address_scope_shared_non_admin(self):
+        res = self._create_address_scope(self.fmt, name='foo-address-scope',
+                                         tenant_id=self._tenant_id,
+                                         admin=False, shared=True)
+        self.assertEqual(webob.exc.HTTPForbidden.code, res.status_int)
+
+    def test_created_address_scope_specify_id(self):
+        res = self._create_address_scope(self.fmt, name='foo-address-scope',
+                                         id='foo-id')
+        self.assertEqual(webob.exc.HTTPClientError.code, res.status_int)
+
+    def test_delete_address_scope(self):
+        with self.address_scope(name='foo-address-scope') as addr_scope:
+            self._delete('address-scopes', addr_scope['address_scope']['id'])
+            self._show('address-scopes', addr_scope['address_scope']['id'],
+                       expected_code=webob.exc.HTTPNotFound.code)
+
+    def test_update_address_scope(self):
+        addr_scope = self._test_create_address_scope(name='foo-address-scope')
+        data = {'address_scope': {'name': 'bar-address-scope'}}
+        self._test_update_address_scope(addr_scope['address_scope']['id'],
+                                        data, expected=data['address_scope'])
+
+    def test_update_address_scope_shared_true_admin(self):
+        addr_scope = self._test_create_address_scope(name='foo-address-scope')
+        data = {'address_scope': {'shared': True}}
+        self._test_update_address_scope(addr_scope['address_scope']['id'],
+                                        data, admin=True,
+                                        expected=data['address_scope'])
+
+    def test_update_address_scope_shared_true_non_admin(self):
+        addr_scope = self._test_create_address_scope(name='foo-address-scope')
+        data = {'address_scope': {'shared': True}}
+        res = self._test_update_address_scope(
+            addr_scope['address_scope']['id'], data, admin=False)
+        self.assertEqual(webob.exc.HTTPForbidden.code, res.status_int)
+
+    def test_update_address_scope_shared_false_admin(self):
+        addr_scope = self._test_create_address_scope(name='foo-address-scope',
+                                                     admin=True, shared=True)
+        data = {'address_scope': {'shared': False}}
+        res = self._test_update_address_scope(
+            addr_scope['address_scope']['id'], data, admin=True)
+        self.assertEqual(webob.exc.HTTPClientError.code, res.status_int)
+
+    def test_get_address_scope(self):
+        addr_scope = self._test_create_address_scope(name='foo-address-scope')
+        req = self.new_show_request('address-scopes',
+                                    addr_scope['address_scope']['id'])
+        res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+        self.assertEqual(addr_scope['address_scope']['id'],
+                         res['address_scope']['id'])
+
+    def test_get_address_scope_different_tenants_not_shared(self):
+        addr_scope = self._test_create_address_scope(name='foo-address-scope')
+        req = self.new_show_request('address-scopes',
+                                    addr_scope['address_scope']['id'])
+        neutron_context = context.Context('', 'not-the-owner')
+        req.environ['neutron.context'] = neutron_context
+        res = req.get_response(self.ext_api)
+        self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int)
+
+    def test_get_address_scope_different_tenants_shared(self):
+        addr_scope = self._test_create_address_scope(name='foo-address-scope',
+                                                     shared=True, admin=True)
+        req = self.new_show_request('address-scopes',
+                                    addr_scope['address_scope']['id'])
+        neutron_context = context.Context('', 'test-tenant-2')
+        req.environ['neutron.context'] = neutron_context
+        res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+        self.assertEqual(addr_scope['address_scope']['id'],
+                         res['address_scope']['id'])
+
+    def test_list_address_scopes(self):
+        self._test_create_address_scope(name='foo-address-scope')
+        self._test_create_address_scope(name='bar-address-scope')
+        res = self._list('address-scopes')
+        self.assertEqual(2, len(res['address_scopes']))
+
+    def test_list_address_scopes_different_tenants_shared(self):
+        self._test_create_address_scope(name='foo-address-scope', shared=True,
+                                        admin=True)
+        admin_res = self._list('address-scopes')
+        mortal_res = self._list(
+            'address-scopes',
+            neutron_context=context.Context('', 'not-the-owner'))
+        self.assertEqual(1, len(admin_res['address_scopes']))
+        self.assertEqual(1, len(mortal_res['address_scopes']))
+
+    def test_list_address_scopes_different_tenants_not_shared(self):
+        self._test_create_address_scope(name='foo-address-scope')
+        admin_res = self._list('address-scopes')
+        mortal_res = self._list(
+            'address-scopes',
+            neutron_context=context.Context('', 'not-the-owner'))
+        self.assertEqual(1, len(admin_res['address_scopes']))
+        self.assertEqual(0, len(mortal_res['address_scopes']))