Merge "Enable CRUD for trunk ports"
diff --git a/neutron/tests/tempest/api/test_trunk.py b/neutron/tests/tempest/api/test_trunk.py
new file mode 100644
index 0000000..589acf9
--- /dev/null
+++ b/neutron/tests/tempest/api/test_trunk.py
@@ -0,0 +1,133 @@
+# 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))
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)