]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Provide config option to limit resources per stack
authorClint Byrum <clint@fewbar.com>
Thu, 5 Sep 2013 22:07:47 +0000 (15:07 -0700)
committerClint Byrum <clint@fewbar.com>
Fri, 20 Sep 2013 00:44:29 +0000 (17:44 -0700)
This provides an upper bounds on the number of resources a root level
stack can contain. The limitation is only applied to the engine creation
point so that existing stacks that are over the limit in the database
will not cause problems. Nested stacks will be addressed in a follow-up
patch.

Partial-Bug: #1215100
Change-Id: I1adcb22cf9bd5750b4ae3f219dd3264d1d02c1fc

etc/heat/heat.conf.sample
heat/common/config.py
heat/common/exception.py
heat/engine/service.py
heat/tests/test_engine_service.py

index 50867b806c9956647b3cacda7158e81a422ff4f0..2608bf8b7d4e98b3c090e44259938830fa565e6d 100644 (file)
 # Subset of trustor roles to be delegated to heat (list value)
 #trusts_delegated_roles=heat_stack_owner
 
+# Maximum resources allowed per top-level stack. (integer
+# value)
+#max_resources_per_stack=1000
+
 # Name of the engine node. This can be an opaque identifier.It
 # is not necessarily a hostname, FQDN, or IP address. (string
 # value)
index 53d26b51d50a1e85887def3bea524001f866d1be..27f75532c017fe27ed8ba81bdfc2d03c0aa34731 100644 (file)
@@ -97,8 +97,10 @@ engine_opts = [
                       'stored password or trusts')),
     cfg.ListOpt('trusts_delegated_roles',
                 default=['heat_stack_owner'],
-                help=_('Subset of trustor roles to be delegated to heat'))]
-
+                help=_('Subset of trustor roles to be delegated to heat')),
+    cfg.IntOpt('max_resources_per_stack',
+               default=1000,
+               help='Maximum resources allowed per top-level stack.')]
 
 rpc_opts = [
     cfg.StrOpt('host',
index 22bc4f8ead92b81cb8d8261205daefb01400531e..7a00ec9c359450cef004d47a6490c216390a83fd 100644 (file)
@@ -322,3 +322,7 @@ class InvalidContentType(HeatException):
 
 class RequestLimitExceeded(HeatException):
     message = _('Request limit exceeded: %(message)s')
+
+
+class StackResourceLimitExceeded(HeatException):
+    message = _('Maximum resources per stack exceeded.')
index 61a414b78cc0cee4a4be507f489eb13efeb24f82..9241c8d2a32524f963fcf6013d40fb9c34ab9fa7 100644 (file)
@@ -19,6 +19,8 @@ import json
 from oslo.config import cfg
 import webob
 
+cfg.CONF.import_opt('max_resources_per_stack', 'heat.common.config')
+
 from heat.openstack.common import timeutils
 from heat.common import context
 from heat.db import api as db_api
@@ -36,6 +38,7 @@ from heat.engine import parser
 from heat.engine import properties
 from heat.engine import resource
 from heat.engine import resources
+from heat.engine import template as tpl
 from heat.engine import watchrule
 
 from heat.openstack.common import log as logging
@@ -249,6 +252,9 @@ class EngineService(service.Service):
 
         tmpl = parser.Template(template, files=files)
 
+        if len(tmpl[tpl.RESOURCES]) > cfg.CONF.max_resources_per_stack:
+            raise exception.StackResourceLimitExceeded()
+
         # Extract the common query parameters
         common_params = api.extract_args(args)
         env = environment.Environment(params)
index ea40369eafc6c53c532cf48fdf5ffadd1795ebff..46829da42ea43296f1211cd71bde72d62011dc4b 100644 (file)
@@ -34,6 +34,7 @@ from heat.engine import parser
 from heat.engine.resource import _register_class
 from heat.engine import service
 from heat.engine.properties import Properties
+from heat.engine import resource as res
 from heat.engine.resources import instance as instances
 from heat.engine.resources import nova_utils
 from heat.engine import resource as rsrs
@@ -413,6 +414,65 @@ class StackServiceCreateUpdateDeleteTest(HeatTestCase):
         self.assertEqual(
             'Missing required credential: X-Auth-User', ex.message)
 
+    def test_stack_create_total_resources_equals_max(self):
+        stack_name = 'service_create_stack_total_resources_equals_max'
+        params = {}
+        res._register_class('GenericResourceType',
+                            generic_rsrc.GenericResource)
+        tpl = {'Resources': {
+               'A': {'Type': 'GenericResourceType'},
+               'B': {'Type': 'GenericResourceType'},
+               'C': {'Type': 'GenericResourceType'}}}
+
+        template = parser.Template(tpl)
+        stack = parser.Stack(self.ctx, stack_name, template,
+                             environment.Environment({}))
+
+        self.m.StubOutWithMock(parser, 'Template')
+        self.m.StubOutWithMock(environment, 'Environment')
+        self.m.StubOutWithMock(parser, 'Stack')
+
+        parser.Template(template, files=None).AndReturn(stack.t)
+        environment.Environment(params).AndReturn(stack.env)
+        parser.Stack(self.ctx, stack.name,
+                     stack.t,
+                     stack.env).AndReturn(stack)
+
+        self.m.StubOutClassWithMocks(hkc.kc, "Client")
+        mock_ks_client = hkc.kc.Client(
+            auth_url=mox.IgnoreArg(),
+            tenant_name='test_tenant',
+            token='abcd1234')
+        mock_ks_client.authenticate().AndReturn(True)
+
+        self.m.StubOutWithMock(hkc.KeystoneClient, 'create_trust_context')
+        hkc.KeystoneClient.create_trust_context().AndReturn(None)
+
+        self.m.ReplayAll()
+
+        cfg.CONF.set_override('max_resources_per_stack', 3)
+
+        result = self.man.create_stack(self.ctx, stack_name, template, params,
+                                       None, {})
+        self.m.VerifyAll()
+        self.assertEquals(stack.identifier(), result)
+        self.assertEquals(3, stack.total_resources())
+
+    def test_stack_create_total_resources_exceeds_max(self):
+        stack_name = 'service_create_stack_total_resources_exceeds_max'
+        params = {}
+        res._register_class('GenericResourceType',
+                            generic_rsrc.GenericResource)
+        tpl = {'Resources': {
+               'A': {'Type': 'GenericResourceType'},
+               'B': {'Type': 'GenericResourceType'},
+               'C': {'Type': 'GenericResourceType'}}}
+        template = parser.Template(tpl)
+        cfg.CONF.set_override('max_resources_per_stack', 2)
+        self.assertRaises(exception.StackResourceLimitExceeded,
+                          self.man.create_stack, self.ctx, stack_name,
+                          template, params, None, {})
+
     def test_stack_validate(self):
         stack_name = 'service_create_test_validate'
         stack = get_wordpress_stack(stack_name, self.ctx)