Merge "Implement 'InstanceId' for LaunchConfiguration"
This commit is contained in:
commit
2c35c47dce
@ -12,9 +12,12 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import constraints
|
||||
from heat.engine import function
|
||||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
|
||||
@ -24,9 +27,11 @@ class LaunchConfiguration(resource.Resource):
|
||||
PROPERTIES = (
|
||||
IMAGE_ID, INSTANCE_TYPE, KEY_NAME, USER_DATA, SECURITY_GROUPS,
|
||||
KERNEL_ID, RAM_DISK_ID, BLOCK_DEVICE_MAPPINGS, NOVA_SCHEDULER_HINTS,
|
||||
INSTANCE_ID,
|
||||
) = (
|
||||
'ImageId', 'InstanceType', 'KeyName', 'UserData', 'SecurityGroups',
|
||||
'KernelId', 'RamDiskId', 'BlockDeviceMappings', 'NovaSchedulerHints',
|
||||
'InstanceId',
|
||||
)
|
||||
|
||||
_NOVA_SCHEDULER_HINT_KEYS = (
|
||||
@ -53,7 +58,6 @@ class LaunchConfiguration(resource.Resource):
|
||||
IMAGE_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Glance image ID or name.'),
|
||||
required=True,
|
||||
constraints=[
|
||||
constraints.CustomConstraint('glance.image')
|
||||
]
|
||||
@ -61,11 +65,19 @@ class LaunchConfiguration(resource.Resource):
|
||||
INSTANCE_TYPE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Nova instance type (flavor).'),
|
||||
required=True,
|
||||
constraints=[
|
||||
constraints.CustomConstraint('nova.flavor')
|
||||
]
|
||||
),
|
||||
INSTANCE_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('The ID of an existing instance you want to use to create '
|
||||
'the launch configuration. All properties are derived from '
|
||||
'the instance with the exception of BlockDeviceMapping.'),
|
||||
constraints=[
|
||||
constraints.CustomConstraint("nova.server")
|
||||
]
|
||||
),
|
||||
KEY_NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Optional Nova keypair name.'),
|
||||
@ -178,6 +190,32 @@ class LaunchConfiguration(resource.Resource):
|
||||
),
|
||||
}
|
||||
|
||||
def rebuild_lc_properties(self, instance_id):
|
||||
server = self.client_plugin('nova').get_server(instance_id)
|
||||
instance_props = {
|
||||
self.IMAGE_ID: server.image['id'],
|
||||
self.INSTANCE_TYPE: server.flavor['id'],
|
||||
self.KEY_NAME: server.key_name,
|
||||
self.SECURITY_GROUPS: [sg['name']
|
||||
for sg in server.security_groups]
|
||||
}
|
||||
lc_props = function.resolve(self.properties.data)
|
||||
for key, value in six.iteritems(instance_props):
|
||||
# the properties which are specified in launch configuration,
|
||||
# will override the attributes from the instance
|
||||
lc_props.setdefault(key, value)
|
||||
|
||||
return lc_props
|
||||
|
||||
def handle_create(self):
|
||||
instance_id = self.properties.get(self.INSTANCE_ID)
|
||||
if instance_id:
|
||||
lc_props = self.rebuild_lc_properties(instance_id)
|
||||
defn = self.t.freeze(properties=lc_props)
|
||||
self.properties = defn.properties(
|
||||
self.properties_schema, self.context)
|
||||
self._update_stored_properties()
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
if 'Metadata' in tmpl_diff:
|
||||
raise resource.UpdateReplace(self.name)
|
||||
@ -205,6 +243,16 @@ class LaunchConfiguration(resource.Resource):
|
||||
msg = _("Ebs is missing, this is required "
|
||||
"when specifying BlockDeviceMappings.")
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
# validate the 'InstanceId', 'ImageId' and 'InstanceType',
|
||||
# if without 'InstanceId', 'ImageId' and 'InstanceType' are required
|
||||
instance_id = self.properties.get(self.INSTANCE_ID)
|
||||
if not instance_id:
|
||||
image_id = self.properties.get(self.IMAGE_ID)
|
||||
instance_type = self.properties.get(self.INSTANCE_TYPE)
|
||||
if not image_id or not instance_type:
|
||||
msg = _('If without InstanceId, '
|
||||
'ImageId and InstanceType are required.')
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
|
@ -238,9 +238,15 @@ class InstanceGroup(stack_resource.StackResource):
|
||||
def _get_conf_properties(self):
|
||||
conf_refid = self.properties[self.LAUNCH_CONFIGURATION_NAME]
|
||||
conf = self.stack.resource_by_refid(conf_refid)
|
||||
|
||||
props = function.resolve(conf.properties.data)
|
||||
if 'InstanceId' in props:
|
||||
props = conf.rebuild_lc_properties(props['InstanceId'])
|
||||
|
||||
props['Tags'] = self._tags()
|
||||
# if the launch configuration is created from an existing instance.
|
||||
# delete the 'InstanceId' property
|
||||
props.pop('InstanceId', None)
|
||||
|
||||
return conf, props
|
||||
|
||||
def _get_instance_definition(self):
|
||||
|
@ -11,12 +11,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import short_id
|
||||
from heat.common import template_format
|
||||
from heat.engine.clients.os import nova
|
||||
from heat.engine import scheduler
|
||||
from heat.tests.autoscaling import inline_templates
|
||||
from heat.tests import common
|
||||
@ -63,6 +65,81 @@ class LaunchConfigurationTest(common.HeatTestCase):
|
||||
self.assertIsNone(rsrc.resource_id)
|
||||
self.assertEqual('LaunchConfig', rsrc.FnGetRefId())
|
||||
|
||||
def test_launch_config_create_with_instanceid(self):
|
||||
t = template_format.parse(inline_templates.as_template)
|
||||
lcp = t['Resources']['LaunchConfig']['Properties']
|
||||
lcp['InstanceId'] = '5678'
|
||||
stack = utils.parse_stack(t, params=inline_templates.as_params)
|
||||
rsrc = stack['LaunchConfig']
|
||||
# ImageId, InstanceType and BlockDeviceMappings keep the lc's values
|
||||
# KeyName and SecurityGroups are derived from the instance
|
||||
lc_props = {
|
||||
'ImageId': 'foo',
|
||||
'InstanceType': 'bar',
|
||||
'BlockDeviceMappings': lcp['BlockDeviceMappings'],
|
||||
'KeyName': 'hth_keypair',
|
||||
'SecurityGroups': ['hth_test']
|
||||
}
|
||||
rsrc.rebuild_lc_properties = mock.Mock(return_value=lc_props)
|
||||
self.stub_ImageConstraint_validate()
|
||||
self.stub_FlavorConstraint_validate()
|
||||
self.stub_SnapshotConstraint_validate()
|
||||
self.patchobject(nova.ServerConstraint, 'validate', return_value=True)
|
||||
self.m.ReplayAll()
|
||||
|
||||
self.assertIsNone(rsrc.validate())
|
||||
scheduler.TaskRunner(rsrc.create)()
|
||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_lc_validate_without_InstanceId_and_ImageId(self):
|
||||
t = template_format.parse(inline_templates.as_template)
|
||||
lcp = t['Resources']['LaunchConfig']['Properties']
|
||||
lcp.pop('ImageId')
|
||||
stack = utils.parse_stack(t, inline_templates.as_params)
|
||||
rsrc = stack['LaunchConfig']
|
||||
self.stub_SnapshotConstraint_validate()
|
||||
self.stub_FlavorConstraint_validate()
|
||||
self.m.ReplayAll()
|
||||
e = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
ex_msg = ('If without InstanceId, '
|
||||
'ImageId and InstanceType are required.')
|
||||
self.assertIn(ex_msg, six.text_type(e))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_lc_validate_without_InstanceId_and_InstanceType(self):
|
||||
t = template_format.parse(inline_templates.as_template)
|
||||
lcp = t['Resources']['LaunchConfig']['Properties']
|
||||
lcp.pop('InstanceType')
|
||||
stack = utils.parse_stack(t, inline_templates.as_params)
|
||||
rsrc = stack['LaunchConfig']
|
||||
self.stub_SnapshotConstraint_validate()
|
||||
self.stub_ImageConstraint_validate()
|
||||
self.m.ReplayAll()
|
||||
e = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
ex_msg = ('If without InstanceId, '
|
||||
'ImageId and InstanceType are required.')
|
||||
self.assertIn(ex_msg, six.text_type(e))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_launch_config_create_with_instanceid_not_found(self):
|
||||
t = template_format.parse(inline_templates.as_template)
|
||||
lcp = t['Resources']['LaunchConfig']['Properties']
|
||||
lcp['InstanceId'] = '5678'
|
||||
stack = utils.parse_stack(t, params=inline_templates.as_params)
|
||||
rsrc = stack['LaunchConfig']
|
||||
|
||||
self.patchobject(nova.NovaClientPlugin, 'get_server',
|
||||
side_effect=exception.ServerNotFound(server='5678'))
|
||||
msg = ("Property error : LaunchConfig: InstanceId Error validating "
|
||||
"value '5678': The server (5678) could not be found.")
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertIn(msg, six.text_type(exc))
|
||||
|
||||
def test_validate_BlockDeviceMappings_without_Ebs_property(self):
|
||||
t = template_format.parse(inline_templates.as_template)
|
||||
lcp = t['Resources']['LaunchConfig']['Properties']
|
||||
|
Loading…
Reference in New Issue
Block a user