Merge "Remove unused LOG"
diff --git a/neutron/tests/tempest/api/base.py b/neutron/tests/tempest/api/base.py
index 2d836d6..84d91df 100644
--- a/neutron/tests/tempest/api/base.py
+++ b/neutron/tests/tempest/api/base.py
@@ -491,6 +491,8 @@
     # This should be defined by subclasses to reflect resource name to test
     resource = None
 
+    field = 'name'
+
     # NOTE(ihrachys): some names, like those starting with an underscore (_)
     # are sorted differently depending on whether the plugin implements native
     # sorting support, or not. So we avoid any such cases here, sticking to
@@ -510,7 +512,7 @@
         actual = list(actual)
         self.assertEqual(len(original), len(actual))
         for expected, res in zip(original, actual):
-            self.assertEqual(expected['name'], res['name'])
+            self.assertEqual(expected[self.field], res[self.field])
 
     @utils.classproperty
     def plural_name(self):
@@ -537,13 +539,13 @@
     def _test_list_sorts(self, direction):
         sort_args = {
             'sort_dir': direction,
-            'sort_key': 'name'
+            'sort_key': self.field
         }
         body = self.list_method(**sort_args)
         resources = self._extract_resources(body)
         self.assertNotEmpty(
             resources, "%s list returned is empty" % self.resource)
-        retrieved_names = [res['name'] for res in resources]
+        retrieved_names = [res[self.field] for res in resources]
         expected = sorted(retrieved_names)
         if direction == constants.SORT_DIRECTION_DESC:
             expected = list(reversed(expected))
@@ -580,7 +582,7 @@
         # first, collect all resources for later comparison
         sort_args = {
             'sort_dir': constants.SORT_DIRECTION_ASC,
-            'sort_key': 'name'
+            'sort_key': self.field
         }
         body = self.list_method(**sort_args)
         expected_resources = self._extract_resources(body)
@@ -666,7 +668,7 @@
             self, direction=constants.SORT_DIRECTION_ASC):
         pagination_args = {
             'sort_dir': direction,
-            'sort_key': 'name',
+            'sort_key': self.field,
         }
         body = self.list_method(**pagination_args)
         expected_resources = self._extract_resources(body)
@@ -709,7 +711,7 @@
     def _test_list_pagination_page_reverse(self, direction):
         pagination_args = {
             'sort_dir': direction,
-            'sort_key': 'name',
+            'sort_key': self.field,
             'limit': 3,
         }
         body = self.list_method(**pagination_args)
diff --git a/neutron/tests/tempest/api/clients.py b/neutron/tests/tempest/api/clients.py
index 8b51c6e..c6f41d0 100644
--- a/neutron/tests/tempest/api/clients.py
+++ b/neutron/tests/tempest/api/clients.py
@@ -15,8 +15,8 @@
 
 from tempest.lib.services.compute import keypairs_client
 from tempest.lib.services.compute import servers_client
+from tempest.lib.services.identity.v2 import tenants_client
 from tempest import manager
-from tempest.services.identity.v2.json import tenants_client
 
 from neutron.tests.tempest import config
 from neutron.tests.tempest.services.network.json import network_client
diff --git a/neutron/tests/tempest/api/test_network_ip_availability.py b/neutron/tests/tempest/api/test_network_ip_availability.py
index 74395ea..6a81128 100644
--- a/neutron/tests/tempest/api/test_network_ip_availability.py
+++ b/neutron/tests/tempest/api/test_network_ip_availability.py
@@ -86,7 +86,7 @@
             mask_bits = config.safe_get_config_value(
                 'network', 'project_network_v6_mask_bits')
 
-        subnet_cidr = cidr.subnet(mask_bits).next()
+        subnet_cidr = next(cidr.subnet(mask_bits))
         prefix_len = subnet_cidr.prefixlen
         subnet = self.create_subnet(network,
                                     cidr=subnet_cidr,
diff --git a/neutron/tests/tempest/api/test_trunk.py b/neutron/tests/tempest/api/test_trunk.py
new file mode 100644
index 0000000..640584a
--- /dev/null
+++ b/neutron/tests/tempest/api/test_trunk.py
@@ -0,0 +1,183 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+#    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 import exceptions as lib_exc
+from tempest import test
+
+from neutron.tests.tempest.api import base
+
+
+class TrunkTestJSONBase(base.BaseAdminNetworkTest):
+
+    def _create_trunk_with_network_and_parent(self, subports):
+        network = self.create_network()
+        parent_port = self.create_port(network)
+        return self.client.create_trunk(parent_port['id'], subports)
+
+
+class TrunkTestJSON(TrunkTestJSONBase):
+
+    @classmethod
+    @test.requires_ext(extension="trunk", service="network")
+    def resource_setup(cls):
+        super(TrunkTestJSON, cls).resource_setup()
+
+    def tearDown(self):
+        # NOTE(tidwellr) These tests create networks and ports, clean them up
+        # after each test to avoid hitting quota limits
+        self.resource_cleanup()
+        super(TrunkTestJSON, self).tearDown()
+
+    @test.idempotent_id('e1a6355c-4768-41f3-9bf8-0f1d192bd501')
+    def test_create_trunk_empty_subports_list(self):
+        trunk = self._create_trunk_with_network_and_parent([])
+        observed_trunk = self.client.show_trunk(trunk['trunk']['id'])
+        self.assertEqual(trunk, observed_trunk)
+
+    @test.idempotent_id('382dfa39-ca03-4bd3-9a1c-91e36d2e3796')
+    def test_create_trunk_subports_not_specified(self):
+        trunk = self._create_trunk_with_network_and_parent(None)
+        observed_trunk = self.client.show_trunk(trunk['trunk']['id'])
+        self.assertEqual(trunk, observed_trunk)
+
+    @test.idempotent_id('7de46c22-e2b6-4959-ac5a-0e624632ab32')
+    def test_create_show_delete_trunk(self):
+        trunk = self._create_trunk_with_network_and_parent(None)
+        trunk_id = trunk['trunk']['id']
+        parent_port_id = trunk['trunk']['port_id']
+        res = self.client.show_trunk(trunk_id)
+        self.assertEqual(trunk_id, res['trunk']['id'])
+        self.assertEqual(parent_port_id, res['trunk']['port_id'])
+        self.client.delete_trunk(trunk_id)
+        self.assertRaises(lib_exc.NotFound, self.client.show_trunk, trunk_id)
+
+    @test.idempotent_id('73365f73-bed6-42cd-960b-ec04e0c99d85')
+    def test_list_trunks(self):
+        trunk1 = self._create_trunk_with_network_and_parent(None)
+        trunk2 = self._create_trunk_with_network_and_parent(None)
+        expected_trunks = {trunk1['trunk']['id']: trunk1['trunk'],
+                           trunk2['trunk']['id']: trunk2['trunk']}
+        trunk_list = self.client.list_trunks()['trunks']
+        matched_trunks = [x for x in trunk_list if x['id'] in expected_trunks]
+        self.assertEqual(2, len(matched_trunks))
+        for trunk in matched_trunks:
+            self.assertEqual(expected_trunks[trunk['id']], trunk)
+
+    @test.idempotent_id('bb5fcead-09b5-484a-bbe6-46d1e06d6cc0')
+    def test_add_subport(self):
+        trunk = self._create_trunk_with_network_and_parent([])
+        network = self.create_network()
+        port = self.create_port(network)
+        subports = [{'port_id': port['id'],
+                     'segmentation_type': 'vlan',
+                     'segmentation_id': 2}]
+        self.client.add_subports(trunk['trunk']['id'], subports)
+        trunk = self.client.show_trunk(trunk['trunk']['id'])
+        observed_subports = trunk['trunk']['sub_ports']
+        self.assertEqual(1, len(observed_subports))
+        created_subport = observed_subports[0]
+        self.assertEqual(subports[0], created_subport)
+
+    @test.idempotent_id('96eea398-a03c-4c3e-a99e-864392c2ca53')
+    def test_remove_subport(self):
+        subport_parent1 = self.create_port(self.create_network())
+        subport_parent2 = self.create_port(self.create_network())
+        subports = [{'port_id': subport_parent1['id'],
+                     'segmentation_type': 'vlan',
+                     'segmentation_id': 2},
+                    {'port_id': subport_parent2['id'],
+                     'segmentation_type': 'vlan',
+                     'segmentation_id': 4}]
+        trunk = self._create_trunk_with_network_and_parent(subports)
+        removed_subport = trunk['trunk']['sub_ports'][0]
+        expected_subport = None
+
+        for subport in subports:
+            if subport['port_id'] != removed_subport['port_id']:
+                expected_subport = subport
+                break
+
+        # Remove the subport and validate PUT response
+        res = self.client.remove_subports(trunk['trunk']['id'],
+                                          [removed_subport])
+        self.assertEqual(1, len(res['sub_ports']))
+        self.assertEqual(expected_subport, res['sub_ports'][0])
+
+        # Validate the results of a subport list
+        trunk = self.client.show_trunk(trunk['trunk']['id'])
+        observed_subports = trunk['trunk']['sub_ports']
+        self.assertEqual(1, len(observed_subports))
+        self.assertEqual(expected_subport, observed_subports[0])
+
+    @test.idempotent_id('bb5fcaad-09b5-484a-dde6-4cd1ea6d6ff0')
+    def test_get_subports(self):
+        network = self.create_network()
+        port = self.create_port(network)
+        subports = [{'port_id': port['id'],
+                     'segmentation_type': 'vlan',
+                     'segmentation_id': 2}]
+        trunk = self._create_trunk_with_network_and_parent(subports)
+        trunk = self.client.get_subports(trunk['trunk']['id'])
+        observed_subports = trunk['sub_ports']
+        self.assertEqual(1, len(observed_subports))
+
+
+class TrunksSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+    resource = 'trunk'
+    field = 'id'
+
+    @classmethod
+    def resource_setup(cls):
+        super(TrunksSearchCriteriaTest, cls).resource_setup()
+        net = cls.create_network(network_name='trunk-search-test-net')
+        for name in cls.resource_names:
+            parent_port = cls.create_port(net)
+            cls.client.create_trunk(parent_port['id'], [])
+
+    @test.idempotent_id('fab73df4-960a-4ae3-87d3-60992b8d3e2d')
+    def test_list_sorts_asc(self):
+        self._test_list_sorts_asc()
+
+    @test.idempotent_id('a426671d-7270-430f-82ff-8f33eec93010')
+    def test_list_sorts_desc(self):
+        self._test_list_sorts_desc()
+
+    @test.idempotent_id('b202fdc8-6616-45df-b6a0-463932de6f94')
+    def test_list_pagination(self):
+        self._test_list_pagination()
+
+    @test.idempotent_id('c4723b8e-8186-4b9a-bf9e-57519967e048')
+    def test_list_pagination_with_marker(self):
+        self._test_list_pagination_with_marker()
+
+    @test.idempotent_id('dcd02a7a-f07e-4d5e-b0ca-b58e48927a9b')
+    def test_list_pagination_with_href_links(self):
+        self._test_list_pagination_with_href_links()
+
+    @test.idempotent_id('eafe7024-77ab-4cfe-824b-0b2bf4217727')
+    def test_list_no_pagination_limit_0(self):
+        self._test_list_no_pagination_limit_0()
+
+    @test.idempotent_id('f8857391-dc44-40cc-89b7-2800402e03ce')
+    def test_list_pagination_page_reverse_asc(self):
+        self._test_list_pagination_page_reverse_asc()
+
+    @test.idempotent_id('ae51e9c9-ceae-4ec0-afd4-147569247699')
+    def test_list_pagination_page_reverse_desc(self):
+        self._test_list_pagination_page_reverse_desc()
+
+    @test.idempotent_id('b4293e59-d794-4a93-be09-38667199ef68')
+    def test_list_pagination_page_reverse_with_href_links(self):
+        self._test_list_pagination_page_reverse_with_href_links()
diff --git a/neutron/tests/tempest/api/test_trunk_negative.py b/neutron/tests/tempest/api/test_trunk_negative.py
new file mode 100644
index 0000000..1cf37b7
--- /dev/null
+++ b/neutron/tests/tempest/api/test_trunk_negative.py
@@ -0,0 +1,190 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+#    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_utils import uuidutils
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron.tests.tempest.api import test_trunk
+
+
+class TrunkTestJSON(test_trunk.TrunkTestJSONBase):
+
+    def tearDown(self):
+        # NOTE(tidwellr) These tests create networks and ports, clean them up
+        # after each test to avoid hitting quota limits
+        self.resource_cleanup()
+        super(TrunkTestJSON, self).tearDown()
+
+    @classmethod
+    @test.requires_ext(extension="trunk", service="network")
+    def resource_setup(cls):
+        super(test_trunk.TrunkTestJSONBase, cls).resource_setup()
+
+    @test.attr(type='negative')
+    @test.idempotent_id('1b5cf87a-1d3a-4a94-ba64-647153d54f32')
+    def test_create_trunk_nonexistent_port_id(self):
+        self.assertRaises(lib_exc.NotFound, self.client.create_trunk,
+                          uuidutils.generate_uuid(), [])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('980bca3b-b0be-45ac-8067-b401e445b796')
+    def test_create_trunk_nonexistent_subport_port_id(self):
+        network = self.create_network()
+        parent_port = self.create_port(network)
+        self.assertRaises(lib_exc.NotFound, self.client.create_trunk,
+                          parent_port['id'],
+                          [{'port_id': uuidutils.generate_uuid(),
+                            'segmentation_type': 'vlan',
+                            'segmentation_id': 2}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('a5c5200a-72a0-43c5-a11a-52f808490344')
+    def test_create_subport_nonexistent_port_id(self):
+        trunk = self._create_trunk_with_network_and_parent([])
+        self.assertRaises(lib_exc.NotFound, self.client.add_subports,
+                          trunk['trunk']['id'],
+                          [{'port_id': uuidutils.generate_uuid(),
+                            'segmentation_type': 'vlan',
+                            'segmentation_id': 2}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('80deb6a9-da2a-48db-b7fd-bcef5b14edc1')
+    def test_create_subport_nonexistent_trunk(self):
+        network = self.create_network()
+        parent_port = self.create_port(network)
+        self.assertRaises(lib_exc.NotFound, self.client.add_subports,
+                          uuidutils.generate_uuid(),
+                          [{'port_id': parent_port['id'],
+                            'segmentation_type': 'vlan',
+                            'segmentation_id': 2}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('7e0f99ab-fe37-408b-a889-9e44ef300084')
+    def test_create_subport_missing_segmentation_id(self):
+        trunk = self._create_trunk_with_network_and_parent([])
+        subport_network = self.create_network()
+        parent_port = self.create_port(subport_network)
+        self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
+                          trunk['trunk']['id'],
+                          [{'port_id': parent_port['id'],
+                            'segmentation_type': 'vlan'}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('a315d78b-2f43-4efa-89ae-166044c568aa')
+    def test_create_trunk_with_subport_missing_segmentation_id(self):
+        subport_network = self.create_network()
+        parent_port = self.create_port(subport_network)
+        self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+                          parent_port['id'],
+                          [{'port_id': uuidutils.generate_uuid(),
+                            'segmentation_type': 'vlan'}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('33498618-f75a-4796-8ae6-93d4fd203fa4')
+    def test_create_trunk_with_subport_missing_segmentation_type(self):
+        subport_network = self.create_network()
+        parent_port = self.create_port(subport_network)
+        self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+                          parent_port['id'],
+                          [{'port_id': uuidutils.generate_uuid(),
+                            'segmentation_id': 3}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('a717691c-4e07-4d81-a98d-6f1c18c5d183')
+    def test_create_trunk_with_subport_missing_port_id(self):
+        subport_network = self.create_network()
+        parent_port = self.create_port(subport_network)
+        self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+                          parent_port['id'],
+                          [{'segmentation_type': 'vlan',
+                            'segmentation_id': 3}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('40aed9be-e976-47d0-a555-bde2c7e74e57')
+    def test_create_trunk_duplicate_subport_segmentation_ids(self):
+        trunk = self._create_trunk_with_network_and_parent([])
+        subport_network1 = self.create_network()
+        subport_network2 = self.create_network()
+        parent_port1 = self.create_port(subport_network1)
+        parent_port2 = self.create_port(subport_network2)
+        self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+                          trunk['trunk']['id'],
+                          [{'port_id': parent_port1['id'],
+                            'segmentation_id': 2,
+                            'segmentation_type': 'vlan'},
+                           {'port_id': parent_port2['id'],
+                            'segmentation_id': 2,
+                            'segmentation_type': 'vlan'}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('6f132ccc-1380-42d8-9c44-50411612bd01')
+    def test_add_subport_port_id_uses_trunk_port_id(self):
+        trunk = self._create_trunk_with_network_and_parent(None)
+        self.assertRaises(lib_exc.Conflict, self.client.add_subports,
+                          trunk['trunk']['id'],
+                          [{'port_id': trunk['trunk']['port_id'],
+                            'segmentation_type': 'vlan',
+                            'segmentation_id': 2}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('00cb40bb-1593-44c8-808c-72b47e64252f')
+    def test_add_subport_duplicate_segmentation_details(self):
+        trunk = self._create_trunk_with_network_and_parent(None)
+        network = self.create_network()
+        parent_port1 = self.create_port(network)
+        parent_port2 = self.create_port(network)
+        self.client.add_subports(trunk['trunk']['id'],
+                                 [{'port_id': parent_port1['id'],
+                                   'segmentation_type': 'vlan',
+                                   'segmentation_id': 2}])
+        self.assertRaises(lib_exc.Conflict, self.client.add_subports,
+                          trunk['trunk']['id'],
+                          [{'port_id': parent_port2['id'],
+                            'segmentation_type': 'vlan',
+                            'segmentation_id': 2}])
+
+    @test.attr(type='negative')
+    @test.idempotent_id('4eac8c25-83ee-4051-9620-34774f565730')
+    def test_add_subport_passing_dict(self):
+        trunk = self._create_trunk_with_network_and_parent(None)
+        self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
+                          trunk['trunk']['id'],
+                          {'port_id': trunk['trunk']['port_id'],
+                           'segmentation_type': 'vlan',
+                           'segmentation_id': 2})
+
+    @test.attr(type='negative')
+    @test.idempotent_id('17ca7dd7-96a8-445a-941e-53c0c86c2fe2')
+    def test_remove_subport_passing_dict(self):
+        network = self.create_network()
+        parent_port = self.create_port(network)
+        subport_data = {'port_id': parent_port['id'],
+                        'segmentation_type': 'vlan',
+                        'segmentation_id': 2}
+        trunk = self._create_trunk_with_network_and_parent([subport_data])
+        self.assertRaises(lib_exc.BadRequest, self.client.remove_subports,
+                          trunk['trunk']['id'], subport_data)
+
+    @test.attr(type='negative')
+    @test.idempotent_id('aaca7dd7-96b8-445a-931e-63f0d86d2fe2')
+    def test_remove_subport_not_found(self):
+        network = self.create_network()
+        parent_port = self.create_port(network)
+        subport_data = {'port_id': parent_port['id'],
+                        'segmentation_type': 'vlan',
+                        'segmentation_id': 2}
+        trunk = self._create_trunk_with_network_and_parent([])
+        self.assertRaises(lib_exc.NotFound, self.client.remove_subports,
+                          trunk['trunk']['id'], [subport_data])
diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py
index d9e333c..3220604 100644
--- a/neutron/tests/tempest/services/network/json/network_client.py
+++ b/neutron/tests/tempest/services/network/json/network_client.py
@@ -659,6 +659,64 @@
         body = jsonutils.loads(body)
         return service_client.ResponseBody(resp, body)
 
+    def create_trunk(self, parent_port_id, subports, tenant_id=None):
+        uri = '%s/trunks' % self.uri_prefix
+        post_data = {
+            'trunk': {
+                'port_id': parent_port_id,
+            }
+        }
+        if subports is not None:
+            post_data['trunk']['sub_ports'] = subports
+        if tenant_id is not None:
+            post_data['trunk']['tenant_id'] = tenant_id
+        resp, body = self.post(uri, self.serialize(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def show_trunk(self, trunk_id):
+        uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def list_trunks(self, **kwargs):
+        uri = '%s/trunks' % self.uri_prefix
+        if kwargs:
+            uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = self.deserialize_single(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_trunk(self, trunk_id):
+        uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def _subports_action(self, action, trunk_id, subports):
+        uri = '%s/trunks/%s/%s' % (self.uri_prefix, trunk_id, action)
+        resp, body = self.put(uri, jsonutils.dumps(subports))
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def add_subports(self, trunk_id, subports):
+        return self._subports_action('add_subports', trunk_id, subports)
+
+    def remove_subports(self, trunk_id, subports):
+        return self._subports_action('remove_subports', trunk_id, subports)
+
+    def get_subports(self, trunk_id):
+        uri = '%s/trunks/%s/%s' % (self.uri_prefix, trunk_id, 'get_subports')
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
     def get_auto_allocated_topology(self, tenant_id=None):
         uri = '%s/auto-allocated-topology/%s' % (self.uri_prefix, tenant_id)
         resp, body = self.get(uri)