From 3333a3c6be6280ffa3d6422a05828ecc56178297 Mon Sep 17 00:00:00 2001 From: Goutham Pratapa Date: Tue, 3 Jan 2017 14:40:46 +0530 Subject: [PATCH] Implement quota-class management commands for KB. "quota-class show" List the quotas for a quota class. "quota-class update" Update quotas for a quota class. "quota-class delete" Delete quotas for a quota-class. Add test-cases for the same. Depends-on: I7c3f809afcf65077701e320fc97135a3c21c7786 Change-Id: I182ef0d84d5fbcca2b443b14d8f4bd6d42735659 --- kingbirdclient/api/base.py | 14 ++ kingbirdclient/api/v1/client.py | 2 + kingbirdclient/api/v1/quota_class_manager.py | 42 ++++ .../commands/v1/quota_class_manager.py | 229 ++++++++++++++++++ kingbirdclient/shell.py | 4 + .../tests/v1/test_quota_class_manager.py | 66 +++++ 6 files changed, 357 insertions(+) create mode 100644 kingbirdclient/api/v1/quota_class_manager.py create mode 100644 kingbirdclient/commands/v1/quota_class_manager.py create mode 100644 kingbirdclient/tests/v1/test_quota_class_manager.py diff --git a/kingbirdclient/api/base.py b/kingbirdclient/api/base.py index 763727f..3993ea5 100644 --- a/kingbirdclient/api/base.py +++ b/kingbirdclient/api/base.py @@ -50,6 +50,20 @@ class ResourceManager(object): resource = self._generate_resource(json_response_key) return resource + def _update(self, url, data): + data = json.dumps(data) + resp = self.http_client.put(url, data) + if resp.status_code != 200: + self._raise_api_exception(resp) + json_response_key = get_json(resp) + result = self._generate_resource(json_response_key) + return result + + def _delete(self, url): + resp = self.http_client.delete(url) + if resp.status_code != 200: + self._raise_api_exception(resp) + def _raise_api_exception(self, resp): error_data = resp.content raise exceptions.APIException(error_code=resp.status_code, diff --git a/kingbirdclient/api/v1/client.py b/kingbirdclient/api/v1/client.py index fb785c4..f20a7ec 100644 --- a/kingbirdclient/api/v1/client.py +++ b/kingbirdclient/api/v1/client.py @@ -19,6 +19,7 @@ import six import osprofiler.profiler from kingbirdclient.api import httpclient +from kingbirdclient.api.v1 import quota_class_manager as qcm from kingbirdclient.api.v1 import quota_manager as qm _DEFAULT_KINGBIRD_URL = "http://localhost:8118/v1.0" @@ -76,6 +77,7 @@ class Client(object): # Create all resource managers self.quota_manager = qm.quota_manager(self.http_client) + self.quota_class_manager = qcm.quota_class_manager(self.http_client) def authenticate(kingbird_url=None, username=None, diff --git a/kingbirdclient/api/v1/quota_class_manager.py b/kingbirdclient/api/v1/quota_class_manager.py new file mode 100644 index 0000000..92de7a2 --- /dev/null +++ b/kingbirdclient/api/v1/quota_class_manager.py @@ -0,0 +1,42 @@ +# Copyright (c) 2017 Ericsson AB. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from kingbirdclient.api import base + + +class QuotaClass(base.Resource): + resource_name = 'os-quota-class-sets' + + +class quota_class_manager(base.ResourceManager): + resource_class = QuotaClass + + def list_quota_class(self, quota_class): + tenant = self.http_client.project_id + url = '/%s/os-quota-class-sets/%s' % (tenant, quota_class) + return self._list(url) + + def quota_class_update(self, quota_class, **kwargs): + if kwargs: + data = dict() + data['quota_class_set'] = { + k: int(v) for k, v in kwargs.items() if v is not None} + tenant = self.http_client.project_id + url = '/%s/os-quota-class-sets/%s' % (tenant, quota_class) + return self._update(url, data) + + def delete_quota_class(self, quota_class): + tenant = self.http_client.project_id + url = '/%s/os-quota-class-sets/%s' % (tenant, quota_class) + return self._delete(url) diff --git a/kingbirdclient/commands/v1/quota_class_manager.py b/kingbirdclient/commands/v1/quota_class_manager.py new file mode 100644 index 0000000..591cc4c --- /dev/null +++ b/kingbirdclient/commands/v1/quota_class_manager.py @@ -0,0 +1,229 @@ +# Copyright (c) 2017 Ericsson AB. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from osc_lib.command import command + +from kingbirdclient.commands.v1 import base +from kingbirdclient import exceptions + + +def format(quotas=None): + columns = ( + 'Quota', + 'Limit' + ) + + if quotas: + data = ( + quotas._data, + quotas._values, + ) + + else: + data = (tuple('' for _ in range(len(columns))),) + + return columns, data + + +class ListQuotaClass(base.KingbirdLister): + """List the quotas for a quota class.""" + + def _get_format_function(self): + return format + + def get_parser(self, parsed_args): + parser = super(ListQuotaClass, self).get_parser(parsed_args) + parser.add_argument( + 'quota_class', + help='Name of quota class to list the quotas.' + ) + return parser + + def _get_resources(self, parsed_args): + kingbird_client = self.app.client_manager.sync_engine + quota_class = parsed_args.quota_class + return kingbird_client.quota_class_manager.\ + list_quota_class(quota_class) + + +class UpdateQuotaClass(base.KingbirdLister): + """Update quotas for a quota class.""" + + def _get_format_function(self): + return format + + def get_parser(self, parsed_args): + parser = super(UpdateQuotaClass, self).get_parser(parsed_args) + + parser.add_argument( + 'quota_class', + help='Name of quota class to update the quotas.' + ) + + parser.add_argument( + '--metadata_items', + help='New value for the "metadata-items" quota' + ) + + parser.add_argument( + '--subnet', + help='New value for the "subnet" quota' + ) + + parser.add_argument( + '--network', + help='New value for the "network" quota' + ) + + parser.add_argument( + '--floatingip', + help='New value for the "floatingip" quota' + ) + + parser.add_argument( + '--gigabytes', + help='New value for the "gigabytes" quota' + ) + + parser.add_argument( + '--backup_gigabytes', + help='New value for the "backup_gigabytes" quota' + ) + parser.add_argument( + '--ram', + help='New value for the "ram" quota' + ) + + parser.add_argument( + '--floating_ips', + help='New value for the "floating_ips" quota' + ) + + parser.add_argument( + '--snapshots', + help='New value for the "snapshots" quota' + ) + + parser.add_argument( + '--security_group_rule', + help='New value for the "security_group_rule" quota' + ) + + parser.add_argument( + '--instances', + help='New value for the "instances" quota' + ) + + parser.add_argument( + '--key_pairs', + help='New value for the "key_pairs" quota' + ) + + parser.add_argument( + '--volumes', + help='New value for the "volumes" quota' + ) + + parser.add_argument( + '--router', + help='New value for the "router" quota' + ) + + parser.add_argument( + '--security_group', + help='New value for the "security_group" quota' + ) + + parser.add_argument( + '--cores', + help='New value for the "cores" quota' + ) + + parser.add_argument( + '--backups', + help='New value for the "backups" quota' + ) + + parser.add_argument( + '--fixed_ips', + help='New value for the "fixed_ips" quota' + ) + + parser.add_argument( + '--port', + help='New value for the "port" quota' + ) + + parser.add_argument( + '--security_groups', + help='New value for the "security_groups" quota' + ) + + return parser + + def _get_resources(self, parsed_args): + quota_class = parsed_args.quota_class + kingbird_client = self.app.client_manager.sync_engine + kwargs = { + "metadata_items": parsed_args.metadata_items, + "subnet": parsed_args.subnet, + "network": parsed_args.network, + "floatingip": parsed_args.floatingip, + "gigabytes": parsed_args.gigabytes, + "backup_gigabytes": parsed_args.backup_gigabytes, + "ram": parsed_args.ram, + "floating_ips": parsed_args.floating_ips, + "snapshots": parsed_args.snapshots, + "security_group_rule": parsed_args.security_group_rule, + "instances": parsed_args.instances, + "key_pairs": parsed_args.key_pairs, + "volumes": parsed_args.volumes, + "router": parsed_args.router, + "security_group": parsed_args.security_group, + "cores": parsed_args.cores, + "backups": parsed_args.backups, + "fixed_ips": parsed_args.fixed_ips, + "port": parsed_args.port, + "security_groups": parsed_args.security_groups + } + return kingbird_client.quota_class_manager.\ + quota_class_update(quota_class, **kwargs) + + +class DeleteQuotaClass(command.Command): + """Delete quotas for a quota-class.""" + + def get_parser(self, prog_name): + parser = super(DeleteQuotaClass, self).get_parser(prog_name) + + parser.add_argument( + 'quota_class', + help='Name of quota class to delete quotas.' + ) + + return parser + + def take_action(self, parsed_args): + kingbird_client = self.app.client_manager.sync_engine + quota_class = parsed_args.quota_class + try: + kingbird_client.quota_class_manager.\ + delete_quota_class(quota_class) + print("Request to delete %s quota_class has been accepted." % + (parsed_args.quota_class)) + except Exception as e: + print(e) + + error_msg = "Unable to delete the specified quota_class." + raise exceptions.KingbirdClientException(error_msg) diff --git a/kingbirdclient/shell.py b/kingbirdclient/shell.py index 36ddc1b..0a34458 100644 --- a/kingbirdclient/shell.py +++ b/kingbirdclient/shell.py @@ -29,6 +29,7 @@ from cliff import commandmanager from osc_lib.command import command import argparse +from kingbirdclient.commands.v1 import quota_class_manager as qcm from kingbirdclient.commands.v1 import quota_manager as qm LOG = logging.getLogger(__name__) @@ -395,6 +396,9 @@ class KingbirdShell(app.App): 'bash-completion': BashCompletionCommand, 'quota defaults': qm.ListDefaults, 'quota show': qm.GlobalLimits, + 'quota-class show': qcm.ListQuotaClass, + 'quota-class update': qcm.UpdateQuotaClass, + 'quota-class delete': qcm.DeleteQuotaClass, } diff --git a/kingbirdclient/tests/v1/test_quota_class_manager.py b/kingbirdclient/tests/v1/test_quota_class_manager.py new file mode 100644 index 0000000..a7ff44e --- /dev/null +++ b/kingbirdclient/tests/v1/test_quota_class_manager.py @@ -0,0 +1,66 @@ +# Copyright (c) 2017 Ericsson AB. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock + +from kingbirdclient.api.v1 import quota_class_manager as qcm +from kingbirdclient.commands.v1 import quota_class_manager as quota_class_cmd +from kingbirdclient.tests import base + +QUOTAS_DICT = { + 'Quota': 'fake_item', + 'Limit': '123' +} + +CLASS_NAME = 'default' + +QUOTACLASSMANAGER = qcm.QuotaClass(mock, QUOTAS_DICT['Quota'], + QUOTAS_DICT['Limit']) + + +class TestCLIQuotaClassManagerV1(base.BaseCommandTest): + + def test_list_quota_class(self): + self.client.quota_class_manager.\ + list_quota_class.return_value = [QUOTACLASSMANAGER] + actual_quota = self.call(quota_class_cmd.ListQuotaClass, + app_args=[CLASS_NAME]) + self.assertEqual([('fake_item', '123')], actual_quota[1]) + + def test_negative_list_quota_class(self): + self.client.quota_class_manager.\ + list_quota_class.return_value = [] + actual_quota = self.call(quota_class_cmd.ListQuotaClass, + app_args=[CLASS_NAME]) + self.assertEqual((('', ''),), actual_quota[1]) + + def test_update_quota_class(self): + self.client.quota_class_manager.\ + quota_class_update.return_value = [QUOTACLASSMANAGER] + actual_quota = self.call(quota_class_cmd.UpdateQuotaClass, + app_args=[CLASS_NAME, '--ram', '51200']) + self.assertEqual([('fake_item', '123')], actual_quota[1]) + + def test_negative_update_quota_class(self): + self.client.quota_class_manager.\ + quota_class_update.return_value = [] + actual_quota = self.call(quota_class_cmd.UpdateQuotaClass, + app_args=[CLASS_NAME, '--ram', '51200']) + self.assertEqual((('', ''),), actual_quota[1]) + + def test_delete_quota_class(self): + self.call(quota_class_cmd.DeleteQuotaClass, + app_args=[CLASS_NAME]) + self.client.quota_class_manager.delete_quota_class.\ + assert_called_once_with(CLASS_NAME)