From 45160d33783674c2cde69dc00508b054f44f5daf Mon Sep 17 00:00:00 2001 From: Simon Merrick Date: Wed, 20 Jan 2021 15:10:48 +1300 Subject: [PATCH] Add service helper for trove quota management Change-Id: Ib09692b1451f1493394af755cbcd14cc96a36b02 --- .../actions/v1/tests/test_resource_actions.py | 31 ++++++++-- adjutant/api/v1/tests/test_api_openstack.py | 16 +++-- adjutant/common/openstack_clients.py | 5 ++ adjutant/common/quota.py | 26 ++++++++ adjutant/common/tests/fake_clients.py | 60 +++++++++++++++++++ adjutant/config/quota.py | 15 +++++ ...d-trove-quota-helper-9c5c96a941ac740c.yaml | 5 ++ requirements.txt | 3 +- 8 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/add-trove-quota-helper-9c5c96a941ac740c.yaml diff --git a/adjutant/actions/v1/tests/test_resource_actions.py b/adjutant/actions/v1/tests/test_resource_actions.py index fcd0710..94b2696 100644 --- a/adjutant/actions/v1/tests/test_resource_actions.py +++ b/adjutant/actions/v1/tests/test_resource_actions.py @@ -29,6 +29,7 @@ from adjutant.common.tests.fake_clients import ( get_fake_neutron, get_fake_novaclient, get_fake_cinderclient, + get_fake_troveclient, setup_neutron_cache, neutron_cache, cinder_cache, @@ -36,6 +37,7 @@ from adjutant.common.tests.fake_clients import ( setup_mock_caches, get_fake_octaviaclient, octavia_cache, + trove_cache, ) from adjutant.common.tests.utils import AdjutantTestCase from adjutant.config import CONF @@ -516,6 +518,7 @@ class ProjectSetupActionTests(AdjutantTestCase): @mock.patch( "adjutant.common.openstack_clients.get_octaviaclient", get_fake_octaviaclient ) +@mock.patch("adjutant.common.openstack_clients.get_troveclient", get_fake_troveclient) class QuotaActionTests(AdjutantTestCase): def test_update_quota(self): """ @@ -686,13 +689,15 @@ class QuotaActionTests(AdjutantTestCase): "adjutant.quota.services": [ { "operation": "override", - "value": {"*": ["cinder", "neutron", "nova", "octavia"]}, + "value": {"*": ["cinder", "neutron", "nova", "octavia", "trove"]}, } ] }, ) - def test_update_quota_octavia(self): - """Tests the quota update of the octavia service""" + def test_update_quota_extra_services(self): + """Tests the quota update of extra services over and above + core openstack services. + """ project = mock.Mock() project.id = "test_project_id" project.name = "test_project" @@ -735,6 +740,8 @@ class QuotaActionTests(AdjutantTestCase): self.assertEqual(neutronquota["network"], 10) octaviaquota = octavia_cache["RegionOne"]["test_project_id"]["quota"] self.assertEqual(octaviaquota["load_balancer"], 10) + trove_quota = trove_cache["RegionOne"]["test_project_id"]["quota"] + self.assertEqual(trove_quota["instances"], 20) @conf_utils.modify_conf( CONF, @@ -742,13 +749,15 @@ class QuotaActionTests(AdjutantTestCase): "adjutant.quota.services": [ { "operation": "override", - "value": {"*": ["cinder", "neutron", "nova", "octavia"]}, + "value": {"*": ["cinder", "neutron", "nova", "octavia", "trove"]}, } ] }, ) - def test_update_quota_octavia_over_usage(self): - """When octavia usage is higher than new quota it won't be changed""" + def test_quota_downgrade_fails_when_usage_exceeds_requested_quota(self): + """Ensures that a quota change will fail validation when the + current usage exceeds the requested quota. + """ project = mock.Mock() project.id = "test_project_id" project.name = "test_project" @@ -780,6 +789,13 @@ class QuotaActionTests(AdjutantTestCase): {"id": "fake_id2"}, ] + trove_cache["RegionOne"][project.id]["instances"] = [ + {"id": "fake_id"}, + {"id": "fake_id2"}, + {"id": "fake_id3"}, + {"id": "fake_id4"}, + ] + action = UpdateProjectQuotasAction(data, task=task, order=1) action.prepare() @@ -790,5 +806,8 @@ class QuotaActionTests(AdjutantTestCase): # check the quotas were updated octaviaquota = octavia_cache["RegionOne"]["test_project_id"]["quota"] + trove_quota = trove_cache["RegionOne"]["test_project_id"]["quota"] + # Still set to default self.assertEqual(octaviaquota["load_balancer"], 1) + self.assertEqual(trove_quota["instances"], 3) diff --git a/adjutant/api/v1/tests/test_api_openstack.py b/adjutant/api/v1/tests/test_api_openstack.py index ef32bd2..e1aab20 100644 --- a/adjutant/api/v1/tests/test_api_openstack.py +++ b/adjutant/api/v1/tests/test_api_openstack.py @@ -28,10 +28,12 @@ from adjutant.common.tests.fake_clients import ( get_fake_novaclient, get_fake_cinderclient, get_fake_octaviaclient, + get_fake_troveclient, cinder_cache, nova_cache, neutron_cache, octavia_cache, + trove_cache, setup_mock_caches, setup_quota_cache, FakeResource, @@ -416,6 +418,7 @@ class OpenstackAPITests(AdjutantAPITestCase): @mock.patch( "adjutant.common.openstack_clients.get_octaviaclient", get_fake_octaviaclient ) +@mock.patch("adjutant.common.openstack_clients.get_troveclient", get_fake_troveclient) class QuotaAPITests(AdjutantAPITestCase): def setUp(self): super(QuotaAPITests, self).setUp() @@ -447,6 +450,11 @@ class QuotaAPITests(AdjutantAPITestCase): load_balancer = CONF.quota.sizes.get(size)["octavia"]["load_balancer"] self.assertEqual(octaviaquota["load_balancer"], load_balancer) + if "trove" in extra_services: + trove_quota = trove_cache[region_name][project_id]["quota"] + instance = CONF.quota.sizes.get(size)["trove"]["instances"] + self.assertEqual(trove_quota["instances"], instance) + def test_update_quota_no_history(self): """ Update the quota size of a project with no history """ @@ -1255,13 +1263,13 @@ class QuotaAPITests(AdjutantAPITestCase): "adjutant.quota.services": [ { "operation": "override", - "value": {"*": ["cinder", "neutron", "nova", "octavia"]}, + "value": {"*": ["cinder", "neutron", "nova", "octavia", "trove"]}, }, ], }, ) - def test_update_quota_no_history_with_octavia(self): - """ Update quota for octavia.""" + def test_update_quota_extra_services(self): + """ Update quota for extra services """ project = fake_clients.FakeProject(name="test_project", id="test_project_id") @@ -1290,5 +1298,5 @@ class QuotaAPITests(AdjutantAPITestCase): # Then check to see the quotas have changed self.check_quota_cache( - "RegionOne", project.id, "medium", extra_services=["octavia"] + "RegionOne", project.id, "medium", extra_services=["octavia", "trove"] ) diff --git a/adjutant/common/openstack_clients.py b/adjutant/common/openstack_clients.py index 8a30cbc..9c5e2ef 100644 --- a/adjutant/common/openstack_clients.py +++ b/adjutant/common/openstack_clients.py @@ -21,6 +21,7 @@ from cinderclient import client as cinderclient from neutronclient.v2_0 import client as neutronclient from novaclient import client as novaclient from octaviaclient.api.v2 import octavia +from troveclient.v1 import client as troveclient from adjutant.config import CONF @@ -78,3 +79,7 @@ def get_octaviaclient(region): service = ks.services.list(name="octavia")[0] endpoint = ks.endpoints.list(service=service, region=region, interface="public")[0] return octavia.OctaviaAPI(session=get_auth_session(), endpoint=endpoint.url) + + +def get_troveclient(region): + return troveclient.Client(session=get_auth_session(), region_name=region) diff --git a/adjutant/common/quota.py b/adjutant/common/quota.py index ddf46a0..d8c17e8 100644 --- a/adjutant/common/quota.py +++ b/adjutant/common/quota.py @@ -166,11 +166,37 @@ class QuotaManager(object): ) return usage + class ServiceQuotaTroveHelper(ServiceQuotaHelper): + def __init__(self, region_name, project_id): + self.client = openstack_clients.get_troveclient(region=region_name) + self.project_id = project_id + + def get_quota(self): + project_quota = self.client.quota.show(self.project_id) + + quotas = {} + for quota in project_quota: + quotas[quota.resource] = quota.limit + return quotas + + def set_quota(self, values): + self.client.quota.update(self.project_id, values) + + def get_usage(self): + project_quota = self.client.quota.show(self.project_id) + + usage = {} + for quota in project_quota: + usage[quota.resource] = quota.in_use + + return usage + _quota_updaters = { "cinder": ServiceQuotaCinderHelper, "nova": ServiceQuotaNovaHelper, "neutron": ServiceQuotaNeutronHelper, "octavia": ServiceQuotaOctaviaHelper, + "trove": ServiceQuotaTroveHelper, } def __init__(self, project_id, size_difference_threshold=None): diff --git a/adjutant/common/tests/fake_clients.py b/adjutant/common/tests/fake_clients.py index 10abfa9..c74f702 100644 --- a/adjutant/common/tests/fake_clients.py +++ b/adjutant/common/tests/fake_clients.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import namedtuple from unittest import mock from uuid import uuid4 @@ -23,6 +24,7 @@ neutron_cache = {} nova_cache = {} cinder_cache = {} octavia_cache = {} +trove_cache = {} class FakeProject(object): @@ -731,6 +733,43 @@ class FakeOctaviaClient(object): raise AttributeError +class FakeTroveClient(object): + class FakeTroveQuotaManager(object): + + FakeTroveResource = namedtuple( + "FakeTroveResource", ["resource", "in_use", "reserved", "limit"] + ) + + def __init__(self, region): + + global trove_cache + self.region = region + if region not in trove_cache: + trove_cache[region] = {} + self.cache = trove_cache[region] + + def show(self, project_id): + quota = self.cache[project_id]["quota"] + + quotas = [] + resources = self.cache[project_id]["quota"].keys() + reserved = 0 + for resource in resources: + in_use = len(self.cache[project_id][resource]) + quotas.append( + self.FakeTroveResource(resource, in_use, reserved, quota[resource]) + ) + return quotas + + def update(self, project_id, values): + if project_id not in self.cache: + self.cache[project_id] = {"quota": {}} + self.cache[project_id]["quota"] = values + + def __init__(self, region): + self.quota = self.FakeTroveQuotaManager(region) + + class FakeNovaClient(FakeOpenstackClient): def __init__(self, region): global nova_cache @@ -784,6 +823,22 @@ class FakeResource(object): self.size = size +def setup_trove_cache(region, project_id): + global trove_cache + if region not in trove_cache: + trove_cache[region] = {} + if project_id not in trove_cache[region]: + trove_cache[region][project_id] = {} + + trove_cache[region][project_id] = { + "instances": [], + "backups": [], + "volumes": [], + } + + trove_cache[region][project_id]["quota"] = dict(CONF.quota.sizes["small"]["trove"]) + + def setup_neutron_cache(region, project_id): global neutron_cache if region not in neutron_cache: @@ -884,6 +939,7 @@ def setup_mock_caches(region, project_id): setup_nova_cache(region, project_id) setup_cinder_cache(region, project_id) setup_neutron_cache(region, project_id) + setup_trove_cache(region, project_id) client = FakeOctaviaClient(region) if project_id in octavia_cache[region]: del octavia_cache[region][project_id] @@ -905,3 +961,7 @@ def get_fake_cinderclient(region): def get_fake_octaviaclient(region): return FakeOctaviaClient(region) + + +def get_fake_troveclient(region): + return FakeTroveClient(region) diff --git a/adjutant/config/quota.py b/adjutant/config/quota.py index 32f7c52..4aac73d 100644 --- a/adjutant/config/quota.py +++ b/adjutant/config/quota.py @@ -53,6 +53,11 @@ DEFAULT_QUOTA_SIZES = { "member": 2, "pool": 1, }, + "trove": { + "instances": 3, + "volumes": 3, + "backups": 15, + }, }, "medium": { "cinder": {"gigabytes": 10000, "volumes": 100, "snapshots": 300}, @@ -85,6 +90,11 @@ DEFAULT_QUOTA_SIZES = { "member": 5, "pool": 5, }, + "trove": { + "instances": 10, + "volumes": 10, + "backups": 50, + }, }, "large": { "cinder": {"gigabytes": 50000, "volumes": 200, "snapshots": 600}, @@ -117,6 +127,11 @@ DEFAULT_QUOTA_SIZES = { "member": 10, "pool": 10, }, + "trove": { + "instances": 20, + "volumes": 20, + "backups": 100, + }, }, } diff --git a/releasenotes/notes/add-trove-quota-helper-9c5c96a941ac740c.yaml b/releasenotes/notes/add-trove-quota-helper-9c5c96a941ac740c.yaml new file mode 100644 index 0000000..cb550f2 --- /dev/null +++ b/releasenotes/notes/add-trove-quota-helper-9c5c96a941ac740c.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add a service quota helper for trove to facilitate the management of trove + quotas via adjutant. diff --git a/requirements.txt b/requirements.txt index f39647c..a9d53f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ python-keystoneclient>=3.19.0 python-neutronclient>=6.12.0 python-novaclient>=14.0.0 python-octaviaclient>=1.8.0 +python-troveclient>=6.0.1 six>=1.12.0 confspirator>=0.2.2 -mysqlclient>=1.4.6 \ No newline at end of file +mysqlclient>=1.4.6