Move instance_error_causes_group_error() to functional tests
Part of blueprint decouple-nested Change-Id: I33a9772642c6e58b4b9419330d2aa4e3f8e5cab4
This commit is contained in:
@@ -19,9 +19,7 @@ import mock
|
||||
from heat.common import exception
|
||||
from heat.common import grouputils
|
||||
from heat.common import template_format
|
||||
from heat.engine import parser
|
||||
from heat.engine import resource
|
||||
from heat.engine.resources import instance
|
||||
from heat.engine.resources import instance_group as instgrp
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.engine import scheduler
|
||||
@@ -29,162 +27,6 @@ from heat.tests.autoscaling import inline_templates
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
ig_template = '''
|
||||
{
|
||||
"AWSTemplateFormatVersion" : "2010-09-09",
|
||||
"Description" : "Template to create multiple instances.",
|
||||
"Parameters" : {},
|
||||
"Resources" : {
|
||||
"JobServerGroup" : {
|
||||
"Type" : "OS::Heat::InstanceGroup",
|
||||
"Properties" : {
|
||||
"LaunchConfigurationName" : { "Ref" : "JobServerConfig" },
|
||||
"Size" : "1",
|
||||
"AvailabilityZones" : ["nova"]
|
||||
}
|
||||
},
|
||||
|
||||
"JobServerConfig" : {
|
||||
"Type" : "AWS::AutoScaling::LaunchConfiguration",
|
||||
"Metadata": {"foo": "bar"},
|
||||
"Properties": {
|
||||
"ImageId" : "foo",
|
||||
"InstanceType" : "m1.large",
|
||||
"KeyName" : "test",
|
||||
"SecurityGroups" : [ "sg-1" ],
|
||||
"UserData" : "jsconfig data",
|
||||
"BlockDeviceMappings": [
|
||||
{
|
||||
"DeviceName": "vdb",
|
||||
"Ebs": {"SnapshotId": "9ef5496e-7426-446a-bbc8-01f84d9c9972",
|
||||
"DeleteOnTermination": "True"}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
class InstanceGroupTest(common.HeatTestCase):
|
||||
def _stub_create(self, num, instance_class=instance.Instance):
|
||||
"""
|
||||
Expect creation of C{num} number of Instances.
|
||||
|
||||
:param instance_class: The resource class to expect to be created
|
||||
instead of instance.Instance.
|
||||
"""
|
||||
self.m.StubOutWithMock(parser.Stack, 'validate')
|
||||
parser.Stack.validate().MultipleTimes().AndReturn(None)
|
||||
self.stub_KeypairConstraint_validate()
|
||||
self.stub_ImageConstraint_validate()
|
||||
self.stub_FlavorConstraint_validate()
|
||||
self.stub_SnapshotConstraint_validate()
|
||||
|
||||
self.m.StubOutWithMock(instance_class, 'handle_create')
|
||||
self.m.StubOutWithMock(instance_class, 'check_create_complete')
|
||||
cookie = object()
|
||||
|
||||
for x in range(num):
|
||||
instance_class.handle_create().AndReturn(cookie)
|
||||
instance_class.check_create_complete(cookie).AndReturn(False)
|
||||
instance_class.check_create_complete(
|
||||
cookie).MultipleTimes().AndReturn(True)
|
||||
|
||||
def create_resource(self, t, stack, resource_name):
|
||||
# subsequent resources may need to reference previous created resources
|
||||
# use the stack's resource objects instead of instantiating new ones
|
||||
rsrc = stack[resource_name]
|
||||
self.assertIsNone(rsrc.validate())
|
||||
scheduler.TaskRunner(rsrc.create)()
|
||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||
return rsrc
|
||||
|
||||
def test_create_instance_error_causes_group_error(self):
|
||||
"""
|
||||
If a resource in an instance group fails to be created, the instance
|
||||
group itself will fail and the broken inner resource will remain.
|
||||
"""
|
||||
t = template_format.parse(ig_template)
|
||||
stack = utils.parse_stack(t)
|
||||
|
||||
self.m.StubOutWithMock(parser.Stack, 'validate')
|
||||
parser.Stack.validate().MultipleTimes().AndReturn(None)
|
||||
self.stub_ImageConstraint_validate()
|
||||
self.stub_KeypairConstraint_validate()
|
||||
self.stub_FlavorConstraint_validate()
|
||||
self.stub_SnapshotConstraint_validate()
|
||||
|
||||
self.m.StubOutWithMock(instance.Instance, 'handle_create')
|
||||
instance.Instance.handle_create().AndRaise(Exception)
|
||||
|
||||
self.m.ReplayAll()
|
||||
self.create_resource(t, stack, 'JobServerConfig')
|
||||
self.assertRaises(
|
||||
exception.ResourceFailure,
|
||||
self.create_resource, t, stack, 'JobServerGroup')
|
||||
|
||||
rsrc = stack['JobServerGroup']
|
||||
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
|
||||
|
||||
# The failed inner resource remains
|
||||
self.assertEqual(1, len(rsrc.nested().resources))
|
||||
child_resource = rsrc.nested().resources.values()[0]
|
||||
self.assertEqual((child_resource.CREATE, child_resource.FAILED),
|
||||
child_resource.state)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_instance_error_causes_group_error(self):
|
||||
"""
|
||||
If a resource in an instance group fails to be created during an
|
||||
update, the instance group itself will fail and the broken inner
|
||||
resource will remain.
|
||||
"""
|
||||
t = template_format.parse(ig_template)
|
||||
stack = utils.parse_stack(t)
|
||||
|
||||
self._stub_create(1)
|
||||
self.m.ReplayAll()
|
||||
self.create_resource(t, stack, 'JobServerConfig')
|
||||
rsrc = self.create_resource(t, stack, 'JobServerGroup')
|
||||
self.assertEqual(1, len(rsrc.nested().resources))
|
||||
succeeded_instance = rsrc.nested().resources.values()[0]
|
||||
|
||||
self.m.VerifyAll()
|
||||
self.m.UnsetStubs()
|
||||
|
||||
self.m.StubOutWithMock(parser.Stack, 'validate')
|
||||
parser.Stack.validate()
|
||||
self.stub_ImageConstraint_validate()
|
||||
self.stub_KeypairConstraint_validate()
|
||||
self.stub_FlavorConstraint_validate()
|
||||
self.stub_SnapshotConstraint_validate()
|
||||
|
||||
self.m.StubOutWithMock(instance.Instance, 'handle_create')
|
||||
instance.Instance.handle_create().AndRaise(Exception)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
props = copy.copy(rsrc.properties.data)
|
||||
props['Size'] = '2'
|
||||
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
|
||||
rsrc.type(),
|
||||
props)
|
||||
updater = scheduler.TaskRunner(rsrc.update, update_snippet)
|
||||
self.assertRaises(exception.ResourceFailure, updater)
|
||||
|
||||
self.assertEqual((rsrc.UPDATE, rsrc.FAILED), rsrc.state)
|
||||
|
||||
# The failed inner resource remains
|
||||
self.assertEqual(2, len(rsrc.nested().resources))
|
||||
child_resource = [r for r in rsrc.nested().resources.values()
|
||||
if r.name != succeeded_instance.name][0]
|
||||
self.assertEqual((child_resource.CREATE, child_resource.FAILED),
|
||||
child_resource.state)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
|
||||
class TestInstanceGroup(common.HeatTestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -76,6 +76,33 @@ resources:
|
||||
random1:
|
||||
type: OS::Heat::RandomString
|
||||
|
||||
outputs:
|
||||
PublicIp:
|
||||
value: {get_attr: [random1, value]}
|
||||
'''
|
||||
|
||||
# This is designed to fail.
|
||||
bad_instance_template = '''
|
||||
heat_template_version: 2013-05-23
|
||||
parameters:
|
||||
ImageId: {type: string}
|
||||
InstanceType: {type: string}
|
||||
KeyName: {type: string}
|
||||
SecurityGroups: {type: comma_delimited_list}
|
||||
UserData: {type: string}
|
||||
Tags: {type: comma_delimited_list}
|
||||
|
||||
resources:
|
||||
random1:
|
||||
type: OS::Heat::RandomString
|
||||
depends_on: waiter
|
||||
ready_poster:
|
||||
type: AWS::CloudFormation::WaitConditionHandle
|
||||
waiter:
|
||||
type: AWS::CloudFormation::WaitCondition
|
||||
properties:
|
||||
Handle: {Ref: ready_poster}
|
||||
Timeout: 1
|
||||
outputs:
|
||||
PublicIp:
|
||||
value: {get_attr: [random1, value]}
|
||||
@@ -95,6 +122,27 @@ outputs:
|
||||
inst_list = self._stack_output(stack, 'InstanceList')
|
||||
self.assertEqual(expected_count, len(inst_list.split(',')))
|
||||
|
||||
def _get_nested_identifier(self, stack_identifier):
|
||||
rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
|
||||
nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
|
||||
self.assertEqual(1, len(nested_link))
|
||||
nested_href = nested_link[0]['href']
|
||||
nested_id = nested_href.split('/')[-1]
|
||||
nested_identifier = '/'.join(nested_href.split('/')[-2:])
|
||||
physical_resource_id = rsrc.physical_resource_id
|
||||
self.assertEqual(physical_resource_id, nested_id)
|
||||
return nested_identifier
|
||||
|
||||
def _assert_instance_state(self, nested_identifier,
|
||||
num_complete, num_failed):
|
||||
for res in self.client.resources.list(nested_identifier):
|
||||
if 'COMPLETE' in res.resource_status:
|
||||
num_complete = num_complete - 1
|
||||
elif 'FAILED' in res.resource_status:
|
||||
num_failed = num_failed - 1
|
||||
self.assertEqual(0, num_failed)
|
||||
self.assertEqual(0, num_complete)
|
||||
|
||||
def test_basic_create_works(self):
|
||||
"""Make sure the working case is good.
|
||||
Note this combines test_override_aws_ec2_instance into this test as
|
||||
@@ -202,3 +250,78 @@ outputs:
|
||||
# replacement will cause the resource physical_resource_id to change.
|
||||
rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup')
|
||||
self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id)
|
||||
|
||||
def test_create_instance_error_causes_group_error(self):
|
||||
"""If a resource in an instance group fails to be created, the instance
|
||||
group itself will fail and the broken inner resource will remain.
|
||||
"""
|
||||
stack_name = self._stack_rand_name()
|
||||
files = {'provider.yaml': self.bad_instance_template}
|
||||
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
|
||||
'parameters': {'size': 2,
|
||||
'image': self.conf.image_ref,
|
||||
'keyname': self.conf.keypair_name,
|
||||
'flavor': self.conf.instance_type}}
|
||||
|
||||
self.client.stacks.create(
|
||||
stack_name=stack_name,
|
||||
template=self.template,
|
||||
files=files,
|
||||
disable_rollback=True,
|
||||
parameters={},
|
||||
environment=env
|
||||
)
|
||||
self.addCleanup(self.client.stacks.delete, stack_name)
|
||||
stack = self.client.stacks.get(stack_name)
|
||||
stack_identifier = '%s/%s' % (stack_name, stack.id)
|
||||
self._wait_for_stack_status(stack_identifier, 'CREATE_FAILED')
|
||||
initial_resources = {
|
||||
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
|
||||
'JobServerGroup': 'OS::Heat::InstanceGroup'}
|
||||
self.assertEqual(initial_resources,
|
||||
self.list_resources(stack_identifier))
|
||||
|
||||
nested_ident = self._get_nested_identifier(stack_identifier)
|
||||
self._assert_instance_state(nested_ident, 0, 2)
|
||||
|
||||
def test_update_instance_error_causes_group_error(self):
|
||||
"""If a resource in an instance group fails to be created during an
|
||||
update, the instance group itself will fail and the broken inner
|
||||
resource will remain.
|
||||
"""
|
||||
files = {'provider.yaml': self.instance_template}
|
||||
env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'},
|
||||
'parameters': {'size': 2,
|
||||
'image': self.conf.image_ref,
|
||||
'keyname': self.conf.keypair_name,
|
||||
'flavor': self.conf.instance_type}}
|
||||
|
||||
stack_identifier = self.stack_create(template=self.template,
|
||||
files=files,
|
||||
environment=env)
|
||||
initial_resources = {
|
||||
'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration',
|
||||
'JobServerGroup': 'OS::Heat::InstanceGroup'}
|
||||
self.assertEqual(initial_resources,
|
||||
self.list_resources(stack_identifier))
|
||||
|
||||
stack = self.client.stacks.get(stack_identifier)
|
||||
self.assert_instance_count(stack, 2)
|
||||
nested_ident = self._get_nested_identifier(stack_identifier)
|
||||
self._assert_instance_state(nested_ident, 2, 0)
|
||||
|
||||
env['parameters']['size'] = 3
|
||||
files2 = {'provider.yaml': self.bad_instance_template}
|
||||
self.client.stacks.update(
|
||||
stack_id=stack_identifier,
|
||||
template=self.template,
|
||||
files=files2,
|
||||
disable_rollback=True,
|
||||
parameters={},
|
||||
environment=env
|
||||
)
|
||||
self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED')
|
||||
|
||||
# assert that there are 3 bad instances
|
||||
nested_ident = self._get_nested_identifier(stack_identifier)
|
||||
self._assert_instance_state(nested_ident, 0, 3)
|
||||
|
||||
Reference in New Issue
Block a user