summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--heat/engine/resources/aws/launch_config.py105
-rw-r--r--heat/tests/test_autoscaling.py92
-rw-r--r--heat/tests/test_instance_group.py17
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)