Merge "Integration tests for tagged attach"
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 3bc1d0c..fa76770 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -354,6 +354,10 @@
.. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+ * `2.49`_
+
+ .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
+
* `2.54`_
.. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id4
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 975728c..b377c0c 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -501,7 +501,7 @@
# is already detached.
pass
- def attach_volume(self, server, volume, device=None):
+ def attach_volume(self, server, volume, device=None, tag=None):
"""Attaches volume to server and waits for 'in-use' volume status.
The volume will be detached when the test tears down.
@@ -510,10 +510,14 @@
:param volume: The volume to attach.
:param device: Optional mountpoint for the attached volume. Note that
this is not guaranteed for all hypervisors and is not recommended.
+ :param tag: Optional device role tag to apply to the volume.
"""
attach_kwargs = dict(volumeId=volume['id'])
if device:
attach_kwargs['device'] = device
+ if tag:
+ attach_kwargs['tag'] = tag
+
attachment = self.servers_client.attach_volume(
server['id'], **attach_kwargs)['volumeAttachment']
# On teardown detach the volume and wait for it to be available. This
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 1213a04..3a85a86 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -13,12 +13,14 @@
# under the License.
import json
+import time
from oslo_log import log as logging
from tempest.api.compute import base
from tempest.common import utils
from tempest.common.utils.linux import remote_client
+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
@@ -31,18 +33,11 @@
LOG = logging.getLogger(__name__)
-class DeviceTaggingTest(base.BaseV2ComputeTest):
-
- min_microversion = '2.32'
- # NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
- # bug in the 2.32 microversion, tags on block devices only worked with the
- # 2.32 microversion specifically. And tags on networks only worked between
- # 2.32 and 2.36 inclusive; the 2.37 microversion broke tags for networks.
- max_microversion = '2.32'
+class DeviceTaggingBase(base.BaseV2ComputeTest):
@classmethod
def skip_checks(cls):
- super(DeviceTaggingTest, cls).skip_checks()
+ super(DeviceTaggingBase, cls).skip_checks()
if not CONF.service_available.neutron:
raise cls.skipException('Neutron is required')
if not CONF.validation.run_validation:
@@ -54,7 +49,7 @@
@classmethod
def setup_clients(cls):
- super(DeviceTaggingTest, cls).setup_clients()
+ super(DeviceTaggingBase, cls).setup_clients()
cls.networks_client = cls.os_primary.networks_client
cls.ports_client = cls.os_primary.ports_client
cls.subnets_client = cls.os_primary.subnets_client
@@ -64,7 +59,55 @@
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True,
dhcp=True)
- super(DeviceTaggingTest, cls).setup_credentials()
+ super(DeviceTaggingBase, cls).setup_credentials()
+
+ def verify_metadata_from_api(self, server, ssh_client, verify_method):
+ md_url = 'http://169.254.169.254/openstack/latest/meta_data.json'
+ LOG.info('Attempting to verify tagged devices in server %s via '
+ 'the metadata service: %s', server['id'], md_url)
+
+ def get_and_verify_metadata():
+ try:
+ ssh_client.exec_command('curl -V')
+ except exceptions.SSHExecCommandFailed:
+ if not CONF.compute_feature_enabled.config_drive:
+ raise self.skipException('curl not found in guest '
+ 'and config drive is '
+ 'disabled')
+ LOG.warning('curl was not found in the guest, device '
+ 'tagging metadata was not checked in the '
+ 'metadata API')
+ return True
+ cmd = 'curl %s' % md_url
+ md_json = ssh_client.exec_command(cmd)
+ verify_method(md_json)
+ return True
+
+ if not test_utils.call_until_true(get_and_verify_metadata,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
+ raise exceptions.TimeoutException('Timeout while verifying '
+ 'metadata on server.')
+
+ def verify_metadata_on_config_drive(self, server, ssh_client,
+ verify_method):
+ LOG.info('Attempting to verify tagged devices in server %s via '
+ 'the config drive.', server['id'])
+ ssh_client.mount_config_drive()
+ cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
+ md_json = ssh_client.exec_command(cmd_md)
+ verify_method(md_json)
+ ssh_client.unmount_config_drive()
+
+
+class TaggedBootDevicesTest(DeviceTaggingBase):
+
+ min_microversion = '2.32'
+ # NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
+ # bug in the 2.32 microversion, tags on block devices only worked with the
+ # 2.32 microversion specifically. And tags on networks only worked between
+ # 2.32 and 2.36 inclusive; the 2.37 microversion broke tags for networks.
+ max_microversion = '2.32'
def verify_device_metadata(self, md_json):
md_dict = json.loads(md_json)
@@ -92,7 +135,7 @@
@decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
@utils.services('network', 'volume', 'image')
- def test_device_tagging(self):
+ def test_tagged_boot_devices(self):
# Create volumes
# The create_volume methods waits for the volumes to be available and
# the base class will clean them up on tearDown.
@@ -207,7 +250,7 @@
self.addCleanup(self.delete_server, server['id'])
server = self.servers_client.show_server(server['id'])['server']
- self.ssh_client = remote_client.RemoteClient(
+ ssh_client = remote_client.RemoteClient(
self.get_server_ip(server, validation_resources),
CONF.validation.image_ssh_user,
pkey=validation_resources['keypair']['private_key'],
@@ -230,46 +273,104 @@
self.assertTrue(self.net_2_100_mac)
self.assertTrue(self.net_2_200_mac)
- # Verify metadata from metadata service
+ # Verify metadata from metadata API
if CONF.compute_feature_enabled.metadata_service:
- md_url = 'http://169.254.169.254/openstack/latest/meta_data.json'
- LOG.info('Attempting to verify tagged devices in server %s via '
- 'the metadata service: %s', server['id'], md_url)
-
- def get_and_verify_metadata():
- try:
- self.ssh_client.exec_command('curl -V')
- except exceptions.SSHExecCommandFailed:
- if not CONF.compute_feature_enabled.config_drive:
- raise self.skipException('curl not found in guest '
- 'and config drive is '
- 'disabled')
- LOG.warning('curl was not found in the guest, device '
- 'tagging metadata was not checked in the '
- 'metadata API')
- return True
- cmd = 'curl %s' % md_url
- md_json = self.ssh_client.exec_command(cmd)
- self.verify_device_metadata(md_json)
- return True
-
- if not test_utils.call_until_true(get_and_verify_metadata,
- CONF.compute.build_timeout,
- CONF.compute.build_interval):
- raise exceptions.TimeoutException('Timeout while verifying '
- 'metadata on server.')
+ self.verify_metadata_from_api(server, ssh_client,
+ self.verify_device_metadata)
# Verify metadata on config drive
if CONF.compute_feature_enabled.config_drive:
- LOG.info('Attempting to verify tagged devices in server %s via '
- 'the config drive.', server['id'])
- self.ssh_client.mount_config_drive()
- cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
- md_json = self.ssh_client.exec_command(cmd_md)
- self.verify_device_metadata(md_json)
- self.ssh_client.unmount_config_drive()
+ self.verify_metadata_on_config_drive(server, ssh_client,
+ self.verify_device_metadata)
-class DeviceTaggingTestV2_42(DeviceTaggingTest):
+class TaggedBootDevicesTest_v242(TaggedBootDevicesTest):
min_microversion = '2.42'
max_microversion = 'latest'
+
+
+class TaggedAttachmentsTest(DeviceTaggingBase):
+
+ min_microversion = '2.49'
+ max_microversion = 'latest'
+
+ @classmethod
+ def skip_checks(cls):
+ super(TaggedAttachmentsTest, cls).skip_checks()
+ if not CONF.compute_feature_enabled.metadata_service:
+ raise cls.skipException('Metadata API must be enabled')
+
+ def verify_device_metadata(self, md_json):
+ md_dict = json.loads(md_json)
+ found_devices = [d['tags'][0] for d in md_dict['devices']]
+ self.assertItemsEqual(found_devices, ['nic-tag', 'volume-tag'])
+
+ def verify_empty_devices(self, md_json):
+ md_dict = json.loads(md_json)
+ self.assertEmpty(md_dict['devices'])
+
+ @decorators.idempotent_id('3e41c782-2a89-4922-a9d2-9a188c4e7c7c')
+ @utils.services('network', 'volume', 'image')
+ def test_tagged_attachment(self):
+ # Create network
+ net = self.networks_client.create_network(
+ name=data_utils.rand_name(
+ 'tagged-attachments-test-net'))['network']
+ self.addCleanup(self.networks_client.delete_network, net['id'])
+
+ # Create subnet
+ subnet = self.subnets_client.create_subnet(
+ network_id=net['id'],
+ cidr='10.10.10.0/24',
+ ip_version=4)['subnet']
+ self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+
+ # Create volume
+ volume = self.create_volume()
+
+ # Boot test server
+ config_drive_enabled = CONF.compute_feature_enabled.config_drive
+ validation_resources = self.get_test_validation_resources(
+ self.os_primary)
+
+ server = self.create_test_server(
+ validatable=True,
+ validation_resources=validation_resources,
+ config_drive=config_drive_enabled,
+ name=data_utils.rand_name('device-tagging-server'),
+ networks=[{'uuid': self.get_tenant_network()['id']}])
+ self.addCleanup(self.delete_server, server['id'])
+
+ # Attach tagged nic and volume
+ interface = self.interfaces_client.create_interface(
+ server['id'], net_id=net['id'],
+ tag='nic-tag')['interfaceAttachment']
+ self.attach_volume(server, volume, tag='volume-tag')
+
+ ssh_client = remote_client.RemoteClient(
+ self.get_server_ip(server, validation_resources),
+ CONF.validation.image_ssh_user,
+ pkey=validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
+
+ # NOTE(artom) The newly attached tagged nic won't appear in the
+ # metadata until the cache is refreshed. We wait 16 seconds since the
+ # default cache expiry is 15 seconds.
+ time.sleep(16)
+ self.verify_metadata_from_api(server, ssh_client,
+ self.verify_device_metadata)
+
+ # Detach tagged nic and volume
+ self.servers_client.detach_volume(server['id'], volume['id'])
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ self.interfaces_client.delete_interface(server['id'],
+ interface['port_id'])
+ waiters.wait_for_interface_detach(self.interfaces_client,
+ server['id'],
+ interface['port_id'])
+ # NOTE(artom) More waiting until metadata cache is refreshed.
+ time.sleep(16)
+ self.verify_metadata_from_api(server, ssh_client,
+ self.verify_empty_devices)