summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2020-11-11 14:28:09 +0000
committerGerrit Code Review <review@openstack.org>2020-11-11 14:28:09 +0000
commit7a40914de29814e9db6254c36b889a8c890e565c (patch)
treeb42c4f21c310b1fdcefef9af9ad120ba54a4e530
parentc02d2f74ebfc17d439e85bbe1b57ad93829bf536 (diff)
parent5be23d1b20bf96f9605b4ae0cf6d7eb1d17d7a2b (diff)
downloadtrove-7a40914de29814e9db6254c36b889a8c890e565c.tar.gz
Merge "Support ram quota"
-rw-r--r--releasenotes/notes/wallaby-add-ram-quota-d8e64d0385b1429f.yaml7
-rw-r--r--trove/common/cfg.py3
-rw-r--r--trove/instance/models.py43
-rw-r--r--trove/quota/models.py1
-rw-r--r--trove/quota/quota.py1
-rw-r--r--trove/tests/api/limits.py5
-rw-r--r--trove/tests/unittests/api/common/test_limits.py9
7 files changed, 50 insertions, 19 deletions
diff --git a/releasenotes/notes/wallaby-add-ram-quota-d8e64d0385b1429f.yaml b/releasenotes/notes/wallaby-add-ram-quota-d8e64d0385b1429f.yaml
new file mode 100644
index 00000000..951d1ac3
--- /dev/null
+++ b/releasenotes/notes/wallaby-add-ram-quota-d8e64d0385b1429f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Added the ability to quota on total amount of RAM in MB used per project.
+ Set ``quota.max_ram_per_tenant`` to enable. Default is -1 (unlimited)
+ to be backwards compatible. Existing installations will need to manually
+ backfill quote usage for this to work as expected.
diff --git a/trove/common/cfg.py b/trove/common/cfg.py
index 6109a359..9d30af17 100644
--- a/trove/common/cfg.py
+++ b/trove/common/cfg.py
@@ -221,6 +221,9 @@ common_opts = [
default=10,
help='Default maximum number of instances per tenant.',
deprecated_name='max_instances_per_user'),
+ cfg.IntOpt('max_ram_per_tenant',
+ default=-1,
+ help='Default maximum total amount of RAM in MB per tenant.'),
cfg.IntOpt('max_accepted_volume_size', default=10,
help='Default maximum volume size (in GB) for an instance.'),
cfg.IntOpt('max_volumes_per_tenant', default=40,
diff --git a/trove/instance/models.py b/trove/instance/models.py
index 43670086..169e7c77 100644
--- a/trove/instance/models.py
+++ b/trove/instance/models.py
@@ -716,8 +716,8 @@ class BaseInstance(SimpleInstance):
self.update_db(task_status=InstanceTasks.DELETING,
configuration_id=None)
task_api.API(self.context).delete_instance(self.id)
-
- deltas = {'instances': -1}
+ flavor = self.get_flavor()
+ deltas = {'instances': -1, 'ram': -flavor.ram}
if self.volume_support:
deltas['volumes'] = -self.volume_size
return run_with_quotas(self.tenant_id,
@@ -913,6 +913,9 @@ class BaseInstance(SimpleInstance):
except exception.ModelNotFoundError:
pass
+ def get_flavor(self):
+ return self.nova_client.flavors.get(self.flavor_id)
+
@property
def volume_client(self):
if not self._volume_client:
@@ -1153,7 +1156,7 @@ class Instance(BuiltInstance):
cls._validate_remote_datastore(context, region_name, flavor,
datastore, datastore_version)
- deltas = {'instances': 1}
+ deltas = {'instances': 1, 'ram': flavor.ram}
if volume_support:
if replica_source:
try:
@@ -1351,9 +1354,6 @@ class Instance(BuiltInstance):
module_models.InstanceModule.create(
context, instance_id, module.id, module.md5)
- def get_flavor(self):
- return self.nova_client.flavors.get(self.flavor_id)
-
def get_default_configuration_template(self):
flavor = self.get_flavor()
LOG.debug("Getting default config template for datastore version "
@@ -1371,13 +1371,13 @@ class Instance(BuiltInstance):
if self.db_info.cluster_id is not None:
raise exception.ClusterInstanceOperationNotSupported()
- # Validate that the old and new flavor IDs are not the same, new flavor
- # can be found and has ephemeral/volume support if required by the
- # current flavor.
+ # Validate that the old and new flavor IDs are not the same, new
+ # flavor can be found and has ephemeral/volume support if required
+ # by the current flavor.
if self.flavor_id == new_flavor_id:
- raise exception.BadRequest(_("The new flavor id must be different "
- "than the current flavor id of '%s'.")
- % self.flavor_id)
+ raise exception.BadRequest(
+ _("The new flavor id must be different "
+ "than the current flavor id of '%s'.") % self.flavor_id)
try:
new_flavor = self.nova_client.flavors.get(new_flavor_id)
except nova_exceptions.NotFound:
@@ -1390,13 +1390,20 @@ class Instance(BuiltInstance):
elif self.device_path is not None:
# ephemeral support enabled
if new_flavor.ephemeral == 0:
- raise exception.LocalStorageNotSpecified(flavor=new_flavor_id)
+ raise exception.LocalStorageNotSpecified(
+ flavor=new_flavor_id)
- # Set the task to RESIZING and begin the async call before returning.
- self.update_db(task_status=InstanceTasks.RESIZING)
- LOG.debug("Instance %s set to RESIZING.", self.id)
- task_api.API(self.context).resize_flavor(self.id, old_flavor,
- new_flavor)
+ def _resize_flavor():
+ # Set the task to RESIZING and begin the async call before
+ # returning.
+ self.update_db(task_status=InstanceTasks.RESIZING)
+ LOG.debug("Instance %s set to RESIZING.", self.id)
+ task_api.API(self.context).resize_flavor(self.id, old_flavor,
+ new_flavor)
+
+ return run_with_quotas(self.tenant_id,
+ {'ram': new_flavor.ram - old_flavor.ram},
+ _resize_flavor)
def resize_volume(self, new_size):
"""Resize instance volume.
diff --git a/trove/quota/models.py b/trove/quota/models.py
index e2ae0979..03533fd0 100644
--- a/trove/quota/models.py
+++ b/trove/quota/models.py
@@ -75,6 +75,7 @@ class Resource(object):
"""Describe a single resource for quota checking."""
INSTANCES = 'instances'
+ RAM = 'ram'
VOLUMES = 'volumes'
BACKUPS = 'backups'
diff --git a/trove/quota/quota.py b/trove/quota/quota.py
index fd5aa766..56891efe 100644
--- a/trove/quota/quota.py
+++ b/trove/quota/quota.py
@@ -349,6 +349,7 @@ QUOTAS = QuotaEngine()
''' Define all kind of resources here '''
resources = [Resource(Resource.INSTANCES, 'max_instances_per_tenant'),
+ Resource(Resource.RAM, 'max_ram_per_tenant'),
Resource(Resource.BACKUPS, 'max_backups_per_tenant'),
Resource(Resource.VOLUMES, 'max_volumes_per_tenant')]
diff --git a/trove/tests/api/limits.py b/trove/tests/api/limits.py
index 2279bdb3..217d7e01 100644
--- a/trove/tests/api/limits.py
+++ b/trove/tests/api/limits.py
@@ -39,6 +39,7 @@ DEFAULT_RATE = CONF.http_get_rate
DEFAULT_MAX_VOLUMES = CONF.max_volumes_per_tenant
DEFAULT_MAX_INSTANCES = CONF.max_instances_per_tenant
DEFAULT_MAX_BACKUPS = CONF.max_backups_per_tenant
+DEFAULT_MAX_RAM = CONF.max_ram_per_tenant
def ensure_limits_are_not_faked(func):
@@ -109,6 +110,7 @@ class Limits(object):
assert_equal(int(abs_limits.max_instances), DEFAULT_MAX_INSTANCES)
assert_equal(int(abs_limits.max_backups), DEFAULT_MAX_BACKUPS)
assert_equal(int(abs_limits.max_volumes), DEFAULT_MAX_VOLUMES)
+ assert_equal(int(abs_limits.max_ram), DEFAULT_MAX_RAM)
for k in d:
assert_equal(d[k].verb, k)
@@ -132,6 +134,7 @@ class Limits(object):
assert_equal(int(abs_limits.max_instances), DEFAULT_MAX_INSTANCES)
assert_equal(int(abs_limits.max_backups), DEFAULT_MAX_BACKUPS)
assert_equal(int(abs_limits.max_volumes), DEFAULT_MAX_VOLUMES)
+ assert_equal(int(abs_limits.max_ram), DEFAULT_MAX_RAM)
assert_equal(get.verb, "GET")
assert_equal(get.unit, "MINUTE")
assert_true(int(get.remaining) <= DEFAULT_RATE - 5)
@@ -163,6 +166,8 @@ class Limits(object):
DEFAULT_MAX_BACKUPS)
assert_equal(int(abs_limits.max_volumes),
DEFAULT_MAX_VOLUMES)
+ assert_equal(int(abs_limits.max_ram,),
+ DEFAULT_MAX_RAM)
except exceptions.OverLimit:
encountered = True
diff --git a/trove/tests/unittests/api/common/test_limits.py b/trove/tests/unittests/api/common/test_limits.py
index ed79639e..73f0ae7c 100644
--- a/trove/tests/unittests/api/common/test_limits.py
+++ b/trove/tests/unittests/api/common/test_limits.py
@@ -48,6 +48,7 @@ class BaseLimitTestSuite(trove_testtools.TestCase):
self.context = trove_testtools.TroveTestContext(self)
self.absolute_limits = {"max_instances": 55,
"max_volumes": 100,
+ "max_ram": 200,
"max_backups": 40}
@@ -114,6 +115,10 @@ class LimitsControllerTest(BaseLimitTestSuite):
resource="instances",
hard_limit=100),
+ "ram": Quota(tenant_id=tenant_id,
+ resource="ram",
+ hard_limit=200),
+
"backups": Quota(tenant_id=tenant_id,
resource="backups",
hard_limit=40),
@@ -135,6 +140,7 @@ class LimitsControllerTest(BaseLimitTestSuite):
{
'max_instances': 100,
'max_backups': 40,
+ 'max_ram': 200,
'verb': 'ABSOLUTE',
'max_volumes': 55
},
@@ -798,7 +804,7 @@ class LimitsViewsTest(trove_testtools.TestCase):
"resetTime": 1311272226
}
]
- abs_view = {"instances": 55, "volumes": 100, "backups": 40}
+ abs_view = {"instances": 55, "volumes": 100, "backups": 40, 'ram': 200}
view_data = views.LimitViews(abs_view, rate_limits)
self.assertIsNotNone(view_data)
@@ -806,6 +812,7 @@ class LimitsViewsTest(trove_testtools.TestCase):
data = view_data.data()
expected = {'limits': [{'max_instances': 55,
'max_backups': 40,
+ 'max_ram': 200,
'verb': 'ABSOLUTE',
'max_volumes': 100},
{'regex': '.*',