diff options
-rw-r--r-- | heat/api/openstack/v1/views/stacks_view.py | 1 | ||||
-rw-r--r-- | heat/engine/api.py | 1 | ||||
-rw-r--r-- | heat/engine/clients/os/nova.py | 7 | ||||
-rw-r--r-- | heat/engine/hot/functions.py | 7 | ||||
-rw-r--r-- | heat/engine/resources/template_resource.py | 30 | ||||
-rw-r--r-- | heat/rpc/api.py | 4 | ||||
-rw-r--r-- | heat/tests/test_engine_api_utils.py | 1 | ||||
-rw-r--r-- | heat/tests/test_hot.py | 44 | ||||
-rw-r--r-- | heat/tests/test_nova_client.py | 9 | ||||
-rw-r--r-- | heat/tests/test_provider_template.py | 34 | ||||
-rw-r--r-- | heat/tests/test_server.py | 25 | ||||
-rw-r--r-- | requirements.txt | 4 |
12 files changed, 152 insertions, 15 deletions
diff --git a/heat/api/openstack/v1/views/stacks_view.py b/heat/api/openstack/v1/views/stacks_view.py index 16fc5a9f1..4cc7fbfdf 100644 --- a/heat/api/openstack/v1/views/stacks_view.py +++ b/heat/api/openstack/v1/views/stacks_view.py @@ -30,6 +30,7 @@ basic_keys = ( engine_api.STACK_UPDATED_TIME, engine_api.STACK_OWNER, engine_api.STACK_PARENT, + engine_api.STACK_USER_PROJECT_ID, ) diff --git a/heat/engine/api.py b/heat/engine/api.py index 1ad332acc..7e6364980 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -102,6 +102,7 @@ def format_stack(stack, preview=False): api.STACK_TIMEOUT: stack.timeout_mins, api.STACK_OWNER: stack.username, api.STACK_PARENT: stack.owner_id, + api.STACK_USER_PROJECT_ID: stack.stack_user_project_id, } if not preview: diff --git a/heat/engine/clients/os/nova.py b/heat/engine/clients/os/nova.py index c851ea486..564f1c0a0 100644 --- a/heat/engine/clients/os/nova.py +++ b/heat/engine/clients/os/nova.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +import collections import email from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText @@ -307,7 +308,7 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers else: # Some clouds append extra (STATUS) strings to the status short_server_status = server.status.split('(')[0] - if short_server_status == "DELETED": + if short_server_status in ("DELETED", "SOFT_DELETED"): break if short_server_status == "ERROR": fault = getattr(server, 'fault', {}) @@ -369,6 +370,10 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers Serialize non-string metadata values before sending them to Nova. """ + if not isinstance(metadata, collections.Mapping): + raise exception.StackValidationFailed(message=_( + "nova server metadata needs to be a Map.")) + return dict((key, (value if isinstance(value, six.string_types) else json.dumps(value)) diff --git a/heat/engine/hot/functions.py b/heat/engine/hot/functions.py index 599f7b3d2..a318df3f1 100644 --- a/heat/engine/hot/functions.py +++ b/heat/engine/hot/functions.py @@ -199,13 +199,18 @@ class GetFile(function.Function): key. """ + def __init__(self, stack, fn_name, args): + super(GetFile, self).__init__(stack, fn_name, args) + + self.files = self.stack.t.files + def result(self): args = function.resolve(self.args) if not (isinstance(args, basestring)): raise TypeError(_('Argument to "%s" must be a string') % self.fn_name) - f = self.stack.t.files.get(args) + f = self.files.get(args) if f is None: fmt_data = {'fn_name': self.fn_name, 'file_key': args} diff --git a/heat/engine/resources/template_resource.py b/heat/engine/resources/template_resource.py index 2543a17fc..44649d3c9 100644 --- a/heat/engine/resources/template_resource.py +++ b/heat/engine/resources/template_resource.py @@ -56,8 +56,19 @@ class TemplateResource(stack_resource.StackResource): self.stack = stack self.validation_exception = None - tri = stack.env.get_resource_info( - json_snippet['Type'], + self._get_resource_info(json_snippet) + + self.properties_schema = {} + self.attributes_schema = {} + + # run Resource.__init__() so we can call self.nested() + super(TemplateResource, self).__init__(name, json_snippet, stack) + if self.validation_exception is None: + self._generate_schema(self.t) + + def _get_resource_info(self, rsrc_defn): + tri = self.stack.env.get_resource_info( + rsrc_defn['Type'], registry_type=environment.TemplateResourceInfo) if tri is None: self.validation_exception = ValueError(_( @@ -71,13 +82,6 @@ class TemplateResource(stack_resource.StackResource): else: self.allowed_schemes = ('http', 'https', 'file') - # run Resource.__init__() so we can call self.nested() - self.properties_schema = {} - self.attributes_schema = {} - super(TemplateResource, self).__init__(name, json_snippet, stack) - if self.validation_exception is None: - self._generate_schema(self.t) - def _generate_schema(self, definition): self._parsed_nested = None try: @@ -255,7 +259,15 @@ class TemplateResource(stack_resource.StackResource): return self.create_with_template(self.child_template(), self.child_params()) + def metadata_update(self, new_metadata=None): + ''' + Refresh the metadata if new_metadata is None + ''' + if new_metadata is None: + self.metadata_set(self.t.metadata()) + def handle_update(self, json_snippet, tmpl_diff, prop_diff): + self._get_resource_info(json_snippet) self._generate_schema(json_snippet) return self.update_with_template(self.child_template(), self.child_params()) diff --git a/heat/rpc/api.py b/heat/rpc/api.py index 8157f0042..6167b8d41 100644 --- a/heat/rpc/api.py +++ b/heat/rpc/api.py @@ -32,7 +32,7 @@ STACK_KEYS = ( STACK_PARAMETERS, STACK_OUTPUTS, STACK_ACTION, STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES, STACK_DISABLE_ROLLBACK, STACK_TIMEOUT, STACK_OWNER, - STACK_PARENT + STACK_PARENT, STACK_USER_PROJECT_ID ) = ( 'stack_name', 'stack_identity', 'creation_time', 'updated_time', 'deletion_time', @@ -41,7 +41,7 @@ STACK_KEYS = ( 'parameters', 'outputs', 'stack_action', 'stack_status', 'stack_status_reason', 'capabilities', 'disable_rollback', 'timeout_mins', 'stack_owner', - 'parent' + 'parent', 'stack_user_project_id' ) STACK_OUTPUT_KEYS = ( diff --git a/heat/tests/test_engine_api_utils.py b/heat/tests/test_engine_api_utils.py index 0f69ccfc6..9468406b9 100644 --- a/heat/tests/test_engine_api_utils.py +++ b/heat/tests/test_engine_api_utils.py @@ -238,6 +238,7 @@ class FormatTest(HeatTestCase): 'stack_owner': 'test_username', 'stack_status': 'IN_PROGRESS', 'stack_status_reason': '', + 'stack_user_project_id': None, 'template_description': 'No description', 'timeout_mins': None, 'parameters': { diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py index c122a000f..1d236417c 100644 --- a/heat/tests/test_hot.py +++ b/heat/tests/test_hot.py @@ -865,6 +865,50 @@ class StackTest(test_parser.StackTest): self.assertEqual('xyz', self.stack['AResource'].properties['Foo']) self.m.VerifyAll() + def test_update_modify_files_ok_replace(self): + tmpl = { + 'heat_template_version': '2013-05-23', + 'parameters': {}, + 'resources': { + 'AResource': { + 'type': 'ResourceWithPropsType', + 'properties': {'Foo': {'get_file': 'foo'}} + } + } + } + + self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, + 'update_template_diff') + + self.stack = parser.Stack(self.ctx, 'update_test_stack', + template.Template(tmpl, + files={'foo': 'abc'})) + self.stack.store() + self.stack.create() + self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE), + self.stack.state) + + updated_stack = parser.Stack(self.ctx, 'updated_stack', + template.Template(tmpl, + files={'foo': 'xyz'})) + + def check_props(*args): + self.assertEqual('abc', self.stack['AResource'].properties['Foo']) + + generic_rsrc.ResourceWithProps.update_template_diff( + {'Type': 'ResourceWithPropsType', + 'Properties': {'Foo': 'xyz'}}, + {'Type': 'ResourceWithPropsType', + 'Properties': {'Foo': 'abc'}} + ).WithSideEffects(check_props).AndRaise(resource.UpdateReplace) + self.m.ReplayAll() + + self.stack.update(updated_stack) + self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE), + self.stack.state) + self.assertEqual('xyz', self.stack['AResource'].properties['Foo']) + self.m.VerifyAll() + class StackAttributesTest(HeatTestCase): """ diff --git a/heat/tests/test_nova_client.py b/heat/tests/test_nova_client.py index dc55437c6..55fcc1843 100644 --- a/heat/tests/test_nova_client.py +++ b/heat/tests/test_nova_client.py @@ -15,6 +15,7 @@ import mock from novaclient import exceptions as nova_exceptions from oslo.config import cfg +import six import uuid from heat.common import exception @@ -232,6 +233,14 @@ class NovaUtilsMetadataTests(NovaClientPluginTestCase): expected = {'test_key': 'null'} self.assertEqual(expected, self.nova_plugin.meta_serialize(original)) + def test_serialize_no_value(self): + """This test is to prove that the user can only pass in a dict to nova + metadata. + """ + excp = self.assertRaises(exception.StackValidationFailed, + self.nova_plugin.meta_serialize, "foo") + self.assertIn('metadata needs to be a Map', six.text_type(excp)) + def test_serialize_combined(self): original = { 'test_key_1': 123, diff --git a/heat/tests/test_provider_template.py b/heat/tests/test_provider_template.py index 44e578e3a..21a990a29 100644 --- a/heat/tests/test_provider_template.py +++ b/heat/tests/test_provider_template.py @@ -443,6 +443,40 @@ class ProviderTemplateTest(HeatTestCase): cls = env.get_class('OS::ResourceType', 'fred') self.assertEqual(template_resource.TemplateResource, cls) + def test_metadata_update_called(self): + provider = { + 'HeatTemplateFormatVersion': '2012-12-12', + 'Parameters': { + 'Foo': {'Type': 'Boolean'}, + }, + } + files = {'test_resource.template': json.dumps(provider)} + + class DummyResource(object): + support_status = support.SupportStatus() + properties_schema = {"Foo": + properties.Schema(properties.Schema.BOOLEAN)} + attributes_schema = {} + + env = environment.Environment() + resource._register_class('DummyResource', DummyResource) + env.load({'resource_registry': + {'DummyResource': 'test_resource.template'}}) + stack = parser.Stack(utils.dummy_context(), 'test_stack', + parser.Template( + {'HeatTemplateFormatVersion': '2012-12-12'}, + files=files), env=env, + stack_id=str(uuid.uuid4())) + + definition = rsrc_defn.ResourceDefinition('test_t_res', + "DummyResource", + {"Foo": "False"}) + temp_res = template_resource.TemplateResource('test_t_res', + definition, stack) + temp_res.metadata_set = mock.Mock() + temp_res.metadata_update() + temp_res.metadata_set.assert_called_once_with({}) + def test_get_template_resource_class(self): test_templ_name = 'file:///etc/heatr/frodo.yaml' minimal_temp = json.dumps({'HeatTemplateFormatVersion': '2012-12-12', diff --git a/heat/tests/test_server.py b/heat/tests/test_server.py index 47de116ed..49ea2f4f9 100644 --- a/heat/tests/test_server.py +++ b/heat/tests/test_server.py @@ -1054,6 +1054,31 @@ class ServersTest(HeatTestCase): self.m.VerifyAll() + def test_server_soft_delete(self): + return_server = self.fc.servers.list()[1] + server = self._create_test_server(return_server, + 'create_delete') + server.resource_id = '1234' + + # this makes sure the auto increment worked on server creation + self.assertTrue(server.id > 0) + + server_get = self.fc.client.get_servers_1234() + self.m.StubOutWithMock(self.fc.client, 'get_servers_1234') + + def make_soft_delete(): + server_get[1]["server"]['status'] = "SOFT_DELETED" + + get = self.fc.client.get_servers_1234 + get().AndReturn(server_get) + get().AndReturn(server_get) + get().WithSideEffects(make_soft_delete).AndReturn(server_get) + mox.Replay(get) + + scheduler.TaskRunner(server.delete)() + self.assertEqual((server.DELETE, server.COMPLETE), server.state) + self.m.VerifyAll() + def test_server_update_metadata(self): return_server = self.fc.servers.list()[1] server = self._create_test_server(return_server, diff --git a/requirements.txt b/requirements.txt index d93379bb9..8ceac4557 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ lxml<=3.3.3,>=2.3 netaddr<=0.7.13,>=0.7.12 oslo.config<=1.6.0,>=1.4.0 # Apache-2.0 oslo.db<1.1,>=1.0.0 # Apache-2.0 -oslo.i18n<=1.3.1,>=1.0.0 # Apache-2.0 +oslo.i18n<=1.3.1,>=1.3.0 # Apache-2.0 oslo.messaging<1.5.0,>=1.4.0 oslo.serialization<=1.2.0,>=1.0.0 # Apache-2.0 oslo.utils<1.5.0,>=1.4.0 # Apache-2.0 @@ -24,7 +24,7 @@ posix-ipc<=0.9.9 pycrypto<=2.6.1,>=2.6 python-ceilometerclient!=1.0.12,!=1.0.13,!=1.0.14,<1.1.0,>=1.0.6 python-cinderclient<=1.1.1,>=1.1.0 -python-glanceclient<=0.15.0,>=0.14.0 +python-glanceclient<0.15.0,>=0.14.0 python-heatclient<0.3.0,>=0.2.9 python-keystoneclient<1.2.0,>=0.10.0 python-neutronclient<2.4.0,>=2.3.6 |