Merge "Allow more options to limit number of resources"
This commit is contained in:
commit
42a0e28288
@ -152,6 +152,19 @@ engine_opts = [
|
||||
default=512,
|
||||
help=_('Maximum number of stacks any one tenant may have '
|
||||
'active at one time. -1 stands for unlimited.')),
|
||||
cfg.IntOpt('max_software_configs_per_tenant',
|
||||
default=4096,
|
||||
help=_('Maximum number of software configs any one tenant may '
|
||||
'have active at one time. -1 stands for unlimited.')),
|
||||
cfg.IntOpt('max_software_deployments_per_tenant',
|
||||
default=4096,
|
||||
help=_('Maximum number of software deployments any one tenant '
|
||||
'may have active at one time.'
|
||||
'-1 stands for unlimited.')),
|
||||
cfg.IntOpt('max_snapshots_per_stack',
|
||||
default=32,
|
||||
help=_('Maximum number of snapshot any one stack may have '
|
||||
'active at one time. -1 stands for unlimited.')),
|
||||
cfg.IntOpt('action_retry_limit',
|
||||
default=5,
|
||||
help=_('Number of times to retry to bring a '
|
||||
|
@ -1431,6 +1431,14 @@ def software_config_get_all(context, limit=None, marker=None):
|
||||
limit=limit, marker=marker).all()
|
||||
|
||||
|
||||
@context_manager.reader
|
||||
def software_config_count_all(context):
|
||||
query = context.session.query(models.SoftwareConfig)
|
||||
if not context.is_admin:
|
||||
query = query.filter_by(tenant=context.tenant_id)
|
||||
return query.count()
|
||||
|
||||
|
||||
@context_manager.writer
|
||||
def software_config_delete(context, config_id):
|
||||
config = _software_config_get(context, config_id)
|
||||
@ -1510,6 +1518,21 @@ def software_deployment_get_all(context, server_id=None):
|
||||
return query.all()
|
||||
|
||||
|
||||
@context_manager.reader
|
||||
def software_deployment_count_all(context):
|
||||
sd = models.SoftwareDeployment
|
||||
query = context.session.query(sd)
|
||||
if not context.is_admin:
|
||||
query = query.filter(
|
||||
sqlalchemy.or_(
|
||||
sd.tenant == context.tenant_id,
|
||||
sd.stack_user_project_id == context.tenant_id,
|
||||
)
|
||||
)
|
||||
|
||||
return query.count()
|
||||
|
||||
|
||||
@context_manager.writer
|
||||
def software_deployment_update(context, deployment_id, values):
|
||||
deployment = _software_deployment_get(context, deployment_id)
|
||||
@ -1587,6 +1610,12 @@ def snapshot_get_all_by_stack(context, stack_id):
|
||||
stack_id=stack_id, tenant=context.tenant_id)
|
||||
|
||||
|
||||
@context_manager.reader
|
||||
def snapshot_count_all_by_stack(context, stack_id):
|
||||
return context.session.query(models.Snapshot).filter_by(
|
||||
stack_id=stack_id, tenant=context.tenant_id).count()
|
||||
|
||||
|
||||
# service
|
||||
|
||||
|
||||
|
@ -73,6 +73,10 @@ from heat.rpc import worker_api as rpc_worker_api
|
||||
cfg.CONF.import_opt('engine_life_check_timeout', 'heat.common.config')
|
||||
cfg.CONF.import_opt('max_resources_per_stack', 'heat.common.config')
|
||||
cfg.CONF.import_opt('max_stacks_per_tenant', 'heat.common.config')
|
||||
cfg.CONF.import_opt('max_snapshots_per_stack', 'heat.common.config')
|
||||
cfg.CONF.import_opt('max_software_configs_per_tenant', 'heat.common.config')
|
||||
cfg.CONF.import_opt('max_software_deployments_per_tenant',
|
||||
'heat.common.config')
|
||||
cfg.CONF.import_opt('enable_stack_abandon', 'heat.common.config')
|
||||
cfg.CONF.import_opt('enable_stack_adopt', 'heat.common.config')
|
||||
cfg.CONF.import_opt('convergence_engine', 'heat.common.config')
|
||||
@ -2124,6 +2128,17 @@ class EngineService(service.ServiceBase):
|
||||
raise exception.ActionInProgress(stack_name=stack.name,
|
||||
action=stack.action)
|
||||
|
||||
# Do not enforce the limit, following the stack limit
|
||||
if not cnxt.is_admin:
|
||||
stack_limit = cfg.CONF.max_snapshots_per_stack
|
||||
count_all = snapshot_object.Snapshot.count_all_by_stack(cnxt,
|
||||
stack.id)
|
||||
if (stack_limit >= 0 and count_all >= stack_limit):
|
||||
message = _("You have reached the maximum snapshots "
|
||||
"per stack, %d. Please delete some "
|
||||
"snapshots.") % stack_limit
|
||||
raise exception.RequestLimitExceeded(message=message)
|
||||
|
||||
lock = stack_lock.StackLock(cnxt, stack.id, self.engine_id)
|
||||
|
||||
with lock.thread_lock():
|
||||
@ -2219,6 +2234,15 @@ class EngineService(service.ServiceBase):
|
||||
@context.request_context
|
||||
def create_software_config(self, cnxt, group, name, config,
|
||||
inputs, outputs, options):
|
||||
# Do not enforce the limit, following the stack limit
|
||||
if not cnxt.is_admin:
|
||||
tenant_limit = cfg.CONF.max_software_configs_per_tenant
|
||||
count_all = self.software_config.count_software_config(cnxt)
|
||||
if (tenant_limit >= 0 and count_all >= tenant_limit):
|
||||
message = _("You have reached the maximum software configs "
|
||||
"per tenant, %d. "
|
||||
"Please delete some configs.") % tenant_limit
|
||||
raise exception.RequestLimitExceeded(message=message)
|
||||
return self.software_config.create_software_config(
|
||||
cnxt,
|
||||
group=group,
|
||||
@ -2257,6 +2281,16 @@ class EngineService(service.ServiceBase):
|
||||
input_values, action, status,
|
||||
status_reason, stack_user_project_id,
|
||||
deployment_id=None):
|
||||
# Do not enforce the limit, following the stack limit
|
||||
if not cnxt.is_admin:
|
||||
tenant_limit = cfg.CONF.max_software_deployments_per_tenant
|
||||
count_all = self.software_config.count_software_deployment(cnxt)
|
||||
if (tenant_limit >= 0 and
|
||||
count_all >= tenant_limit):
|
||||
message = _("You have reached the maximum software "
|
||||
"deployments per tenant, %d. "
|
||||
"Please delete some deployments.") % tenant_limit
|
||||
raise exception.RequestLimitExceeded(message=message)
|
||||
return self.software_config.create_software_deployment(
|
||||
cnxt, server_id=server_id,
|
||||
config_id=config_id,
|
||||
|
@ -52,6 +52,9 @@ class SoftwareConfigService(object):
|
||||
for sc in scs]
|
||||
return result
|
||||
|
||||
def count_software_config(self, cnxt):
|
||||
return software_config_object.SoftwareConfig.count_all(cnxt)
|
||||
|
||||
def create_software_config(self, cnxt, group, name, config,
|
||||
inputs, outputs, options):
|
||||
|
||||
@ -81,6 +84,10 @@ class SoftwareConfigService(object):
|
||||
result = [api.format_software_deployment(sd) for sd in all_sd]
|
||||
return result
|
||||
|
||||
def count_software_deployment(self, cnxt):
|
||||
return software_deployment_object.SoftwareDeployment.count_all(
|
||||
cnxt)
|
||||
|
||||
def metadata_software_deployments(self, cnxt, server_id):
|
||||
if not server_id:
|
||||
raise ValueError(_('server_id must be specified'))
|
||||
|
@ -74,3 +74,7 @@ class Snapshot(
|
||||
return [cls._from_db_object(context, cls(), db_snapshot)
|
||||
for db_snapshot
|
||||
in db_api.snapshot_get_all_by_stack(context, stack_id)]
|
||||
|
||||
@classmethod
|
||||
def count_all_by_stack(cls, context, stack_id):
|
||||
return db_api.snapshot_count_all_by_stack(context, stack_id)
|
||||
|
@ -65,6 +65,10 @@ class SoftwareConfig(
|
||||
scs = db_api.software_config_get_all(context, **kwargs)
|
||||
return [cls._from_db_object(context, cls(), sc) for sc in scs]
|
||||
|
||||
@classmethod
|
||||
def count_all(cls, context, **kwargs):
|
||||
return db_api.software_config_count_all(context, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, context, config_id):
|
||||
db_api.software_config_delete(context, config_id)
|
||||
|
@ -77,6 +77,11 @@ class SoftwareDeployment(
|
||||
for db_deployment in db_api.software_deployment_get_all(
|
||||
context, server_id)]
|
||||
|
||||
@classmethod
|
||||
def count_all(cls, context):
|
||||
return db_api.software_deployment_count_all(
|
||||
context)
|
||||
|
||||
@classmethod
|
||||
def update_by_id(cls, context, deployment_id, values):
|
||||
"""Note this is a bit unusual as it returns the object.
|
||||
|
@ -1121,6 +1121,13 @@ class SqlAlchemyTest(common.HeatTestCase):
|
||||
tenant_id='admin_tenant')
|
||||
self._test_software_config_get_all(get_ctx=admin_ctx)
|
||||
|
||||
def test_software_config_count_all(self):
|
||||
self.assertEqual(0, db_api.software_config_count_all(self.ctx))
|
||||
self._create_software_config_record()
|
||||
self._create_software_config_record()
|
||||
self._create_software_config_record()
|
||||
self.assertEqual(3, db_api.software_config_count_all(self.ctx))
|
||||
|
||||
def test_software_config_delete(self):
|
||||
scf_id = self._create_software_config_record()
|
||||
|
||||
@ -1250,6 +1257,17 @@ class SqlAlchemyTest(common.HeatTestCase):
|
||||
deployments = db_api.software_deployment_get_all(admin_ctx)
|
||||
self.assertEqual(1, len(deployments))
|
||||
|
||||
def test_software_deployment_count_all(self):
|
||||
self.assertEqual(0, db_api.software_deployment_count_all(self.ctx))
|
||||
values = self._deployment_values()
|
||||
deployment = db_api.software_deployment_create(self.ctx, values)
|
||||
self.assertIsNotNone(deployment)
|
||||
deployment = db_api.software_deployment_create(self.ctx, values)
|
||||
self.assertIsNotNone(deployment)
|
||||
deployment = db_api.software_deployment_create(self.ctx, values)
|
||||
self.assertIsNotNone(deployment)
|
||||
self.assertEqual(3, db_api.software_deployment_count_all(self.ctx))
|
||||
|
||||
def test_software_deployment_update(self):
|
||||
deployment_id = str(uuid.uuid4())
|
||||
err = self.assertRaises(exception.NotFound,
|
||||
@ -1435,6 +1453,38 @@ class SqlAlchemyTest(common.HeatTestCase):
|
||||
self.assertEqual(values['status'], snapshot.status)
|
||||
self.assertIsNotNone(snapshot.created_at)
|
||||
|
||||
def test_snapshot_count_all_by_stack(self):
|
||||
template = create_raw_template(self.ctx)
|
||||
user_creds = create_user_creds(self.ctx)
|
||||
stack1 = create_stack(self.ctx, template, user_creds)
|
||||
stack2 = create_stack(self.ctx, template, user_creds)
|
||||
values = [
|
||||
{
|
||||
'tenant': self.ctx.tenant_id,
|
||||
'status': 'IN_PROGRESS',
|
||||
'stack_id': stack1.id,
|
||||
'name': 'snp1'
|
||||
},
|
||||
{
|
||||
'tenant': self.ctx.tenant_id,
|
||||
'status': 'IN_PROGRESS',
|
||||
'stack_id': stack1.id,
|
||||
'name': 'snp1'
|
||||
},
|
||||
{
|
||||
'tenant': self.ctx.tenant_id,
|
||||
'status': 'IN_PROGRESS',
|
||||
'stack_id': stack2.id,
|
||||
'name': 'snp2'
|
||||
}
|
||||
]
|
||||
for val in values:
|
||||
self.assertIsNotNone(db_api.snapshot_create(self.ctx, val))
|
||||
self.assertEqual(2, db_api.snapshot_count_all_by_stack(self.ctx,
|
||||
stack1.id))
|
||||
self.assertEqual(1, db_api.snapshot_count_all_by_stack(self.ctx,
|
||||
stack2.id))
|
||||
|
||||
|
||||
def create_raw_template(context, **kwargs):
|
||||
t = template_format.parse(wp_template)
|
||||
|
@ -15,6 +15,7 @@ import datetime
|
||||
from unittest import mock
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_messaging.rpc import dispatcher
|
||||
from oslo_serialization import jsonutils as json
|
||||
from oslo_utils import timeutils
|
||||
@ -169,6 +170,14 @@ class SoftwareConfigServiceTest(common.HeatTestCase):
|
||||
config['outputs'])
|
||||
self.assertEqual(kwargs['options'], config['options'])
|
||||
|
||||
def test_create_config_exceeds_max_per_tenant(self):
|
||||
cfg.CONF.set_override('max_software_configs_per_tenant', 0)
|
||||
ex = self.assertRaises(dispatcher.ExpectedException,
|
||||
self._create_software_config)
|
||||
self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0])
|
||||
self.assertIn("You have reached the maximum software configs "
|
||||
"per tenant", str(ex.exc_info[1]))
|
||||
|
||||
def test_create_software_config_structured(self):
|
||||
kwargs = {
|
||||
'group': 'json-file',
|
||||
@ -504,6 +513,14 @@ class SoftwareConfigServiceTest(common.HeatTestCase):
|
||||
self.assertEqual(deployment_id, deployment['id'])
|
||||
self.assertEqual(kwargs['input_values'], deployment['input_values'])
|
||||
|
||||
def test_create_deployment_exceeds_max_per_tenant(self):
|
||||
cfg.CONF.set_override('max_software_deployments_per_tenant', 0)
|
||||
ex = self.assertRaises(dispatcher.ExpectedException,
|
||||
self._create_software_deployment)
|
||||
self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0])
|
||||
self.assertIn("You have reached the maximum software deployments"
|
||||
" per tenant", str(ex.exc_info[1]))
|
||||
|
||||
def test_create_software_deployment_invalid_stack_user_project_id(self):
|
||||
sc_kwargs = {
|
||||
'group': 'Heat::Chef',
|
||||
|
@ -98,6 +98,19 @@ class SnapshotServiceTest(common.HeatTestCase):
|
||||
self.assertIsNotNone(snapshot['creation_time'])
|
||||
mock_load.assert_called_once_with(self.ctx, stack=mock.ANY)
|
||||
|
||||
@mock.patch.object(stack.Stack, 'load')
|
||||
def test_create_snapshot_exceeds_max_per_stack(self, mock_load):
|
||||
stk = self._create_stack('stack_snapshot_exceeds_max')
|
||||
mock_load.return_value = stk
|
||||
|
||||
cfg.CONF.set_override('max_snapshots_per_stack', 0)
|
||||
ex = self.assertRaises(dispatcher.ExpectedException,
|
||||
self.engine.stack_snapshot,
|
||||
self.ctx, stk.identifier(), 'snap_none')
|
||||
self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0])
|
||||
self.assertIn("You have reached the maximum snapshots per stack",
|
||||
str(ex.exc_info[1]))
|
||||
|
||||
@mock.patch.object(stack.Stack, 'load')
|
||||
def test_create_snapshot_action_in_progress(self, mock_load):
|
||||
stack_name = 'stack_snapshot_action_in_progress'
|
||||
|
26
releasenotes/notes/limit-resources-aeb2f24e705840de.yaml
Normal file
26
releasenotes/notes/limit-resources-aeb2f24e705840de.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Heat now supports limiting number of software configs, software
|
||||
deployments, stack snapshots which users can create, by the following
|
||||
config options. These limits are not enforced for users with admin role.
|
||||
|
||||
- ``[DEFAULT] max_software_configis_per_tenant``
|
||||
- ``[DEFAULT] max_software_deployments_per_tenant``
|
||||
- ``[DEFAULT] max_snapshots_per_stack``
|
||||
|
||||
upgrade:
|
||||
- |
|
||||
Now the following limits are enforced by default, unless a request user
|
||||
has admin role.
|
||||
|
||||
- Maximum number of software configs per project is 4096
|
||||
- Maximum number of software deployments per project is 4096
|
||||
- Maximum number of stack snapshots per tenant is 32
|
||||
|
||||
Set the following options in case the limits should be increased. Limits
|
||||
can be disabled by setting -1 to these options.
|
||||
|
||||
- ``[DEFAULT] max_software_configis_per_tenant``
|
||||
- ``[DEFAULT] max_software_deployments_per_tenant``
|
||||
- ``[DEFAULT] max_snapshots_per_stack``
|
Loading…
Reference in New Issue
Block a user