Provide config option to limit resources per stack
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
This commit is contained in:
parent
f00272b3b8
commit
ecf3954d23
@ -31,6 +31,10 @@
|
|||||||
# Subset of trustor roles to be delegated to heat (list value)
|
# Subset of trustor roles to be delegated to heat (list value)
|
||||||
#trusts_delegated_roles=heat_stack_owner
|
#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
|
# Name of the engine node. This can be an opaque identifier.It
|
||||||
# is not necessarily a hostname, FQDN, or IP address. (string
|
# is not necessarily a hostname, FQDN, or IP address. (string
|
||||||
# value)
|
# value)
|
||||||
|
@ -97,8 +97,10 @@ engine_opts = [
|
|||||||
'stored password or trusts')),
|
'stored password or trusts')),
|
||||||
cfg.ListOpt('trusts_delegated_roles',
|
cfg.ListOpt('trusts_delegated_roles',
|
||||||
default=['heat_stack_owner'],
|
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 = [
|
rpc_opts = [
|
||||||
cfg.StrOpt('host',
|
cfg.StrOpt('host',
|
||||||
|
@ -322,3 +322,7 @@ class InvalidContentType(HeatException):
|
|||||||
|
|
||||||
class RequestLimitExceeded(HeatException):
|
class RequestLimitExceeded(HeatException):
|
||||||
message = _('Request limit exceeded: %(message)s')
|
message = _('Request limit exceeded: %(message)s')
|
||||||
|
|
||||||
|
|
||||||
|
class StackResourceLimitExceeded(HeatException):
|
||||||
|
message = _('Maximum resources per stack exceeded.')
|
||||||
|
@ -19,6 +19,8 @@ import json
|
|||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
|
cfg.CONF.import_opt('max_resources_per_stack', 'heat.common.config')
|
||||||
|
|
||||||
from heat.openstack.common import timeutils
|
from heat.openstack.common import timeutils
|
||||||
from heat.common import context
|
from heat.common import context
|
||||||
from heat.db import api as db_api
|
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 properties
|
||||||
from heat.engine import resource
|
from heat.engine import resource
|
||||||
from heat.engine import resources
|
from heat.engine import resources
|
||||||
|
from heat.engine import template as tpl
|
||||||
from heat.engine import watchrule
|
from heat.engine import watchrule
|
||||||
|
|
||||||
from heat.openstack.common import log as logging
|
from heat.openstack.common import log as logging
|
||||||
@ -249,6 +252,9 @@ class EngineService(service.Service):
|
|||||||
|
|
||||||
tmpl = parser.Template(template, files=files)
|
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
|
# Extract the common query parameters
|
||||||
common_params = api.extract_args(args)
|
common_params = api.extract_args(args)
|
||||||
env = environment.Environment(params)
|
env = environment.Environment(params)
|
||||||
|
@ -34,6 +34,7 @@ from heat.engine import parser
|
|||||||
from heat.engine.resource import _register_class
|
from heat.engine.resource import _register_class
|
||||||
from heat.engine import service
|
from heat.engine import service
|
||||||
from heat.engine.properties import Properties
|
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 instance as instances
|
||||||
from heat.engine.resources import nova_utils
|
from heat.engine.resources import nova_utils
|
||||||
from heat.engine import resource as rsrs
|
from heat.engine import resource as rsrs
|
||||||
@ -413,6 +414,65 @@ class StackServiceCreateUpdateDeleteTest(HeatTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'Missing required credential: X-Auth-User', ex.message)
|
'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):
|
def test_stack_validate(self):
|
||||||
stack_name = 'service_create_test_validate'
|
stack_name = 'service_create_test_validate'
|
||||||
stack = get_wordpress_stack(stack_name, self.ctx)
|
stack = get_wordpress_stack(stack_name, self.ctx)
|
||||||
|
Loading…
Reference in New Issue
Block a user