heat/heat/tests/test_metadata_refresh.py

373 lines
12 KiB
Python

#
# 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 mock
import mox
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()
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
@mock.patch(('heat.engine.resources.aws.ec2.instance.Instance'
'.is_service_available'))
def test_wait_meta(self, mock_is_service_available):
'''
1 create stack
2 assert empty instance metadata
3 service.resource_signal()
4 assert valid waitcond metadata
5 assert valid instance metadata
'''
mock_is_service_available.return_value = True
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()