diff options
-rw-r--r-- | heat/engine/resources/aws/launch_config.py | 105 | ||||
-rw-r--r-- | heat/tests/test_autoscaling.py | 92 | ||||
-rw-r--r-- | heat/tests/test_instance_group.py | 17 |
3 files changed, 205 insertions, 9 deletions
diff --git a/heat/engine/resources/aws/launch_config.py b/heat/engine/resources/aws/launch_config.py index 3a1bf0f14..4b3dad170 100644 --- a/heat/engine/resources/aws/launch_config.py +++ b/heat/engine/resources/aws/launch_config.py @@ -12,14 +12,11 @@ # under the License. +from heat.common import exception from heat.engine import constraints from heat.engine import properties from heat.engine import resource -from heat.openstack.common import log as logging - -LOG = logging.getLogger(__name__) - class LaunchConfiguration(resource.Resource): @@ -37,6 +34,20 @@ class LaunchConfiguration(resource.Resource): 'Key', 'Value', ) + _BLOCK_DEVICE_MAPPING_KEYS = ( + DEVICE_NAME, EBS, NO_DEVICE, VIRTUAL_NAME, + ) = ( + 'DeviceName', 'Ebs', 'NoDevice', 'VirtualName', + ) + + _EBS_KEYS = ( + DELETE_ON_TERMINATION, IOPS, SNAPSHOT_ID, VOLUME_SIZE, + VOLUME_TYPE, + ) = ( + 'DeleteOnTermination', 'Iops', 'SnapshotId', 'VolumeSize', + 'VolumeType' + ) + properties_schema = { IMAGE_ID: properties.Schema( properties.Schema.STRING, @@ -77,9 +88,68 @@ class LaunchConfiguration(resource.Resource): implemented=False ), BLOCK_DEVICE_MAPPINGS: properties.Schema( - properties.Schema.STRING, - _('Not Implemented.'), - implemented=False + properties.Schema.LIST, + _('Block device mappings to attach to instance.'), + schema=properties.Schema( + properties.Schema.MAP, + schema={ + DEVICE_NAME: properties.Schema( + properties.Schema.STRING, + _('A device name where the volume will be ' + 'attached in the system at /dev/device_name.' + 'e.g. vdb'), + required=True, + ), + EBS: properties.Schema( + properties.Schema.MAP, + _('The ebs volume to attach to the instance.'), + schema={ + DELETE_ON_TERMINATION: properties.Schema( + properties.Schema.BOOLEAN, + _('Indicate whether the volume should be ' + 'deleted when the instance is terminated.'), + default=True + ), + IOPS: properties.Schema( + properties.Schema.NUMBER, + _('The number of I/O operations per second ' + 'that the volume supports.'), + implemented=False + ), + SNAPSHOT_ID: properties.Schema( + properties.Schema.STRING, + _('The ID of the snapshot to create ' + 'a volume from.'), + ), + VOLUME_SIZE: properties.Schema( + properties.Schema.STRING, + _('The size of the volume, in GB. Must be ' + 'equal or greater than the size of the ' + 'snapshot. It is safe to leave this blank ' + 'and have the Compute service infer ' + 'the size.'), + ), + VOLUME_TYPE: properties.Schema( + properties.Schema.STRING, + _('The volume type.'), + implemented=False + ), + }, + ), + NO_DEVICE: properties.Schema( + properties.Schema.MAP, + _('The can be used to unmap a defined device.'), + implemented=False + ), + VIRTUAL_NAME: properties.Schema( + properties.Schema.STRING, + _('The name of the virtual device. The name must be ' + 'in the form ephemeralX where X is a number ' + 'starting from zero (0); for example, ephemeral0.'), + implemented=False + ), + }, + ), ), NOVA_SCHEDULER_HINTS: properties.Schema( properties.Schema.LIST, @@ -107,6 +177,27 @@ class LaunchConfiguration(resource.Resource): def FnGetRefId(self): return self.physical_resource_name_or_FnGetRefId() + def validate(self): + ''' + Validate any of the provided params + ''' + super(LaunchConfiguration, self).validate() + # now we don't support without snapshot_id in bdm + bdm = self.properties.get(self.BLOCK_DEVICE_MAPPINGS) + if bdm: + for mapping in bdm: + ebs = mapping.get(self.EBS) + if ebs: + snapshot_id = ebs.get(self.SNAPSHOT_ID) + if not snapshot_id: + msg = _("SnapshotId is missing, this is required " + "when specifying BlockDeviceMappings.") + raise exception.StackValidationFailed(message=msg) + else: + msg = _("Ebs is missing, this is required " + "when specifying BlockDeviceMappings.") + raise exception.StackValidationFailed(message=msg) + def resource_mapping(): return { diff --git a/heat/tests/test_autoscaling.py b/heat/tests/test_autoscaling.py index 1a843ff16..9674278f5 100644 --- a/heat/tests/test_autoscaling.py +++ b/heat/tests/test_autoscaling.py @@ -89,6 +89,12 @@ as_template = ''' "Properties": { "ImageId" : {"Ref": "ImageId"}, "InstanceType" : "bar", + "BlockDeviceMappings": [ + { + "DeviceName": "vdb", + "Ebs": {"SnapshotId": "9ef5496e-7426-446a-bbc8-01f84d9c9972", + "DeleteOnTermination": "True"} + }] } } } @@ -133,12 +139,19 @@ class AutoScalingTest(HeatTestCase): self.assertIsNone(conf.validate()) scheduler.TaskRunner(conf.create)() self.assertEqual((conf.CREATE, conf.COMPLETE), conf.state) + # check bdm in configuration + self.assertIsNotNone(conf.properties['BlockDeviceMappings']) # create the group resource rsrc = stack[resource_name] self.assertIsNone(rsrc.validate()) scheduler.TaskRunner(rsrc.create)() self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + # check bdm in instance_definition + instance_definition = rsrc._get_instance_definition() + self.assertIn('BlockDeviceMappings', + instance_definition['Properties']) + return rsrc def create_scaling_policy(self, t, stack, resource_name): @@ -1738,6 +1751,85 @@ class AutoScalingTest(HeatTestCase): self.assertIsNone(rsrc.resource_id) self.assertEqual('LaunchConfig', rsrc.FnGetRefId()) + def test_validate_BlockDeviceMappings_VolumeSize_invalid_str(self): + t = template_format.parse(as_template) + lcp = t['Resources']['LaunchConfig']['Properties'] + bdm = [{'DeviceName': 'vdb', + 'Ebs': {'SnapshotId': '1234', + 'VolumeSize': 10}}] + lcp['BlockDeviceMappings'] = bdm + stack = utils.parse_stack(t, params=self.params) + self.stub_ImageConstraint_validate() + self.m.ReplayAll() + + e = self.assertRaises(exception.StackValidationFailed, + self.create_scaling_group, t, + stack, 'LaunchConfig') + + expected_msg = "Value must be a string" + self.assertIn(expected_msg, six.text_type(e)) + + self.m.VerifyAll() + + def test_validate_BlockDeviceMappings_without_Ebs_property(self): + t = template_format.parse(as_template) + lcp = t['Resources']['LaunchConfig']['Properties'] + bdm = [{'DeviceName': 'vdb'}] + lcp['BlockDeviceMappings'] = bdm + stack = utils.parse_stack(t, params=self.params) + + self.stub_ImageConstraint_validate() + self.m.ReplayAll() + + e = self.assertRaises(exception.StackValidationFailed, + self.create_scaling_group, t, + stack, 'LaunchConfig') + + self.assertIn("Ebs is missing, this is required", + six.text_type(e)) + + self.m.VerifyAll() + + def test_validate_BlockDeviceMappings_without_SnapshotId_property(self): + t = template_format.parse(as_template) + lcp = t['Resources']['LaunchConfig']['Properties'] + bdm = [{'DeviceName': 'vdb', + 'Ebs': {'VolumeSize': '1'}}] + lcp['BlockDeviceMappings'] = bdm + stack = utils.parse_stack(t, params=self.params) + + self.stub_ImageConstraint_validate() + self.m.ReplayAll() + + e = self.assertRaises(exception.StackValidationFailed, + self.create_scaling_group, t, + stack, 'LaunchConfig') + + self.assertIn("SnapshotId is missing, this is required", + six.text_type(e)) + self.m.VerifyAll() + + def test_validate_BlockDeviceMappings_without_DeviceName_property(self): + t = template_format.parse(as_template) + lcp = t['Resources']['LaunchConfig']['Properties'] + bdm = [{'Ebs': {'SnapshotId': '1234', + 'VolumeSize': '1'}}] + lcp['BlockDeviceMappings'] = bdm + stack = utils.parse_stack(t, params=self.params) + self.stub_ImageConstraint_validate() + self.m.ReplayAll() + + e = self.assertRaises(exception.StackValidationFailed, + self.create_scaling_group, t, + stack, 'LaunchConfig') + + excepted_error = ('Property error : LaunchConfig: BlockDeviceMappings ' + 'Property error : BlockDeviceMappings: 0 Property ' + 'error : 0: Property DeviceName not assigned') + self.assertIn(excepted_error, six.text_type(e)) + + self.m.VerifyAll() + class TestInstanceGroup(HeatTestCase): params = {'KeyName': 'test', 'ImageId': 'foo'} diff --git a/heat/tests/test_instance_group.py b/heat/tests/test_instance_group.py index fda07f93b..7dd108a38 100644 --- a/heat/tests/test_instance_group.py +++ b/heat/tests/test_instance_group.py @@ -48,7 +48,13 @@ ig_template = ''' "InstanceType" : "m1.large", "KeyName" : "test", "SecurityGroups" : [ "sg-1" ], - "UserData" : "jsconfig data" + "UserData" : "jsconfig data", + "BlockDeviceMappings": [ + { + "DeviceName": "vdb", + "Ebs": {"SnapshotId": "9ef5496e-7426-446a-bbc8-01f84d9c9972", + "DeleteOnTermination": "True"} + }] } } } @@ -102,11 +108,18 @@ class InstanceGroupTest(HeatTestCase): instance.Instance.FnGetAtt('PublicIp').AndReturn('1.2.3.4') self.m.ReplayAll() - self.create_resource(t, stack, 'JobServerConfig') + lc_rsrc = self.create_resource(t, stack, 'JobServerConfig') + # check bdm in configuration + self.assertIsNotNone(lc_rsrc.properties['BlockDeviceMappings']) + rsrc = self.create_resource(t, stack, 'JobServerGroup') self.assertEqual(utils.PhysName(stack.name, rsrc.name), rsrc.FnGetRefId()) self.assertEqual('1.2.3.4', rsrc.FnGetAtt('InstanceList')) + # check bdm in instance_definition + instance_definition = rsrc._get_instance_definition() + self.assertIn('BlockDeviceMappings', + instance_definition['Properties']) nested = rsrc.nested() self.assertEqual(nested.id, rsrc.resource_id) |