# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mox from oslo_config import cfg from heat.common import identifier from heat.common import template_format from heat.engine import environment from heat.engine.resources.aws.cfn import wait_condition_handle as aws_wch from heat.engine.resources.aws.ec2 import instance from heat.engine.resources.openstack.nova import server from heat.engine import scheduler from heat.engine import service from heat.engine import stack as parser from heat.engine import template as tmpl from heat.tests import common from heat.tests import utils test_template_metadata = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "", "Parameters" : { "KeyName" : {"Type" : "String", "Default": "mine" }, }, "Resources" : { "S1": { "Type": "AWS::EC2::Instance", "Metadata" : { "AWS::CloudFormation::Init" : { "config" : { "files" : { "/tmp/random_file" : { "content" : { "Fn::Join" : ["", [ "s2-ip=", {"Fn::GetAtt": ["S2", "PublicIp"]} ]]}, "mode" : "000400", "owner" : "root", "group" : "root" } } } } }, "Properties": { "ImageId" : "a", "InstanceType" : "m1.large", "KeyName" : { "Ref" : "KeyName" }, "UserData" : "#!/bin/bash -v\n" } }, "S2": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId" : "a", "InstanceType" : "m1.large", "KeyName" : { "Ref" : "KeyName" }, "UserData" : "#!/bin/bash -v\n" } } } } ''' test_template_waitcondition = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Just a WaitCondition.", "Parameters" : { "KeyName" : {"Type" : "String", "Default": "mine" }, }, "Resources" : { "WH" : { "Type" : "AWS::CloudFormation::WaitConditionHandle" }, "S1": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId" : "a", "InstanceType" : "m1.large", "KeyName" : { "Ref" : "KeyName" }, "UserData" : { "Fn::Join" : [ "", [ "#!/bin/bash -v\n", "echo ", { "Ref" : "WH" }, "\n" ] ] } } }, "WC" : { "Type" : "AWS::CloudFormation::WaitCondition", "DependsOn": "S1", "Properties" : { "Handle" : {"Ref" : "WH"}, "Timeout" : "5" } }, "S2": { "Type": "AWS::EC2::Instance", "Metadata" : { "test" : {"Fn::GetAtt": ["WC", "Data"]} }, "Properties": { "ImageId" : "a", "InstanceType" : "m1.large", "KeyName" : { "Ref" : "KeyName" }, "UserData" : "#!/bin/bash -v\n" } } } } ''' test_template_server = ''' heat_template_version: 2013-05-23 resources: instance1: type: OS::Nova::Server metadata: {"template_data": {get_attr: [instance2, first_address]}} properties: image: cirros-0.3.2-x86_64-disk flavor: m1.small key_name: stack_key instance2: type: OS::Nova::Server metadata: {'apples': 'pears'} properties: image: cirros-0.3.2-x86_64-disk flavor: m1.small key_name: stack_key ''' class MetadataRefreshTest(common.HeatTestCase): ''' The point of the test is to confirm that metadata gets updated when FnGetAtt() returns something different. ''' def setUp(self): super(MetadataRefreshTest, self).setUp() def create_stack(self, stack_name='test_stack', params=None): params = params or {} temp = template_format.parse(test_template_metadata) template = tmpl.Template(temp, env=environment.Environment(params)) ctx = utils.dummy_context() stack = parser.Stack(ctx, stack_name, template, disable_rollback=True) self.stack_id = stack.store() self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.stub_FlavorConstraint_validate() self.m.StubOutWithMock(instance.Instance, 'handle_create') self.m.StubOutWithMock(instance.Instance, 'check_create_complete') for cookie in (object(), object()): instance.Instance.handle_create().AndReturn(cookie) create_complete = instance.Instance.check_create_complete(cookie) create_complete.InAnyOrder().AndReturn(True) self.m.StubOutWithMock(instance.Instance, 'FnGetAtt') return stack def test_FnGetAtt(self): self.stack = self.create_stack() instance.Instance.FnGetAtt('PublicIp').AndReturn('1.2.3.5') # called by metadata_update() instance.Instance.FnGetAtt('PublicIp').AndReturn('10.0.0.5') self.m.ReplayAll() self.stack.create() self.assertEqual((self.stack.CREATE, self.stack.COMPLETE), self.stack.state) s1 = self.stack['S1'] s2 = self.stack['S2'] files = s1.metadata_get()[ 'AWS::CloudFormation::Init']['config']['files'] cont = files['/tmp/random_file']['content'] self.assertEqual((s2.CREATE, s2.COMPLETE), s2.state) self.assertEqual('s2-ip=1.2.3.5', cont) s1.metadata_update() s2.metadata_update() files = s1.metadata_get()[ 'AWS::CloudFormation::Init']['config']['files'] cont = files['/tmp/random_file']['content'] self.assertEqual('s2-ip=10.0.0.5', cont) self.m.VerifyAll() class WaitCondMetadataUpdateTest(common.HeatTestCase): def setUp(self): super(WaitCondMetadataUpdateTest, self).setUp() self.man = service.EngineService('a-host', 'a-topic') self.man.create_periodic_tasks() cfg.CONF.set_default('heat_waitcondition_server_url', 'http://server.test:8000/v1/waitcondition') def create_stack(self, stack_name='test_stack'): temp = template_format.parse(test_template_waitcondition) template = tmpl.Template(temp) ctx = utils.dummy_context() stack = parser.Stack(ctx, stack_name, template, disable_rollback=True) self.stack_id = stack.store() self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.stub_FlavorConstraint_validate() self.m.StubOutWithMock(instance.Instance, 'handle_create') self.m.StubOutWithMock(instance.Instance, 'check_create_complete') for cookie in (object(), object()): instance.Instance.handle_create().AndReturn(cookie) instance.Instance.check_create_complete(cookie).AndReturn(True) id = identifier.ResourceIdentifier('test_tenant_id', stack.name, stack.id, '', 'WH') self.m.StubOutWithMock(aws_wch.WaitConditionHandle, 'identifier') aws_wch.WaitConditionHandle.identifier().MultipleTimes().AndReturn(id) self.m.StubOutWithMock(scheduler.TaskRunner, '_sleep') return stack def test_wait_meta(self): ''' 1 create stack 2 assert empty instance metadata 3 service.resource_signal() 4 assert valid waitcond metadata 5 assert valid instance metadata ''' self.stack = self.create_stack() watch = self.stack['WC'] inst = self.stack['S2'] def check_empty(sleep_time): self.assertEqual('{}', watch.FnGetAtt('Data')) self.assertIsNone(inst.metadata_get()['test']) def update_metadata(id, data, reason): self.man.resource_signal(utils.dummy_context(), dict(self.stack.identifier()), 'WH', {'Data': data, 'Reason': reason, 'Status': 'SUCCESS', 'UniqueId': id}, sync_call=True) def post_success(sleep_time): update_metadata('123', 'foo', 'bar') scheduler.TaskRunner._sleep(mox.IsA(int)).WithSideEffects(check_empty) scheduler.TaskRunner._sleep(mox.IsA(int)).WithSideEffects(post_success) scheduler.TaskRunner._sleep(mox.IsA(int)).MultipleTimes().AndReturn( None) self.m.ReplayAll() self.stack.create() self.assertEqual((self.stack.CREATE, self.stack.COMPLETE), self.stack.state) self.assertEqual('{"123": "foo"}', watch.FnGetAtt('Data')) self.assertEqual('{"123": "foo"}', inst.metadata_get()['test']) update_metadata('456', 'blarg', 'wibble') self.assertEqual('{"123": "foo", "456": "blarg"}', watch.FnGetAtt('Data')) self.assertEqual('{"123": "foo"}', inst.metadata_get()['test']) self.assertEqual('{"123": "foo", "456": "blarg"}', inst.metadata_get(refresh=True)['test']) self.m.VerifyAll() class MetadataRefreshTestServer(common.HeatTestCase): ''' The point of the test is to confirm that metadata gets updated when FnGetAtt() returns something different when using a native OS::Nova::Server resource, and that metadata keys set inside the resource (as opposed to in the template), e.g for deployments, don't get overwritten on update/refresh. ''' def setUp(self): super(MetadataRefreshTestServer, self).setUp() def create_stack(self, stack_name='test_stack_native', params=None): params = params or {} temp = template_format.parse(test_template_server) template = tmpl.Template(temp, env=environment.Environment(params)) ctx = utils.dummy_context() stack = parser.Stack(ctx, stack_name, template, disable_rollback=True) self.stack_id = stack.store() self.stub_ImageConstraint_validate() self.stub_KeypairConstraint_validate() self.stub_FlavorConstraint_validate() self.m.StubOutWithMock(server.Server, 'handle_create') self.m.StubOutWithMock(server.Server, 'check_create_complete') for cookie in (object(), object()): server.Server.handle_create().AndReturn(cookie) create_complete = server.Server.check_create_complete(cookie) create_complete.InAnyOrder().AndReturn(True) self.m.StubOutWithMock(server.Server, 'FnGetAtt') return stack def test_FnGetAtt(self): self.stack = self.create_stack() # Note dummy addresses are from TEST-NET-1 ref rfc5737 server.Server.FnGetAtt('first_address').AndReturn('192.0.2.1') # called by metadata_update() server.Server.FnGetAtt('first_address').AndReturn('192.0.2.2') server.Server.FnGetAtt('first_address').AndReturn('192.0.2.2') self.m.ReplayAll() self.stack.create() self.assertEqual((self.stack.CREATE, self.stack.COMPLETE), self.stack.state) s1 = self.stack['instance1'] s2 = self.stack['instance2'] md = s1.metadata_get() self.assertEqual({u'template_data': '192.0.2.1'}, md) s1.metadata_update() s2.metadata_update() md = s1.metadata_get() self.assertEqual({u'template_data': '192.0.2.2'}, md) # Now set some metadata via the resource, like is done by # _populate_deployments_metadata. This should be persisted over # calls to metadata_update() new_md = {u'template_data': '192.0.2.2', 'set_by_rsrc': 'orange'} s1.metadata_set(new_md) md = s1.metadata_get(refresh=True) self.assertEqual(new_md, md) s1.metadata_update() md = s1.metadata_get(refresh=True) self.assertEqual(new_md, md) self.m.VerifyAll()