Merge "Implement BlockDeviceMappings for LaunchConfiguration"
This commit is contained in:
commit
f31647d17b
|
@ -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 {
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue