Merge "Implement 'InstanceId' for LaunchConfiguration"

This commit is contained in:
Jenkins 2015-01-26 05:00:04 +00:00 committed by Gerrit Code Review
commit 2c35c47dce
3 changed files with 134 additions and 3 deletions

View File

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

View File

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

View File

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