diff --git a/heat/engine/resources/nova_utils.py b/heat/engine/resources/nova_utils.py index 4db43392c8..112f745f5e 100644 --- a/heat/engine/resources/nova_utils.py +++ b/heat/engine/resources/nova_utils.py @@ -293,3 +293,9 @@ def server_to_ipaddress(client, server): for n in server.networks: if len(server.networks[n]) > 0: return server.networks[n][0] + + +def absolute_limits(nova_client): + """Return the absolute limits as a dictionary.""" + limits = nova_client.limits.get() + return dict([(limit.name, limit.value) for limit in list(limits.absolute)]) diff --git a/heat/engine/resources/server.py b/heat/engine/resources/server.py index c95def53d0..3202ee1b4f 100644 --- a/heat/engine/resources/server.py +++ b/heat/engine/resources/server.py @@ -147,9 +147,8 @@ class Server(resource.Resource): 'Type': 'Map', 'UpdateAllowed': True, 'Description': _('Arbitrary key/value metadata to store for this ' - 'server. A maximum of five entries is allowed, ' - 'and both keys and values must be 255 characters ' - 'or less.')}, + 'server. Both keys and values must be 255 ' + 'characters or less.')}, 'user_data_format': { 'Type': 'String', 'Default': 'HEAT_CFNTOOLS', @@ -494,6 +493,18 @@ class Server(resource.Resource): '') % dict(network=network['network'], server=self.name)) + # verify that the number of metadata entries is not greater + # than the maximum number allowed in the provider's absolute + # limits + metadata = self.properties.get('metadata') + if metadata is not None: + limits = nova_utils.absolute_limits(self.nova()) + if len(metadata) > limits['maxServerMeta']: + msg = _('Instance metadata must not contain greater than %s ' + 'entries. This is the maximum number allowed by your ' + 'service provider') % limits['maxServerMeta'] + raise exception.StackValidationFailed(message=msg) + def handle_delete(self): ''' Delete a server, blocking until it is disposed by OpenStack diff --git a/heat/tests/test_server.py b/heat/tests/test_server.py index fabd8d839c..8f6264f8c7 100644 --- a/heat/tests/test_server.py +++ b/heat/tests/test_server.py @@ -1156,3 +1156,57 @@ class ServersTest(HeatTestCase): self.assertEqual(msg, str(ex)) self.m.VerifyAll() + + def test_validate_metadata_too_many(self): + stack_name = 'srv_val_metadata' + (t, stack) = self._setup_test_stack(stack_name) + + t['Resources']['WebServer']['Properties']['metadata'] = {'a': 1, + 'b': 2, + 'c': 3, + 'd': 4} + server = servers.Server('server_create_image_err', + t['Resources']['WebServer'], stack) + + limits = self.m.CreateMockAnything() + max_server_meta = self.m.CreateMockAnything() + max_server_meta.name = 'maxServerMeta' + max_server_meta.value = 3 + limits.absolute = [max_server_meta] + self.m.StubOutWithMock(self.fc.limits, 'get') + self.fc.limits.get().AndReturn(limits) + + self.m.StubOutWithMock(server, 'nova') + server.nova().MultipleTimes().AndReturn(self.fc) + self.m.ReplayAll() + + ex = self.assertRaises(exception.StackValidationFailed, + server.validate) + self.assertIn('Instance metadata must not contain greater than 3 ' + 'entries', str(ex)) + self.m.VerifyAll() + + def test_validate_metadata_okay(self): + stack_name = 'srv_val_metadata' + (t, stack) = self._setup_test_stack(stack_name) + + t['Resources']['WebServer']['Properties']['metadata'] = {'a': 1, + 'b': 2, + 'c': 3} + server = servers.Server('server_create_image_err', + t['Resources']['WebServer'], stack) + + limits = self.m.CreateMockAnything() + max_server_meta = self.m.CreateMockAnything() + max_server_meta.name = 'maxServerMeta' + max_server_meta.value = 3 + limits.absolute = [max_server_meta] + self.m.StubOutWithMock(self.fc.limits, 'get') + self.fc.limits.get().AndReturn(limits) + + self.m.StubOutWithMock(server, 'nova') + server.nova().MultipleTimes().AndReturn(self.fc) + self.m.ReplayAll() + + self.assertIsNone(server.validate()) + self.m.VerifyAll() diff --git a/heat/tests/v1_1/fakes.py b/heat/tests/v1_1/fakes.py index dd12a865d4..d3d0793dcb 100644 --- a/heat/tests/v1_1/fakes.py +++ b/heat/tests/v1_1/fakes.py @@ -360,3 +360,9 @@ class FakeHTTPClient(base_client.HTTPClient): 'id': '42'}, {'label': 'foo', 'id': '42'}]}) + + # + # Limits + # + def get_limits(self, *kw): + return (200, {'limits': {'absolute': {'maxServerMeta': 3}}})