diff --git a/kingbirdclient/api/base.py b/kingbirdclient/api/base.py new file mode 100644 index 0000000..126af67 --- /dev/null +++ b/kingbirdclient/api/base.py @@ -0,0 +1,55 @@ +# Copyright (c) 2016 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 json + + +class Resource(object): + # This will be overridden by the actual resource + resource_name = 'Something' + + def __init__(self, manager, data, values): + self.manager = manager + self._data = data + self._values = values + + +class ResourceManager(object): + resource_class = None + + def __init__(self, http_client): + self.http_client = http_client + + def _list(self, url, response_key=None): + resp = self.http_client.get(url) + if resp.status_code != 200: + self._raise_api_exception(resp) + json_response_key = get_json(resp) + json_objects = [json_response_key[item] for item in json_response_key] + + resource = [] + for json_object in json_objects: + for resource_data in json_object: + resource.append(self.resource_class(self, resource_data, + json_object[resource_data])) + return resource + + +def get_json(response): + """Get JSON representation of response.""" + json_field_or_function = getattr(response, 'json', None) + if callable(json_field_or_function): + return response.json() + else: + return json.loads(response.content) diff --git a/kingbirdclient/api/v1/client.py b/kingbirdclient/api/v1/client.py index 141dd81..fb785c4 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_manager as qm _DEFAULT_KINGBIRD_URL = "http://localhost:8118/v1.0" @@ -73,6 +74,9 @@ class Client(object): insecure=insecure ) + # Create all resource managers + self.quota_manager = qm.quota_manager(self.http_client) + def authenticate(kingbird_url=None, username=None, api_key=None, project_name=None, auth_url=None, diff --git a/kingbirdclient/api/v1/quota_manager.py b/kingbirdclient/api/v1/quota_manager.py new file mode 100644 index 0000000..e2e13da --- /dev/null +++ b/kingbirdclient/api/v1/quota_manager.py @@ -0,0 +1,29 @@ +# Copyright (c) 2016 Ericsson AB. +# All Rights Reserved. +# +# 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 Quota(base.Resource): + resource_name = 'os-quota-sets' + + +class quota_manager(base.ResourceManager): + resource_class = Quota + + def list_defaults(self): + tenant = self.http_client.project_id + url = '/%s/os-quota-sets/defaults' % tenant + return self._list(url) diff --git a/kingbirdclient/commands/v1/__init__.py b/kingbirdclient/commands/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kingbirdclient/commands/v1/base.py b/kingbirdclient/commands/v1/base.py new file mode 100644 index 0000000..3a7ff43 --- /dev/null +++ b/kingbirdclient/commands/v1/base.py @@ -0,0 +1,51 @@ +# Copyright (c) 2016 Ericsson AB +# All Rights Reserved +# +# 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 abc + +from osc_lib.command import command +import six + + +@six.add_metaclass(abc.ABCMeta) +class KingbirdLister(command.Lister): + @abc.abstractmethod + def _get_format_function(self): + raise NotImplementedError + + @abc.abstractmethod + def _get_resources(self, parsed_args): + """Get a list of API resources (e.g. using client).""" + raise NotImplementedError + + def _validate_parsed_args(self, parsed_args): + # No-op by default. + pass + + def take_action(self, parsed_args): + self._validate_parsed_args(parsed_args) + f = self._get_format_function() + + ret = self._get_resources(parsed_args) + if not isinstance(ret, list): + ret = [ret] + + data = [f(r)[1] for r in ret] + + if data: + return f()[0], data + else: + return f() diff --git a/kingbirdclient/commands/v1/quota_manager.py b/kingbirdclient/commands/v1/quota_manager.py new file mode 100644 index 0000000..5572a8a --- /dev/null +++ b/kingbirdclient/commands/v1/quota_manager.py @@ -0,0 +1,44 @@ +# Copyright (c) 2016 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.commands.v1 import base + + +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 ListDefaults(base.KingbirdLister): + """List all default quotas.""" + + def _get_format_function(self): + return format + + def _get_resources(self, parsed_args): + kingbird_client = self.app.client_manager.sync_engine + return kingbird_client.quota_manager.list_defaults() diff --git a/kingbirdclient/shell.py b/kingbirdclient/shell.py index 0fbb797..640fe88 100644 --- a/kingbirdclient/shell.py +++ b/kingbirdclient/shell.py @@ -29,7 +29,7 @@ from cliff import commandmanager from osc_lib.command import command import argparse - +from kingbirdclient.commands.v1 import quota_manager as qm LOG = logging.getLogger(__name__) @@ -290,6 +290,19 @@ class KingbirdShell(app.App): '(Env: KINGBIRDCLIENT_INSECURE)' ) + parser.add_argument( + '--profile', + dest='profile', + metavar='HMAC_KEY', + help='HMAC key to use for encrypting context data for performance ' + 'profiling of operation. This key should be one of the ' + 'values configured for the osprofiler middleware in kingbird,' + 'it is specified in the profiler section of the kingbird ' + 'configuration (i.e. /etc/kingbird/kingbird.conf). ' + 'Without the key, profiling will not be triggered even if ' + 'osprofiler is enabled on the server side.' + ) + return parser def initialize_app(self, argv): @@ -337,7 +350,8 @@ class KingbirdShell(app.App): service_type=self.options.service_type, auth_token=self.options.token, cacert=self.options.cacert, - insecure=self.options.insecure + insecure=self.options.insecure, + profile=self.options.profile ) if not self.options.auth_url and not skip_auth: @@ -348,6 +362,15 @@ class KingbirdShell(app.App): " default url with --os-auth-system or env[OS_AUTH_SYSTEM]") ) + # Adding client_manager variable to make kingbird client work with + # unified OpenStack client. + ClientManager = type( + 'ClientManager', + (object,), + dict(sync_engine=self.client) + ) + self.client_manager = ClientManager() + def _set_shell_commands(self, cmds_dict): for k, v in cmds_dict.items(): self.command_manager.add_command(k, v) @@ -370,6 +393,7 @@ class KingbirdShell(app.App): def _get_commands_v1(): return { 'bash-completion': BashCompletionCommand, + 'quota defaults': qm.ListDefaults, } diff --git a/kingbirdclient/tests/base.py b/kingbirdclient/tests/base.py index 250970c..5644a6d 100644 --- a/kingbirdclient/tests/base.py +++ b/kingbirdclient/tests/base.py @@ -71,7 +71,7 @@ class BaseClientTest(unittest2.TestCase): class BaseCommandTest(unittest2.TestCase): def setUp(self): self.app = mock.Mock() - self.client = self.app.client_manager.workflow_engine + self.client = self.app.client_manager.sync_engine def call(self, command, app_args=[], prog_name=''): cmd = command(self.app, app_args) diff --git a/kingbirdclient/tests/test_shell.py b/kingbirdclient/tests/test_shell.py index 367d2db..b876259 100644 --- a/kingbirdclient/tests/test_shell.py +++ b/kingbirdclient/tests/test_shell.py @@ -31,7 +31,7 @@ class TestShell(base.BaseShellTests): @mock.patch('kingbirdclient.api.client.determine_client_version') def test_default_kingbird_version(self, mock): default_version = 'v1.0' - self.shell('quota-defaults') + self.shell('quota defaults') self.assertTrue(mock.called) kingbird_version = mock.call_args self.assertEqual(default_version, kingbird_version[0][0]) @@ -43,7 +43,7 @@ class TestShell(base.BaseShellTests): '--os-username=admin ' '--os-password=1234 ' '--os-tenant-name=admin ' - 'quota-defaults' + 'quota defaults' ) self.assertTrue(mock.called) params = mock.call_args @@ -57,7 +57,7 @@ class TestShell(base.BaseShellTests): '--os-username=admin ' '--os-password=1234 ' '--os-tenant-name=admin ' - 'quota-defaults' + 'quota defaults' ) self.assertTrue(mock.called) params = mock.call_args @@ -74,7 +74,7 @@ class TestShell(base.BaseShellTests): @mock.patch('kingbirdclient.api.client.client') def test_kb_default_service_type(self, mock): - self.shell('quota-defaults') + self.shell('quota defaults') self.assertTrue(mock.called) params = mock.call_args # Default service type is synchronization @@ -89,7 +89,7 @@ class TestShell(base.BaseShellTests): @mock.patch('kingbirdclient.api.client.client') def test_kb_default_endpoint_type(self, mock): - self.shell('quota-defaults') + self.shell('quota defaults') self.assertTrue(mock.called) params = mock.call_args self.assertEqual('publicURL', params[1]['endpoint_type']) @@ -98,7 +98,7 @@ class TestShell(base.BaseShellTests): def test_os_auth_token(self, mock): self.shell( '--os-auth-token=abcd1234 ' - 'quota-defaults' + 'quota defaults' ) self.assertTrue(mock.called) params = mock.call_args @@ -107,7 +107,7 @@ class TestShell(base.BaseShellTests): @mock.patch('kingbirdclient.api.client.client') def test_command_without_kingbird_url(self, mock): self.shell( - 'quota-defaults' + 'quota defaults' ) self.assertTrue(mock.called) params = mock.call_args @@ -126,8 +126,22 @@ class TestShell(base.BaseShellTests): @mock.patch('kingbirdclient.api.client.client') def test_command_without_project_name(self, mock): self.shell( - 'quota-defaults' + 'quota defaults' ) self.assertTrue(mock.called) params = mock.call_args self.assertEqual('', params[1]['project_name']) + + @mock.patch('kingbirdclient.api.client.client') + def test_kingbird_profile(self, mock): + self.shell('--profile=SECRET_HMAC_KEY quota defaults') + self.assertTrue(mock.called) + params = mock.call_args + self.assertEqual('SECRET_HMAC_KEY', params[1]['profile']) + + @mock.patch('kingbirdclient.api.client.client') + def test_kingbird_without_profile(self, mock): + self.shell('quota defaults') + self.assertTrue(mock.called) + params = mock.call_args + self.assertEqual(None, params[1]['profile']) diff --git a/kingbirdclient/tests/v1/__init__.py b/kingbirdclient/tests/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kingbirdclient/tests/v1/test_quota_manager.py b/kingbirdclient/tests/v1/test_quota_manager.py new file mode 100644 index 0000000..fe47c21 --- /dev/null +++ b/kingbirdclient/tests/v1/test_quota_manager.py @@ -0,0 +1,40 @@ +# Copyright (c) 2016 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_manager as qm +from kingbirdclient.commands.v1 import quota_manager as quota_cmd +from kingbirdclient.tests import base + +QUOTAS_DICT = { + 'Quota': 'fake_item', + 'Limit': '123' +} + +QUOTAMANAGER = qm.Quota(mock, QUOTAS_DICT['Quota'], + QUOTAS_DICT['Limit']) + + +class TestCLIQuotaManagerV1(base.BaseCommandTest): + + def test_list_defaults(self): + self.client.quota_manager.list_defaults.return_value = [QUOTAMANAGER] + actual_quota = self.call(quota_cmd.ListDefaults) + self.assertEqual([('fake_item', '123')], actual_quota[1]) + + def test_negative_list_defaults(self): + self.client.quota_manager.list_defaults.return_value = [] + actual_quota = self.call(quota_cmd.ListDefaults) + self.assertEqual((('', ''),), actual_quota[1])