From fc8f8e6a82de85e0d0e0230444d5aa8d662a46cb Mon Sep 17 00:00:00 2001 From: Artom Lifshitz Date: Wed, 13 Apr 2016 11:08:32 +0000 Subject: [PATCH] Integration tests for device role tagging This adds an API test that tests device role tagging. It boots an instance with tagged network interfaces and block devices and makes sure that the tags appear correctly in the metadata, both via the API and in the configdrive. Change-Id: Ica92b771fc04891ad231967470f8b0ade7d2dead Implements blueprint virt-device-role-tagging --- doc/source/microversion_testing.rst | 4 + tempest/api/compute/base.py | 10 +- .../compute/servers/test_device_tagging.py | 268 ++++++++++++++++++ 3 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 tempest/api/compute/servers/test_device_tagging.py diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst index dc73ef23a9..6b87b4dfe6 100644 --- a/doc/source/microversion_testing.rst +++ b/doc/source/microversion_testing.rst @@ -236,6 +236,10 @@ Microversion tests implemented in Tempest .. _2.25: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-mitaka + * `2.32`_ + + .. _2.32: http://docs.openstack.org/developer/nova/api_microversion_history.html#id29 + * `2.37`_ .. _2.37: http://docs.openstack.org/developer/nova/api_microversion_history.html#id34 diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py index d8294f7322..4fe8737d0b 100644 --- a/tempest/api/compute/base.py +++ b/tempest/api/compute/base.py @@ -373,14 +373,18 @@ class BaseV2ComputeTest(api_version_utils.BaseMicroversionTest, self.request_microversion)) @classmethod - def create_volume(cls): + def create_volume(cls, image_ref=None): """Create a volume and wait for it to become 'available'. + :param image_ref: Specify an image id to create a bootable volume. :returns: The available volume. """ vol_name = data_utils.rand_name(cls.__name__ + '-volume') - volume = cls.volumes_client.create_volume( - size=CONF.volume.volume_size, display_name=vol_name)['volume'] + create_params = dict(size=CONF.volume.volume_size, + display_name=vol_name) + if image_ref is not None: + create_params['imageRef'] = image_ref + volume = cls.volumes_client.create_volume(**create_params)['volume'] cls.volumes.append(volume) waiters.wait_for_volume_status(cls.volumes_client, volume['id'], 'available') diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py new file mode 100644 index 0000000000..9acc2b16a3 --- /dev/null +++ b/tempest/api/compute/servers/test_device_tagging.py @@ -0,0 +1,268 @@ +# Copyright (C) 2016, Red Hat, Inc. +# +# 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 json + +from oslo_log import log as logging + +from tempest.api.compute import base +from tempest.common.utils import data_utils +from tempest.common.utils.linux import remote_client +from tempest.common import waiters +from tempest import config +from tempest import exceptions +from tempest import test + + +CONF = config.CONF + +LOG = logging.getLogger(__name__) + + +class DeviceTaggingTest(base.BaseV2ComputeTest): + + min_microversion = '2.32' + max_microversion = 'latest' + + @classmethod + def skip_checks(cls): + super(DeviceTaggingTest, cls).skip_checks() + if not CONF.service_available.neutron: + raise cls.skipException('Neutron is required') + if not CONF.validation.run_validation: + raise cls.skipException('Validation must be enabled') + if (not CONF.compute_feature_enabled.config_drive + and not CONF.compute_feature_enabled.metadata_service): + raise cls.skipException('One of metadata or config drive must be ' + 'enabled') + + @classmethod + def setup_clients(cls): + super(DeviceTaggingTest, cls).setup_clients() + cls.networks_client = cls.os.networks_client + cls.ports_client = cls.os.ports_client + cls.volumes_client = cls.os.volumes_client + cls.subnets_client = cls.os.subnets_client + cls.routers_client = cls.os.routers_client + cls.interfaces_client = cls.os.interfaces_client + + @classmethod + def setup_credentials(cls): + cls.set_network_resources(network=True, subnet=True, router=True, + dhcp=True) + super(DeviceTaggingTest, cls).setup_credentials() + + @classmethod + def resource_setup(cls): + cls.set_validation_resources() + super(DeviceTaggingTest, cls).resource_setup() + + def verify_device_metadata(self, md_json): + md_dict = json.loads(md_json) + for d in md_dict['devices']: + if d['type'] == 'nic': + if d['mac'] == self.port1['mac_address']: + self.assertEqual(d['tags'], ['port-1']) + if d['mac'] == self.port2['mac_address']: + self.assertEqual(d['tags'], ['port-2']) + if d['mac'] == self.net_2_100_mac: + self.assertEqual(d['tags'], ['net-2-100']) + if d['mac'] == self.net_2_200_mac: + self.assertEqual(d['tags'], ['net-2-200']) + + found_devices = [d['tags'][0] for d in md_dict['devices']] + self.assertItemsEqual(found_devices, ['port-1', 'port-2', 'net-1', + 'net-2-100', 'net-2-200', + 'boot', 'other']) + + @test.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96') + @test.services('network', 'volume', 'image') + def test_device_tagging(self): + # Create volumes + # The create_volume methods waits for the volumes to be available and + # the base class will clean them up on tearDown. + boot_volume = self.create_volume(CONF.compute.image_ref) + other_volume = self.create_volume() + untagged_volume = self.create_volume() + + # Create networks + net1 = self.networks_client.create_network( + name=data_utils.rand_name('device-tagging-net1'))['network'] + self.addCleanup(self.networks_client.delete_network, net1['id']) + + net2 = self.networks_client.create_network( + name=data_utils.rand_name('device-tagging-net2'))['network'] + self.addCleanup(self.networks_client.delete_network, net2['id']) + + # Create subnets + subnet1 = self.subnets_client.create_subnet( + network_id=net1['id'], + cidr='10.1.1.0/24', + ip_version=4)['subnet'] + self.addCleanup(self.subnets_client.delete_subnet, subnet1['id']) + + subnet2 = self.subnets_client.create_subnet( + network_id=net2['id'], + cidr='10.2.2.0/24', + ip_version=4)['subnet'] + self.addCleanup(self.subnets_client.delete_subnet, subnet2['id']) + + # Create ports + self.port1 = self.ports_client.create_port( + network_id=net1['id'], + fixed_ips=[{'subnet_id': subnet1['id']}])['port'] + self.addCleanup(self.ports_client.delete_port, self.port1['id']) + + self.port2 = self.ports_client.create_port( + network_id=net1['id'], + fixed_ips=[{'subnet_id': subnet1['id']}])['port'] + self.addCleanup(self.ports_client.delete_port, self.port2['id']) + + # Create server + admin_pass = data_utils.rand_password() + config_drive_enabled = CONF.compute_feature_enabled.config_drive + + server = self.create_test_server( + validatable=True, + config_drive=config_drive_enabled, + adminPass=admin_pass, + name=data_utils.rand_name('device-tagging-server'), + networks=[ + # Validation network for ssh + { + 'uuid': self.get_tenant_network()['id'] + }, + # Different tags for different ports + { + 'port': self.port1['id'], + 'tag': 'port-1' + }, + { + 'port': self.port2['id'], + 'tag': 'port-2' + }, + # Two nics on same net, one tagged one not + { + 'uuid': net1['id'], + 'tag': 'net-1' + }, + { + 'uuid': net1['id'] + }, + # Two nics on same net, different IP + { + 'uuid': net2['id'], + 'fixed_ip': '10.2.2.100', + 'tag': 'net-2-100' + }, + { + 'uuid': net2['id'], + 'fixed_ip': '10.2.2.200', + 'tag': 'net-2-200' + } + ], + block_device_mapping_v2=[ + # Boot volume + { + 'uuid': boot_volume['id'], + 'source_type': 'volume', + 'destination_type': 'volume', + 'boot_index': 0, + 'tag': 'boot' + }, + # Other volume + { + 'uuid': other_volume['id'], + 'source_type': 'volume', + 'destination_type': 'volume', + 'boot_index': 1, + 'tag': 'other' + }, + # Untagged volume + { + 'uuid': untagged_volume['id'], + 'source_type': 'volume', + 'destination_type': 'volume', + 'boot_index': 2 + } + ]) + + self.addCleanup(waiters.wait_for_server_termination, + self.servers_client, server['id']) + self.addCleanup(self.servers_client.delete_server, server['id']) + + self.ssh_client = remote_client.RemoteClient( + self.get_server_ip(server), + CONF.validation.image_ssh_user, + admin_pass, + self.validation_resources['keypair']['private_key'], + server=server, + servers_client=self.servers_client) + + # Find the MAC addresses of our fixed IPs + self.net_2_100_mac = None + self.net_2_200_mac = None + ifaces = self.interfaces_client.list_interfaces(server['id']) + for iface in ifaces['interfaceAttachments']: + if 'fixed_ips' in iface: + for ip in iface['fixed_ips']: + if ip['ip_address'] == '10.2.2.100': + self.net_2_100_mac = iface['mac_addr'] + if ip['ip_address'] == '10.2.2.200': + self.net_2_200_mac = iface['mac_addr'] + # Make sure we have the MACs we need, there's no reason for some to be + # missing + self.assertTrue(self.net_2_100_mac) + self.assertTrue(self.net_2_200_mac) + + # Verify metadata from metadata service + 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.call_until_true(get_and_verify_metadata, + CONF.compute.build_timeout, + CONF.compute.build_interval): + raise exceptions.TimeoutException('Timeout while verifying ' + 'metadata on server.') + + # Verify metadata on config drive + if CONF.compute_feature_enabled.config_drive: + cmd_blkid = 'blkid -t LABEL=config-2 -o device' + LOG.info('Attempting to verify tagged devices in server %s via ' + 'the config drive.', server['id']) + dev_name = self.ssh_client.exec_command(cmd_blkid) + dev_name = dev_name.rstrip() + self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name) + 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)