# Copyright 2014 # The Cloudscaling Group, 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 math from oslo_log import log from tempest.lib.common.utils import data_utils from tempest.lib import decorators import testtools from ec2api.tests.functional import base from ec2api.tests.functional import config CONF = config.CONF LOG = log.getLogger(__name__) class EC2_EBSInstanceTuneBDM(base.EC2TestCase): """Test change root device attributes at instance launch.""" @classmethod @base.safe_setup def setUpClass(cls): super(EC2_EBSInstanceTuneBDM, cls).setUpClass() if not CONF.aws.ebs_image_id: raise cls.skipException('aws EBS image does not provided') cls.image_id = CONF.aws.ebs_image_id cls.zone = CONF.aws.aws_zone data = cls.client.describe_images(ImageIds=[cls.image_id]) assert 1 == len(data['Images']) image = data['Images'][0] cls.root_device_name = image['RootDeviceName'] bdm = image['BlockDeviceMappings'] bdm = [v for v in bdm if v['DeviceName'] == cls.root_device_name] assert 1 == len(bdm) ebs = bdm[0]['Ebs'] cls.root_device_size = ebs.get('VolumeSize') if not cls.root_device_size: snapshotId = ebs.get('SnapshotId') data = cls.client.describe_snapshots(SnapshotIds=[snapshotId]) assert 1 == len(data['Snapshots']) cls.root_device_size = data['Snapshots'][0]['VolumeSize'] @decorators.idempotent_id('2f51dd78-ff1e-494a-bcbc-f47580df17cb') def test_launch_ebs_instance_with_persistent_root_device(self): """ Launch EBS-backed instance with left root device after termination """ instance_id = self.run_instance(ImageId=self.image_id, BlockDeviceMappings=[{'DeviceName': self.root_device_name, 'Ebs': {'DeleteOnTermination': False}}]) bdt = self.get_instance_bdm(instance_id, self.root_device_name) self.assertIsNotNone(bdt) volume_id = bdt['Ebs'].get('VolumeId') res_clean_vol = self.addResourceCleanUp(self.client.delete_volume, VolumeId=volume_id) self.assertIsNotNone(volume_id) self.assertFalse(bdt['Ebs']['DeleteOnTermination']) data = self.client.describe_volumes(VolumeIds=[volume_id]) self.assertEqual(1, len(data['Volumes'])) self.client.terminate_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_delete(instance_id) self.get_volume_waiter().wait_available(volume_id) self.client.delete_volume(VolumeId=volume_id) self.cancelResourceCleanUp(res_clean_vol) self.get_volume_waiter().wait_delete(volume_id) @decorators.idempotent_id('0c820ed3-2e2f-4384-9649-cea907f00bf4') def test_launch_ebs_instance_with_resized_root_device(self): """Launch EBS-backed instance with resizing root device.""" new_size = int(math.ceil(self.root_device_size * 1.1)) instance_id = self.run_instance(ImageId=self.image_id, BlockDeviceMappings=[{'DeviceName': self.root_device_name, 'Ebs': {'VolumeSize': new_size}}]) bdt = self.get_instance_bdm(instance_id, self.root_device_name) self.assertIsNotNone(bdt) volume_id = bdt['Ebs'].get('VolumeId') self.assertIsNotNone(volume_id) self.assertTrue(bdt['Ebs']['DeleteOnTermination']) data = self.client.describe_volumes(VolumeIds=[volume_id]) self.assertEqual(1, len(data['Volumes'])) volume = data['Volumes'][0] self.assertEqual(new_size, volume['Size']) self.client.terminate_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_delete(instance_id) @decorators.idempotent_id('a0dbb3bd-167f-4f35-bb9d-aa53233e3123') def test_launch_ebs_instance_with_creating_blank_volume(self): """Launch instance with creating blank volume.""" device_name_prefix = base.get_device_name_prefix(self.root_device_name) device_name = device_name_prefix + 'b' instance_id = self.run_instance(ImageId=self.image_id, BlockDeviceMappings=[{'DeviceName': device_name, 'Ebs': {'VolumeSize': 1}}]) bdt = self.get_instance_bdm(instance_id, device_name) self.assertIsNotNone(bdt) volume_id = bdt['Ebs'].get('VolumeId') self.assertIsNotNone(volume_id) self.assertTrue(bdt['Ebs']['DeleteOnTermination']) data = self.client.describe_volumes(VolumeIds=[volume_id]) self.assertEqual(1, len(data['Volumes'])) volume = data['Volumes'][0] self.assertEqual(1, volume['Size']) self.client.terminate_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_delete(instance_id) class EC2_EBSInstanceAttaching(base.EC2TestCase): """ Launch instance with two attached volumes. One at first free slot (xxdb, other at some free slot (xxdh). Use full device name for the first and short device name for the second. Check used device names. Detach devices and reattach their back with same names. Check used device names again. Detach devices and attach their in next slots (xxdc and xxdi). First with full name, and second with short. Check useed device names. Sometimes this test case failed in AWS because volumes get attach state 'busy'. Then it's need to give a pause and rerun test case. Some dublicate tests are hidden to less output information. """ @classmethod @base.safe_setup def setUpClass(cls): super(EC2_EBSInstanceAttaching, cls).setUpClass() if not CONF.aws.run_incompatible_tests: raise cls.skipException('Decsribe returns full device name while ' 'we boot with short name.') if not CONF.aws.ebs_image_id: raise cls.skipException('aws EBS image does not provided') cls.image_id = CONF.aws.ebs_image_id cls.zone = CONF.aws.aws_zone data = cls.client.describe_images(ImageIds=[cls.image_id]) assert 1 == len(data['Images']) image = data['Images'][0] root_device_name = image['RootDeviceName'] device_name_prefix = base.get_device_name_prefix(root_device_name) cls.full_device_name_prefix = device_name_prefix cls.short_device_name_prefix = device_name_prefix[len("/dev/"):] data = cls.client.create_volume(AvailabilityZone=cls.zone, Size=1) cls.volume_id = data['VolumeId'] cls.addResourceCleanUpStatic(cls.client.delete_volume, VolumeId=cls.volume_id) cls.get_volume_waiter().wait_available(cls.volume_id) data = cls.client.create_snapshot(VolumeId=cls.volume_id) cls.snapshot_id = data['SnapshotId'] cls.addResourceCleanUpStatic(cls.client.delete_snapshot, SnapshotId=cls.snapshot_id) cls.get_snapshot_waiter().wait_available(cls.snapshot_id, final_set=('completed')) instance_type = CONF.aws.instance_type cls.device1_name = cls.full_device_name_prefix + "d" cls.device2_name = cls.short_device_name_prefix + "h" data = cls.client.run_instances( ImageId=cls.image_id, InstanceType=instance_type, Placement={'AvailabilityZone': cls.zone}, MinCount=1, MaxCount=1, BlockDeviceMappings=[{'DeviceName': cls.device1_name, 'Ebs': {'SnapshotId': cls.snapshot_id, 'DeleteOnTermination': True}}, {'DeviceName': cls.device2_name, 'Ebs': {'SnapshotId': cls.snapshot_id, 'DeleteOnTermination': True}}]) instance_id = data['Instances'][0]['InstanceId'] cls.instance_id = instance_id cls.addResourceCleanUpStatic(cls.client.terminate_instances, InstanceIds=[instance_id]) cls.get_instance_waiter().wait_available(instance_id, final_set=('running')) data = cls.client.describe_instances(InstanceIds=[instance_id]) assert 1 == len(data.get('Reservations', [])) instances = data['Reservations'][0].get('Instances', []) assert 1 == len(instances) instance = instances[0] bdms = instance['BlockDeviceMappings'] for bdt in bdms: if bdt['DeviceName'] == cls.device1_name: cls.volume_id1 = bdt['Ebs']['VolumeId'] if bdt['DeviceName'] == cls.device2_name: cls.volume_id2 = bdt['Ebs']['VolumeId'] assert cls.volume_id1 assert cls.volume_id2 @classmethod def tearDownClass(cls): super(EC2_EBSInstanceAttaching, cls).tearDownClass() # NOTE(andrey-mp): Amazon resets flag DeleteOnTermination after # reattaching volume, so we need delete them manually for volume_id in [cls.volume_id1, cls.volume_id2]: try: cls.cleanUpItem(cls.client.delete_volume, [], {'VolumeId': volume_id}) except BaseException: LOG.exception('EBSInstanceAttaching.tearDownClass failure') def _test_attaching(self, volume_id, device_name, device_prefix, new_device_name_letter): self.client.detach_volume(VolumeId=volume_id) clean_v = self.addResourceCleanUp(self.client.delete_volume, VolumeId=volume_id) self.get_volume_attachment_waiter().wait_delete(volume_id) bdt = self.get_instance_bdm(self.instance_id, device_name) self.assertIsNone(bdt) self.client.attach_volume(InstanceId=self.instance_id, VolumeId=volume_id, Device=device_name) self.cancelResourceCleanUp(clean_v) self.get_volume_attachment_waiter().wait_available( volume_id, final_set=('attached')) data = self.client.describe_volumes(VolumeIds=[volume_id]) self.assertEqual(1, len(data['Volumes'])) self.assertEqual('in-use', data['Volumes'][0]['State']) bdt = self.get_instance_bdm(self.instance_id, device_name) self.assertIsNotNone(bdt) self.client.detach_volume(VolumeId=volume_id) clean_v = self.addResourceCleanUp(self.client.delete_volume, VolumeId=volume_id) self.get_volume_attachment_waiter().wait_delete(volume_id) bdt = self.get_instance_bdm(self.instance_id, device_name) self.assertIsNone(bdt) new_device_name = device_prefix + new_device_name_letter self.client.attach_volume(InstanceId=self.instance_id, VolumeId=volume_id, Device=new_device_name) self.cancelResourceCleanUp(clean_v) self.get_volume_attachment_waiter().wait_available( volume_id, final_set=('attached')) data = self.client.describe_volumes(VolumeIds=[volume_id]) self.assertEqual(1, len(data['Volumes'])) self.assertEqual('in-use', data['Volumes'][0]['State']) bdt = self.get_instance_bdm(self.instance_id, new_device_name) self.assertIsNotNone(bdt) @decorators.idempotent_id('2176d935-5254-4e2a-9eb4-fc899f63c530') def test_attaching_by_full_name(self): """Attach and reattach device by full name.""" self._test_attaching(self.volume_id1, self.device1_name, self.full_device_name_prefix, "e") @decorators.idempotent_id('43af092e-3f04-45a7-bec7-8da39cde1f4c') def test_attaching_by_short_name(self): """Attach and reattach device by short name.""" self._test_attaching(self.volume_id2, self.device2_name, self.short_device_name_prefix, "i") class EC2_EBSInstanceSnapshot(base.EC2TestCase): """ Launch EBS-backed image, snapshot root device, register image, and launch another instance from the image (http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ instance-launch-snapshot.html) """ @classmethod @base.safe_setup def setUpClass(cls): super(EC2_EBSInstanceSnapshot, cls).setUpClass() if not CONF.aws.ebs_image_id: raise cls.skipException('aws EBS image does not provided') cls.image_id = CONF.aws.ebs_image_id cls.zone = CONF.aws.aws_zone @decorators.idempotent_id('07caac78-750c-48a1-975d-d3a6bd988108') def test_create_ebs_instance_snapshot(self): """Create snapshot of EBS-backed instance and check it.""" instance_id = self.run_instance(ImageId=self.image_id) instance = self.get_instance(instance_id) bdt = self.get_instance_bdm(instance_id, None) self.assertIsNotNone(bdt) volume_id = bdt['Ebs'].get('VolumeId') self.assertIsNotNone(volume_id) self.client.stop_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_available(instance_id, final_set=('stopped')) self.addResourceCleanUp(self.client.delete_volume, VolumeId=volume_id) data = self.client.create_snapshot(VolumeId=volume_id) snapshot_id = data['SnapshotId'] self.addResourceCleanUp(self.client.delete_snapshot, SnapshotId=snapshot_id) self.get_snapshot_waiter().wait_available(snapshot_id, final_set=('completed')) self.client.terminate_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_delete(instance_id) kwargs = { 'Name': data_utils.rand_name('ebs-ami'), 'RootDeviceName': instance['RootDeviceName'], 'BlockDeviceMappings': [{'DeviceName': instance['RootDeviceName'], 'Ebs': {'SnapshotId': snapshot_id, 'DeleteOnTermination': True}}] } if 'Architecture' in instance: kwargs['Architecture'] = instance['Architecture'] if 'KernelId' in instance: kwargs['KernelId'] = instance['KernelId'] if 'RamdiskId' in instance: kwargs['RamdiskId'] = instance['RamdiskId'] data = self.client.register_image(*[], **kwargs) image_id = data['ImageId'] clean_i = self.addResourceCleanUp(self.client.deregister_image, ImageId=image_id) self.get_image_waiter().wait_available(image_id) instance_id = self.run_instance(ImageId=image_id) # NOTE(andrey-mp): if instance will run then test will pass self.client.terminate_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_delete(instance_id) self.client.deregister_image(ImageId=image_id) self.cancelResourceCleanUp(clean_i) class EC2_EBSInstanceResizeRootDevice(base.EC2TestCase): """ Launch EBS-backed instance, stop instance, detach root volume, snapshot it, create volume from snapshot with increased size, attach new root volume, start instance (http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-expand-volume.html) """ @classmethod @base.safe_setup def setUpClass(cls): super(EC2_EBSInstanceResizeRootDevice, cls).setUpClass() if not CONF.aws.ebs_image_id: raise cls.skipException('aws EBS image does not provided') cls.image_id = CONF.aws.ebs_image_id cls.zone = CONF.aws.aws_zone @decorators.idempotent_id('0ea1dee6-c2c3-4cad-9676-5bf6e7ae54a8') @testtools.skipUnless( CONF.aws.run_incompatible_tests, "Error from nova: " "Unexpected Forbidden raised: Can't detach root device volume") def test_resize_root_ebs_device(self): """Resize root device of launched instance.""" clean_dict = dict() instance_id = self.run_instance(clean_dict=clean_dict, ImageId=self.image_id) res_clean = clean_dict['instance'] instance = self.get_instance(instance_id) bdt = self.get_instance_bdm(instance_id, None) self.assertIsNotNone(bdt) volume_id = bdt['Ebs'].get('VolumeId') self.assertIsNotNone(volume_id) self.client.stop_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_available(instance_id, final_set=('stopped')) self.client.detach_volume(VolumeId=volume_id) clean_v = self.addResourceCleanUp(self.client.delete_volume, VolumeId=volume_id) self.get_volume_attachment_waiter().wait_delete(volume_id) data = self.client.create_snapshot(VolumeId=volume_id) snapshot_id = data['SnapshotId'] clean_s = self.addResourceCleanUp(self.client.delete_snapshot, SnapshotId=snapshot_id) self.get_snapshot_waiter().wait_available(snapshot_id, final_set=('completed')) new_size = int(math.ceil(data['VolumeSize'] * 1.1)) data = self.client.create_volume(AvailabilityZone=self.zone, Size=new_size, SnapshotId=snapshot_id) volume_id2 = data['VolumeId'] clean_v2 = self.addResourceCleanUp(self.client.delete_volume, VolumeId=volume_id2) self.get_volume_waiter().wait_available(volume_id2) self.client.delete_snapshot(SnapshotId=snapshot_id) self.cancelResourceCleanUp(clean_s) self.client.delete_volume(VolumeId=volume_id) self.cancelResourceCleanUp(clean_v) self.get_volume_waiter().wait_delete(volume_id) self.client.attach_volume( InstanceId=instance_id, VolumeId=volume_id2, Device=instance['RootDeviceName']) self.get_volume_attachment_waiter().wait_available( volume_id2, final_set=('attached')) # NOTE(andrey-mp): move this cleanup operation to the end of trash # (it will remove first) self.cancelResourceCleanUp(res_clean) res_clean = self.addResourceCleanUp(self.client.terminate_instances, InstanceIds=[instance_id]) self.client.start_instances(InstanceIds=[instance_id]) self.get_instance_waiter().wait_available(instance_id, final_set=('running')) # NOTE(andrey-mp): if instance will run then test will pass self.client.terminate_instances(InstanceIds=[instance_id]) self.cancelResourceCleanUp(res_clean) self.get_instance_waiter().wait_delete(instance_id) self.client.delete_volume(VolumeId=volume_id2) self.cancelResourceCleanUp(clean_v2)