Merge "add assert to ping test"
diff --git a/.zuul.yaml b/.zuul.yaml
index 65e00b7..36c3893 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -45,6 +45,7 @@
         - net-mtu-writable
         - network-ip-availability
         - network_availability_zone
+        - network-segment-range
         - pagination
         - port-resource-request
         - port-mac-address-regenerate
@@ -58,6 +59,7 @@
         - quotas
         - quota_details
         - rbac-policies
+        - rbac-security-groups
         - router
         - router_availability_zone
         - security-group
@@ -90,6 +92,7 @@
         neutron-segments: true
         neutron-trunk: true
         neutron-uplink-status-propagation: true
+        neutron-network-segment-range: true
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
diff --git a/neutron_tempest_plugin/api/admin/test_network_segment_range.py b/neutron_tempest_plugin/api/admin/test_network_segment_range.py
new file mode 100644
index 0000000..e747fed
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_network_segment_range.py
@@ -0,0 +1,247 @@
+# Copyright (c) 2019, Intel Corporation.
+# 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 tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+
+TEST_SEGMENT_RANGE_MINIMUM_ID = 1100
+TEST_SEGMENT_RANGE_MAXIMUM_ID = 1105
+
+
+class NetworkSegmentRangeTestBase(base.BaseAdminNetworkTest):
+
+    required_extensions = ['network-segment-range']
+
+    @classmethod
+    def skip_checks(cls):
+        super(NetworkSegmentRangeTestBase, cls).skip_checks()
+
+    @classmethod
+    def resource_setup(cls):
+        super(NetworkSegmentRangeTestBase, cls).resource_setup()
+        network_type = "vxlan"
+        physical_network = ""
+        minimum = TEST_SEGMENT_RANGE_MINIMUM_ID
+        maximum = TEST_SEGMENT_RANGE_MAXIMUM_ID
+        cls._network_segment_range_data = {
+            'network_type': network_type, 'physical_network': physical_network,
+            'minimum': minimum, 'maximum': maximum}
+
+    def _create_network_segment_range(self, name=None,
+                                      shared=False, project_id=None,
+                                      network_type=None, physical_network=None,
+                                      minimum=None, maximum=None):
+        name = name or data_utils.rand_name('test_network_segment_range')
+
+        if shared:
+            project_id = ""
+        else:
+            test_project = data_utils.rand_name('test_project')
+            test_description = data_utils.rand_name('desc')
+            project_id = self.create_project(
+                name=test_project,
+                description=test_description)['id']
+
+        network_type = (network_type or
+                        self._network_segment_range_data['network_type'])
+        physical_network = (
+            physical_network or
+            self._network_segment_range_data['physical_network'])
+        minimum = minimum or self._network_segment_range_data['minimum']
+        maximum = maximum or self._network_segment_range_data['maximum']
+
+        network_segment_range = self.create_network_segment_range(
+            name=name, shared=shared, project_id=project_id,
+            network_type=network_type, physical_network=physical_network,
+            minimum=minimum, maximum=maximum)
+        # _delete_network_segment_range will ensure that the network segment
+        # range is really removed
+        self.addCleanup(self._delete_network_segment_range,
+                        network_segment_range['id'])
+
+        return network_segment_range
+
+    def _delete_network_segment_range(self, network_segment_range_id):
+        # Deletes a network segment range and verifies if it is deleted or not
+        self.admin_client.delete_network_segment_range(
+            network_segment_range_id)
+        # Asserting that the network segment range is not found in list after
+        # deletion
+        labels = self.admin_client.list_network_segment_ranges(
+            id=network_segment_range_id)
+        list_range_ids = [r['id'] for r in labels['network_segment_ranges']]
+        self.assertNotIn(network_segment_range_id, list_range_ids)
+
+
+class NetworkSegmentRangeTestJson(NetworkSegmentRangeTestBase):
+    """Test Network Segment Range
+
+    Tests the following operations in the Neutron API using the REST client for
+    Neutron:
+
+        List, Show, Create, Update, Delete Network Segment Range
+    """
+
+    @decorators.idempotent_id('44a1ece1-d85d-4253-92f8-4efc318a6d8e')
+    def test_create_update_delete_network_segment_range(self):
+        # Creates a network segment range
+        network_segment_range = self._create_network_segment_range()
+        self.assertIsNotNone(network_segment_range['id'])
+        self.assertFalse(network_segment_range['default'])
+        self.assertFalse(network_segment_range['shared'])
+        self.assertEqual('vxlan', network_segment_range['network_type'])
+        self.assertEqual(TEST_SEGMENT_RANGE_MINIMUM_ID,
+                         network_segment_range['minimum'])
+        self.assertEqual(TEST_SEGMENT_RANGE_MAXIMUM_ID,
+                         network_segment_range['maximum'])
+        # Updates a network segment range
+        updated_minimum = TEST_SEGMENT_RANGE_MINIMUM_ID - 50
+        updated_maximum = TEST_SEGMENT_RANGE_MAXIMUM_ID + 50
+        body = self.admin_client.update_network_segment_range(
+            network_segment_range['id'], name='new-range-name',
+            minimum=updated_minimum, maximum=updated_maximum)
+        updated_network_segment_range = body['network_segment_range']
+        self.assertEqual('new-range-name',
+                         updated_network_segment_range['name'])
+        self.assertEqual(updated_minimum,
+                         updated_network_segment_range['minimum'])
+        self.assertEqual(updated_maximum,
+                         updated_network_segment_range['maximum'])
+
+    @decorators.idempotent_id('5e118fef-a139-4886-8250-07e73d2cbe7a')
+    def test_update_network_segment_range_failed_with_existing_range_impacted(
+            self):
+        # Creates a network segment range
+        network_segment_range = self._create_network_segment_range()
+        project_id = network_segment_range['project_id']
+        # Creates a network
+        name = data_utils.rand_name('test_network_for_' + project_id)
+        network = self.create_network(
+            name, client=self.admin_client, project_id=project_id)
+        # Updates a network segment range
+        updated_maximum = TEST_SEGMENT_RANGE_MAXIMUM_ID + 50
+        self.assertRaises(lib_exc.Conflict,
+                          self.admin_client.update_network_segment_range,
+                          network_segment_range['id'],
+                          name='new-range-name',
+                          minimum=network['provider:segmentation_id'],
+                          maximum=updated_maximum)
+        # network needs to be deleted otherwise the range deletion will fail
+        # because the segment is in use (assigned to the network created)
+        self.admin_client.delete_network(network['id'])
+
+    @decorators.idempotent_id('0019de49-c0ea-4554-af57-18ad4ae30195')
+    def test_create_network_with_tenant_specific_network_segment_range(self):
+        # Creates a network segment range
+        network_segment_range = self._create_network_segment_range()
+        project_id = network_segment_range['project_id']
+        # Creates a set of networks
+        network_ids = []
+        for _ in range(TEST_SEGMENT_RANGE_MAXIMUM_ID -
+                       TEST_SEGMENT_RANGE_MINIMUM_ID + 1):
+            name = data_utils.rand_name('test_network_for_' + project_id)
+            network = self.create_network(
+                name, client=self.admin_client, project_id=project_id)
+
+            observed_network = self.admin_client.show_network(
+                network['id'])['network']
+            network_ids.append(network['id'])
+            self.assertTrue(
+                TEST_SEGMENT_RANGE_MINIMUM_ID <=
+                observed_network['provider:segmentation_id'] <=
+                TEST_SEGMENT_RANGE_MAXIMUM_ID)
+        # networks need to be deleted otherwise the range deletion will fail
+        # because the segments are in use (assigned to the networks created)
+        for network_id in network_ids:
+            self.admin_client.delete_network(network_id)
+
+    @decorators.idempotent_id('2129a26b-a97b-43d6-b0b2-04253c6046f8')
+    def test_create_network_with_default_network_segment_range(self):
+        # Creates a set of networks without creating a network segment range,
+        # i.e. using default network segment ranges only.
+        network_ids = []
+        for _ in range(5):
+            name = data_utils.rand_name('test_network')
+            network = self.create_network(name)
+
+            self.assertEqual(name, network['name'])
+
+            observed_network = self.admin_client.show_network(
+                network['id'])['network']
+            network_ids.append(network['id'])
+            self.assertEqual(name, observed_network['name'])
+            # default vxlan network segment range: 1-2000
+            self.assertTrue(
+                1 <= observed_network['provider:segmentation_id'] <= 2000)
+        # networks need to be deleted otherwise the range deletion will fail
+        # because the segments are in use (assigned to the networks created)
+        for network_id in network_ids:
+            self.admin_client.delete_network(network_id)
+
+    @decorators.idempotent_id('54fa26c9-37b5-4df4-a934-a705f29920fc')
+    def test_show_network_segment_range(self):
+        # Creates a network segment range
+        network_segment_range = self._create_network_segment_range()
+        # Verifies the details of a network segment range
+        body = self.admin_client.show_network_segment_range(
+            network_segment_range['id'])
+        observed_range = body['network_segment_range']
+        self.assertEqual(network_segment_range['id'], observed_range['id'])
+        self.assertEqual(network_segment_range['name'], observed_range['name'])
+        self.assertFalse(observed_range['default'])
+        self.assertFalse(observed_range['shared'])
+        self.assertEqual(network_segment_range['project_id'],
+                         observed_range['project_id'])
+        self.assertEqual(network_segment_range['network_type'],
+                         observed_range['network_type'])
+        self.assertEqual(network_segment_range['minimum'],
+                         observed_range['minimum'])
+        self.assertEqual(network_segment_range['maximum'],
+                         observed_range['maximum'])
+
+    @decorators.idempotent_id('17139cc1-4826-4bf9-9c39-85b74894d938')
+    def test_list_network_segment_ranges(self):
+        # Creates a network segment range
+        network_segment_range = self._create_network_segment_range()
+        # Verify network segment range lists
+        body = self.admin_client.list_network_segment_ranges(id=33)
+        list_range_ids = [r['id'] for r in body['network_segment_ranges']]
+        self.assertNotIn(network_segment_range['id'], list_range_ids)
+
+        body = self.admin_client.list_network_segment_ranges(
+            id=network_segment_range['id'])
+        list_range_ids = [r['id'] for r in body['network_segment_ranges']]
+        self.assertIn(network_segment_range['id'], list_range_ids)
+
+    @decorators.idempotent_id('42959544-9956-4b0c-aec6-d56533323924')
+    def test_delete_network_segment_range_failed_with_segment_referenced(
+            self):
+        # Creates a network segment range
+        network_segment_range = self._create_network_segment_range()
+        project_id = network_segment_range['project_id']
+        # Creates a network
+        name = data_utils.rand_name('test_network_for_' + project_id)
+        network = self.create_network(
+            name, client=self.admin_client, project_id=project_id)
+        # Deletes a network segment range
+        self.assertRaises(lib_exc.Conflict,
+                          self.admin_client.delete_network_segment_range,
+                          network_segment_range['id'])
+        # network needs to be deleted otherwise the range deletion will fail
+        # because the segment is in use (assigned to the network created)
+        self.admin_client.delete_network(network['id'])
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 2943198..7b91d94 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -135,6 +135,7 @@
         cls.reserved_subnet_cidrs = set()
         cls.keypairs = []
         cls.trunks = []
+        cls.network_segment_ranges = []
 
     @classmethod
     def resource_cleanup(cls):
@@ -244,6 +245,12 @@
             for keypair in cls.keypairs:
                 cls._try_delete_resource(cls.delete_keypair, keypair)
 
+            # Clean up network_segment_ranges
+            for network_segment_range in cls.network_segment_ranges:
+                cls._try_delete_resource(
+                    cls.admin_client.delete_network_segment_range,
+                    network_segment_range['id'])
+
         super(BaseNetworkTest, cls).resource_cleanup()
 
     @classmethod
@@ -913,6 +920,25 @@
         return metering_label_rule
 
     @classmethod
+    def create_network_segment_range(cls, name, shared,
+                                     project_id, network_type,
+                                     physical_network, minimum,
+                                     maximum):
+        """Wrapper utility that returns a test network segment range."""
+        network_segment_range_args = {'name': name,
+                                      'shared': shared,
+                                      'project_id': project_id,
+                                      'network_type': network_type,
+                                      'physical_network': physical_network,
+                                      'minimum': minimum,
+                                      'maximum': maximum}
+        body = cls.admin_client.create_network_segment_range(
+            **network_segment_range_args)
+        network_segment_range = body['network_segment_range']
+        cls.network_segment_ranges.append(network_segment_range)
+        return network_segment_range
+
+    @classmethod
     def create_flavor(cls, name, description, service_type):
         """Wrapper utility that returns a test flavor."""
         body = cls.admin_client.create_flavor(
diff --git a/neutron_tempest_plugin/api/test_security_groups.py b/neutron_tempest_plugin/api/test_security_groups.py
index b6d344d..d44ba50 100644
--- a/neutron_tempest_plugin/api/test_security_groups.py
+++ b/neutron_tempest_plugin/api/test_security_groups.py
@@ -16,6 +16,8 @@
 from neutron_lib import constants
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions
+import testtools
 
 from neutron_tempest_plugin.api import base
 from neutron_tempest_plugin.api import base_security_groups
@@ -113,3 +115,144 @@
     def test_security_group_rule_protocol_legacy_names(self):
         self._test_security_group_rule_protocols(
             protocols=self.protocol_legacy_names)
+
+
+class RbacSharedSecurityGroupTest(base.BaseAdminNetworkTest):
+
+    force_tenant_isolation = True
+    credentials = ['primary', 'alt', 'admin']
+    required_extensions = ['security-group', 'rbac-security-groups']
+
+    @classmethod
+    def resource_setup(cls):
+        super(RbacSharedSecurityGroupTest, cls).resource_setup()
+        cls.client2 = cls.os_alt.network_client
+
+    def _create_security_group(self):
+        return self.create_security_group(
+            name=data_utils.rand_name('test-sg'),
+            project={'id': self.admin_client.tenant_id})
+
+    def _make_admin_sg_shared_to_tenant_id(self, tenant_id):
+        sg = self._create_security_group()
+        rbac_policy = self.admin_client.create_rbac_policy(
+            object_type='security_group',
+            object_id=sg['id'],
+            action='access_as_shared',
+            target_tenant=tenant_id,
+        )['rbac_policy']
+        return {'security_group': sg, 'rbac_policy': rbac_policy}
+
+    @decorators.idempotent_id('2a41eb8f-2a35-11e9-bae9-acde48001122')
+    def test_policy_target_update(self):
+        res = self._make_admin_sg_shared_to_tenant_id(
+            self.client.tenant_id)
+        # change to client2
+        update_res = self.admin_client.update_rbac_policy(
+                res['rbac_policy']['id'], target_tenant=self.client2.tenant_id)
+        self.assertEqual(self.client2.tenant_id,
+                         update_res['rbac_policy']['target_tenant'])
+        # make sure everything else stayed the same
+        res['rbac_policy'].pop('target_tenant')
+        update_res['rbac_policy'].pop('target_tenant')
+        self.assertEqual(res['rbac_policy'], update_res['rbac_policy'])
+
+    @decorators.idempotent_id('2a619a8a-2a35-11e9-90d9-acde48001122')
+    def test_port_presence_prevents_policy_rbac_policy_deletion(self):
+        res = self._make_admin_sg_shared_to_tenant_id(
+            self.client2.tenant_id)
+        sg_id = res['security_group']['id']
+        net = self.create_network(client=self.client2)
+        port = self.client2.create_port(
+            network_id=net['id'],
+            security_groups=[sg_id])['port']
+
+        # a port with shared sg should prevent the deletion of an
+        # rbac-policy required for it to be shared
+        with testtools.ExpectedException(exceptions.Conflict):
+            self.admin_client.delete_rbac_policy(res['rbac_policy']['id'])
+
+        # cleanup
+        self.client2.delete_port(port['id'])
+        self.admin_client.delete_rbac_policy(res['rbac_policy']['id'])
+
+    @decorators.idempotent_id('2a81795c-2a35-11e9-9d86-acde48001122')
+    def test_regular_client_shares_to_another_regular_client(self):
+        # owned by self.admin_client
+        sg = self._create_security_group()
+        with testtools.ExpectedException(exceptions.NotFound):
+            self.client.show_security_group(sg['id'])
+        rbac_policy = self.admin_client.create_rbac_policy(
+            object_type='security_group', object_id=sg['id'],
+            action='access_as_shared',
+            target_tenant=self.client.tenant_id)['rbac_policy']
+        self.client.show_security_group(sg['id'])
+
+        self.assertIn(rbac_policy,
+                      self.admin_client.list_rbac_policies()['rbac_policies'])
+        # ensure that 'client2' can't see the rbac-policy sharing the
+        # sg to it because the rbac-policy belongs to 'client'
+        self.assertNotIn(rbac_policy['id'], [p['id'] for p in
+                          self.client2.list_rbac_policies()['rbac_policies']])
+
+    @decorators.idempotent_id('2a9fd480-2a35-11e9-9cb6-acde48001122')
+    def test_filter_fields(self):
+        sg = self._create_security_group()
+        self.admin_client.create_rbac_policy(
+            object_type='security_group', object_id=sg['id'],
+            action='access_as_shared', target_tenant=self.client2.tenant_id)
+        field_args = (('id',), ('id', 'action'), ('object_type', 'object_id'),
+                      ('tenant_id', 'target_tenant'))
+        for fields in field_args:
+            res = self.admin_client.list_rbac_policies(fields=fields)
+            self.assertEqual(set(fields), set(res['rbac_policies'][0].keys()))
+
+    @decorators.idempotent_id('2abf8f9e-2a35-11e9-85f7-acde48001122')
+    def test_rbac_policy_show(self):
+        res = self._make_admin_sg_shared_to_tenant_id(
+            self.client.tenant_id)
+        p1 = res['rbac_policy']
+        p2 = self.admin_client.create_rbac_policy(
+            object_type='security_group',
+            object_id=res['security_group']['id'],
+            action='access_as_shared',
+            target_tenant='*')['rbac_policy']
+
+        self.assertEqual(
+            p1, self.admin_client.show_rbac_policy(p1['id'])['rbac_policy'])
+        self.assertEqual(
+            p2, self.admin_client.show_rbac_policy(p2['id'])['rbac_policy'])
+
+    @decorators.idempotent_id('2adf6bd7-2a35-11e9-9c62-acde48001122')
+    def test_filter_rbac_policies(self):
+        sg = self._create_security_group()
+        rbac_pol1 = self.admin_client.create_rbac_policy(
+            object_type='security_group', object_id=sg['id'],
+            action='access_as_shared',
+            target_tenant=self.client2.tenant_id)['rbac_policy']
+        rbac_pol2 = self.admin_client.create_rbac_policy(
+            object_type='security_group', object_id=sg['id'],
+            action='access_as_shared',
+            target_tenant=self.admin_client.tenant_id)['rbac_policy']
+        res1 = self.admin_client.list_rbac_policies(id=rbac_pol1['id'])[
+            'rbac_policies']
+        res2 = self.admin_client.list_rbac_policies(id=rbac_pol2['id'])[
+            'rbac_policies']
+        self.assertEqual(1, len(res1))
+        self.assertEqual(1, len(res2))
+        self.assertEqual(rbac_pol1['id'], res1[0]['id'])
+        self.assertEqual(rbac_pol2['id'], res2[0]['id'])
+
+    @decorators.idempotent_id('2aff3900-2a35-11e9-96b3-acde48001122')
+    def test_regular_client_blocked_from_sharing_anothers_policy(self):
+        sg = self._make_admin_sg_shared_to_tenant_id(
+            self.client.tenant_id)['security_group']
+        with testtools.ExpectedException(exceptions.BadRequest):
+            self.client.create_rbac_policy(
+                object_type='security_group', object_id=sg['id'],
+                action='access_as_shared',
+                target_tenant=self.client2.tenant_id)
+
+        # make sure the rbac-policy is invisible to the tenant for which it's
+        # being shared
+        self.assertFalse(self.client.list_rbac_policies()['rbac_policies'])
diff --git a/requirements.txt b/requirements.txt
index dc77e63..bb836d1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@
 # process, which may cause wedges in the gate later.
 
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
-neutron-lib>=1.18.0 # Apache-2.0
+neutron-lib>=1.25.0 # Apache-2.0
 oslo.config>=5.2.0 # Apache-2.0
 ipaddress>=1.0.17;python_version<'3.3' # PSF
 netaddr>=0.7.18 # BSD