Merge "Implement BlockDeviceMappings for LaunchConfiguration"

This commit is contained in:
Jenkins 2014-09-05 04:47:42 +00:00 committed by Gerrit Code Review
commit f31647d17b
3 changed files with 205 additions and 9 deletions

View File

@ -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 {

View File

@ -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'}

View File

@ -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)