Add live migration with trunk test
Change-Id: I2d75fae81145b4bd1c0d38fabd785bc26835be15
Related-Bug: #1914747
Depends-On: https://review.opendev.org/c/openstack/neutron/+/774245
Depends-On: https://review.opendev.org/c/openstack/nova/+/775838
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 52ccea7..73b795f 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -23,6 +23,8 @@
from tempest.common import utils
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
CONF = config.CONF
@@ -55,6 +57,10 @@
def setup_clients(cls):
super(LiveMigrationTestBase, cls).setup_clients()
cls.admin_migration_client = cls.os_admin.migrations_client
+ cls.networks_client = cls.os_primary.networks_client
+ cls.subnets_client = cls.os_primary.subnets_client
+ cls.ports_client = cls.os_primary.ports_client
+ cls.trunks_client = cls.os_primary.trunks_client
def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
kwargs = dict()
@@ -197,6 +203,86 @@
self.assertEqual(volume_id1, volume_id2)
+ def _create_net_subnet(self, name, cidr):
+ net_name = data_utils.rand_name(name=name)
+ net = self.networks_client.create_network(name=net_name)['network']
+ self.addClassResourceCleanup(
+ self.networks_client.delete_network, net['id'])
+
+ subnet = self.subnets_client.create_subnet(
+ network_id=net['id'],
+ cidr=cidr,
+ ip_version=4)
+ self.addClassResourceCleanup(self.subnets_client.delete_subnet,
+ subnet['subnet']['id'])
+ return net
+
+ def _create_port(self, network_id, name):
+ name = data_utils.rand_name(name=name)
+ port = self.ports_client.create_port(name=name,
+ network_id=network_id)['port']
+ self.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.ports_client.delete_port,
+ port_id=port['id'])
+ return port
+
+ def _create_trunk_with_subport(self):
+ tenant_network = self.get_tenant_network()
+ parent = self._create_port(network_id=tenant_network['id'],
+ name='parent')
+ net = self._create_net_subnet(name='subport_net', cidr='19.80.0.0/24')
+ subport = self._create_port(network_id=net['id'], name='subport')
+
+ trunk = self.trunks_client.create_trunk(
+ name=data_utils.rand_name('trunk'),
+ port_id=parent['id'],
+ sub_ports=[{"segmentation_id": 42, "port_id": subport['id'],
+ "segmentation_type": "vlan"}]
+ )['trunk']
+ self.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.trunks_client.delete_trunk,
+ trunk['id'])
+ return trunk, parent, subport
+
+ def _is_port_status_active(self, port_id):
+ port = self.ports_client.show_port(port_id)['port']
+ return port['status'] == 'ACTIVE'
+
+ @decorators.idempotent_id('0022c12e-a482-42b0-be2d-396b5f0cffe3')
+ @utils.requires_ext(service='network', extension='trunk')
+ @utils.services('network')
+ def test_live_migration_with_trunk(self):
+ """Test live migration with trunk and subport"""
+ trunk, parent, subport = self._create_trunk_with_subport()
+
+ server = self.create_test_server(
+ wait_until="ACTIVE", networks=[{'port': parent['id']}])
+
+ # Wait till subport status is ACTIVE
+ self.assertTrue(
+ test_utils.call_until_true(
+ self._is_port_status_active, CONF.validation.connect_timeout,
+ 5, subport['id']))
+ parent = self.ports_client.show_port(parent['id'])['port']
+ self.assertEqual('ACTIVE', parent['status'])
+ subport = self.ports_client.show_port(subport['id'])['port']
+
+ if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+ # not to specify a host so that the scheduler will pick one
+ target_host = None
+ else:
+ target_host = self.get_host_other_than(server['id'])
+
+ self._live_migrate(server['id'], target_host, 'ACTIVE')
+
+ # Wait till subport status is ACTIVE
+ self.assertTrue(
+ test_utils.call_until_true(
+ self._is_port_status_active, CONF.validation.connect_timeout,
+ 5, subport['id']))
+ parent = self.ports_client.show_port(parent['id'])['port']
+ self.assertEqual('ACTIVE', parent['status'])
+
class LiveMigrationRemoteConsolesV26Test(LiveMigrationTestBase):
min_microversion = '2.6'
diff --git a/tempest/clients.py b/tempest/clients.py
index c4e00fe..6807fc4 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -72,6 +72,7 @@
self.qos_client = self.network.QosClient()
self.qos_min_bw_client = self.network.QosMinimumBandwidthRulesClient()
self.segments_client = self.network.SegmentsClient()
+ self.trunks_client = self.network.TrunksClient()
def _set_image_clients(self):
if CONF.service_available.glance:
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
index f7ac046..7e57499 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -36,6 +36,7 @@
from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
from tempest.lib.services.network.subnets_client import SubnetsClient
from tempest.lib.services.network.tags_client import TagsClient
+from tempest.lib.services.network.trunks_client import TrunksClient
from tempest.lib.services.network.versions_client import NetworkVersionsClient
__all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient',
@@ -44,4 +45,4 @@
'QosClient', 'QosMinimumBandwidthRulesClient', 'QuotasClient',
'RoutersClient', 'SecurityGroupRulesClient', 'SecurityGroupsClient',
'SegmentsClient', 'ServiceProvidersClient', 'SubnetpoolsClient',
- 'SubnetsClient', 'TagsClient']
+ 'SubnetsClient', 'TagsClient', 'TrunksClient']
diff --git a/tempest/lib/services/network/trunks_client.py b/tempest/lib/services/network/trunks_client.py
new file mode 100644
index 0000000..2fd9e01
--- /dev/null
+++ b/tempest/lib/services/network/trunks_client.py
@@ -0,0 +1,100 @@
+# 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.services.network import base
+
+
+class TrunksClient(base.BaseNetworkClient):
+
+ def create_trunk(self, **kwargs):
+ """Creates a trunk.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#create-trunk
+ """
+ uri = '/trunks'
+ post_data = {'trunk': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_trunk(self, trunk_id, **kwargs):
+ """Updates a trunk.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#update-trunk
+ """
+ uri = '/trunks/%s' % trunk_id
+ put_data = {'trunk': kwargs}
+ return self.update_resource(uri, put_data)
+
+ def show_trunk(self, trunk_id):
+ """Shows details for a trunk.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#show-trunk
+ """
+ uri = '/trunks/%s' % trunk_id
+ return self.show_resource(uri)
+
+ def delete_trunk(self, trunk_id):
+ """Deletes a trunk.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#delete-trunk
+ """
+ uri = '/trunks/%s' % trunk_id
+ return self.delete_resource(uri)
+
+ def list_trunks(self, **filters):
+ """Lists trunks to which the tenant has access.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#list-trunks
+ """
+ uri = '/trunks'
+ return self.list_resources(uri, **filters)
+
+ def add_subports_to_trunk(self, trunk_id, sub_ports):
+ """Add subports to a trunk.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#add-subports-to-trunk
+ """
+ uri = '/trunks/%s/add_subports' % trunk_id
+ put_data = {'sub_ports': sub_ports}
+ return self.update_resource(uri, put_data)
+
+ def delete_subports_from_trunk(self, trunk_id, sub_ports):
+ """Deletes subports from a trunk.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#delete-subports-from-trunk
+ """
+ uri = '/trunks/%s/remove_subports' % trunk_id
+ put_data = {'sub_ports': sub_ports}
+ return self.update_resource(uri, put_data)
+
+ def list_subports_of_trunk(self, trunk_id):
+ """List subports of a trunk.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/network/v2/index.html#list-subports-for-trunk
+ """
+ uri = '/trunks/%s/get_subports' % trunk_id
+ return self.list_resources(uri)
diff --git a/tempest/tests/lib/services/network/test_trunks_client.py b/tempest/tests/lib/services/network/test_trunks_client.py
new file mode 100644
index 0000000..b637d5e
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_trunks_client.py
@@ -0,0 +1,201 @@
+# 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 copy
+
+from tempest.lib.services.network import trunks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTrunksClient(base.BaseServiceTest):
+
+ FAKE_TRUNK_ID = "dfbc2103-93cf-4edf-952a-ef6deb32ddc6"
+ FAKE_PORT_ID = "1f04eb36-6c84-11eb-b0ab-4fc62961629d"
+ FAKE_TRUNKS = {
+ "trunks": [
+ {
+ "admin_state_up": True,
+ "description": "",
+ "id": "dfbc2103-93cf-4edf-952a-ef6deb32ddc6",
+ "name": "trunk0",
+ "port_id": "00130aab-bb51-42a1-a7c4-6703a3a43aa5",
+ "project_id": "",
+ "revision_number": 2,
+ "status": "DOWN",
+ "sub_ports": [
+ {
+ "port_id": "87d2483d-e5e6-483d-b5f0-81b9ed1d1a91",
+ "segmentation_id": 101,
+ "segmentation_type": "vlan"
+ }
+ ],
+ "tags": [],
+ },
+ {
+ "admin_state_up": True,
+ "description": "",
+ "id": "9eb0e72e-11d3-4295-bcaf-6c89008d9f0a",
+ "name": "trunk1",
+ "port_id": "035a12bf-2ae3-42ae-8ad6-9f70640cddde",
+ "project_id": "",
+ "revision_number": 2,
+ "status": "DOWN",
+ "sub_ports": [
+ {
+ "port_id": "cba839d5-02e2-4e09-b964-81356da78165",
+ "segmentation_id": 102,
+ "segmentation_type": "vlan"
+ }
+ ],
+ "tags": [],
+ },
+ ]
+ }
+
+ FAKE_TRUNK_1 = {
+ "name": "trunk0",
+ "port_id": "00130aab-bb51-42a1-a7c4-6703a3a43aa5"
+ }
+
+ def setUp(self):
+ super(TestTrunksClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.trunks_client = trunks_client.TrunksClient(
+ fake_auth, "network", "regionOne")
+
+ def _test_create_trunk(self, bytes_body=False):
+ self.check_service_client_function(
+ self.trunks_client.create_trunk,
+ "tempest.lib.common.rest_client.RestClient.post",
+ {"trunk": self.FAKE_TRUNKS["trunks"][0]},
+ bytes_body,
+ 201,
+ **self.FAKE_TRUNK_1)
+
+ def _test_list_trunks(self, bytes_body=False):
+ self.check_service_client_function(
+ self.trunks_client.list_trunks,
+ "tempest.lib.common.rest_client.RestClient.get",
+ self.FAKE_TRUNKS,
+ bytes_body,
+ 200)
+
+ def _test_show_trunk(self, bytes_body=False):
+ self.check_service_client_function(
+ self.trunks_client.show_trunk,
+ "tempest.lib.common.rest_client.RestClient.get",
+ {"trunk": self.FAKE_TRUNKS["trunks"][0]},
+ bytes_body,
+ 200,
+ trunk_id=self.FAKE_TRUNK_ID)
+
+ def _test_update_trunk(self, bytes_body=False):
+ update_kwargs = {
+ "admin_state_up": True,
+ "name": "new_trunk"
+ }
+
+ resp_body = {
+ "trunk": copy.deepcopy(
+ self.FAKE_TRUNKS["trunks"][0]
+ )
+ }
+ resp_body["trunk"].update(update_kwargs)
+
+ self.check_service_client_function(
+ self.trunks_client.update_trunk,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ trunk_id=self.FAKE_TRUNK_ID,
+ **update_kwargs)
+
+ def _test_add_subports_to_trunk(self, bytes_body=False):
+ sub_ports = [{
+ "port_id": "f04eb36-6c84-11eb-b0ab-4fc62961629d",
+ "segmentation_type": "vlan",
+ "segmentation_id": "1001"
+ }]
+ resp_body = copy.deepcopy(self.FAKE_TRUNKS["trunks"][0])
+
+ resp_body["sub_ports"].append(sub_ports)
+ self.check_service_client_function(
+ self.trunks_client.add_subports_to_trunk,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ trunk_id=self.FAKE_TRUNK_ID,
+ sub_ports=sub_ports)
+
+ def _test_delete_subports_from_trunk(self, bytes_body=False):
+ fake_sub_ports = self.FAKE_TRUNKS['trunks'][0]['sub_ports']
+ sub_ports = [
+ {"port_id": fake_sub_ports[0]['port_id']}
+ ]
+ resp_body = copy.deepcopy(self.FAKE_TRUNKS["trunks"][0])
+
+ resp_body['sub_ports'] = []
+ self.check_service_client_function(
+ self.trunks_client.delete_subports_from_trunk,
+ "tempest.lib.common.rest_client.RestClient.put",
+ resp_body,
+ bytes_body,
+ 200,
+ trunk_id=self.FAKE_TRUNK_ID,
+ sub_ports=sub_ports)
+
+ def test_create_trunk_with_str_body(self):
+ self._test_create_trunk()
+
+ def test_create_trunk_with_bytes_body(self):
+ self._test_create_trunk(bytes_body=True)
+
+ def test_list_trunks_with_str_body(self):
+ self._test_list_trunks()
+
+ def test_list_trunks_with_bytes_body(self):
+ self._test_list_trunks(bytes_body=True)
+
+ def test_show_trunk_with_str_body(self):
+ self._test_show_trunk()
+
+ def test_show_trunk_with_bytes_body(self):
+ self._test_show_trunk(bytes_body=True)
+
+ def test_update_trunk_with_str_body(self):
+ self._test_update_trunk()
+
+ def test_update_trunk_with_bytes_body(self):
+ self._test_update_trunk(bytes_body=True)
+
+ def test_add_subports_to_trunk_str_body(self):
+ self._test_add_subports_to_trunk()
+
+ def test_add_subports_to_trunk_bytes_body(self):
+ self._test_add_subports_to_trunk(bytes_body=True)
+
+ def test_delete_subports_from_trunk_str_body(self):
+ self._test_delete_subports_from_trunk()
+
+ def test_delete_subports_from_trunk_bytes_body(self):
+ self._test_delete_subports_from_trunk(bytes_body=True)
+
+ def test_delete_trunk(self):
+ self.check_service_client_function(
+ self.trunks_client.delete_trunk,
+ "tempest.lib.common.rest_client.RestClient.delete",
+ {},
+ status=204,
+ trunk_id=self.FAKE_TRUNK_ID)
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 5a14430..b83eb34 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -302,6 +302,7 @@
devstack_services:
neutron-placement: true
neutron-qos: true
+ neutron-trunk: true
group-vars:
subnode:
devstack_localrc: