From cb0b8b0515b29d29104564bca7066bbb84fb96af Mon Sep 17 00:00:00 2001 From: chenying Date: Tue, 18 Jul 2017 15:42:05 +0800 Subject: [PATCH] Add operation log API cmd to karborclient Change-Id: I097ec3424b47939ff00f417bedb186305de39a62 blueprint: operation-log-api --- karborclient/osc/v1/operation_logs.py | 111 ++++++++++++++++ .../tests/unit/osc/v1/test_operation_logs.py | 124 ++++++++++++++++++ .../tests/unit/v1/test_operation_logs.py | 63 +++++++++ karborclient/v1/client.py | 3 + karborclient/v1/operation_logs.py | 44 +++++++ karborclient/v1/shell.py | 90 +++++++++++++ setup.cfg | 2 + 7 files changed, 437 insertions(+) create mode 100644 karborclient/osc/v1/operation_logs.py create mode 100644 karborclient/tests/unit/osc/v1/test_operation_logs.py create mode 100644 karborclient/tests/unit/v1/test_operation_logs.py create mode 100644 karborclient/v1/operation_logs.py diff --git a/karborclient/osc/v1/operation_logs.py b/karborclient/osc/v1/operation_logs.py new file mode 100644 index 0000000..f5d76af --- /dev/null +++ b/karborclient/osc/v1/operation_logs.py @@ -0,0 +1,111 @@ +# 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. + +"""Data protection V1 operation_log action implementations""" + +from osc_lib.command import command +from osc_lib import utils as osc_utils +from oslo_log import log as logging + +from karborclient.i18n import _ + + +class ListOperationLogs(command.Lister): + _description = _("List operation_logs.") + + log = logging.getLogger(__name__ + ".ListOperationLogs") + + def get_parser(self, prog_name): + parser = super(ListOperationLogs, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) + parser.add_argument( + '--status', + metavar='', + help=_('Filter results by status'), + ) + parser.add_argument( + '--marker', + metavar='', + help=_('The last operation_log ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + metavar='', + help=_('Maximum number of operation_logs to display'), + ) + parser.add_argument( + '--sort', + metavar="[:]", + default=None, + help=_("Sort output by selected keys and directions(asc or desc), " + "multiple keys and directions can be " + "specified separated by comma"), + ) + parser.add_argument( + '--project', + metavar='', + help=_('Filter results by a project(admin only)') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + data_protection_client = self.app.client_manager.data_protection + all_projects = bool(parsed_args.project) or parsed_args.all_projects + + search_opts = { + 'all_tenants': all_projects, + 'project_id': parsed_args.project, + 'status': parsed_args.status, + } + + data = data_protection_client.operation_logs.list( + search_opts=search_opts, marker=parsed_args.marker, + limit=parsed_args.limit, sort=parsed_args.sort) + + column_headers = ['Id', 'Operation Type', 'Checkpoint id', + 'Plan Id', 'Provider id', 'Restore Id', + 'Scheduled Operation Id', 'Status', + 'Started At', 'Ended At', 'Error Info', + 'Extra Info'] + + return (column_headers, + (osc_utils.get_item_properties( + s, column_headers + ) for s in data)) + + +class ShowOperationLog(command.ShowOne): + _description = "Shows operation_log details" + + def get_parser(self, prog_name): + parser = super(ShowOperationLog, self).get_parser(prog_name) + parser.add_argument( + 'operation_log', + metavar="", + help=_('The UUID of the operation_log.') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.data_protection + operation_log = osc_utils.find_resource(client.operation_logs, + parsed_args.operation_log) + + operation_log._info.pop("links", None) + return zip(*sorted(operation_log._info.items())) diff --git a/karborclient/tests/unit/osc/v1/test_operation_logs.py b/karborclient/tests/unit/osc/v1/test_operation_logs.py new file mode 100644 index 0000000..dd29949 --- /dev/null +++ b/karborclient/tests/unit/osc/v1/test_operation_logs.py @@ -0,0 +1,124 @@ +# 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 karborclient.osc.v1 import operation_logs as osc_operation_logs +from karborclient.tests.unit.osc.v1 import fakes +from karborclient.v1 import operation_logs + + +OPERATIONLOG_INFO = { + "id": "22b82aa7-9179-4c71-bba2-caf5c0e68db7", + "project_id": "e486a2f49695423ca9c47e589b948108", + "operation_type": "protect", + "checkpoint_id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3", + "plan_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9", + "provider_id": "23902b02-5666-4ee6-8dfe-962ac09c3994", + "restore_id": None, + "scheduled_operation_id": "23902b02-5666-4ee6-8dfe-962ac09c3991", + "started_at": "2015-08-27T09:50:58-05:00", + "ended_at": "2015-08-27T10:50:58-05:00", + "status": "protecting", + "error_info": "Could not access bank", + "extra_info": None +} + + +class TestOperationLogs(fakes.TestDataProtection): + def setUp(self): + super(TestOperationLogs, self).setUp() + self.operation_logs_mock = ( + self.app.client_manager.data_protection.operation_logs) + self.operation_logs_mock.reset_mock() + + +class TestListOperationLogs(TestOperationLogs): + def setUp(self): + super(TestListOperationLogs, self).setUp() + self.operation_logs_mock.list.return_value = [ + operation_logs.OperationLog(None, OPERATIONLOG_INFO)] + + # Command to test + self.cmd = osc_operation_logs.ListOperationLogs(self.app, None) + + def test_operation_logs_list(self): + arglist = ['--status', 'success'] + verifylist = [('status', 'success')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Check that columns are correct + expected_columns = ( + ['Id', 'Operation Type', 'Checkpoint id', 'Plan Id', + 'Provider id', 'Restore Id', 'Scheduled Operation Id', + 'Status', 'Started At', 'Ended At', 'Error Info', + 'Extra Info']) + self.assertEqual(expected_columns, columns) + + # Check that data is correct + expected_data = [("22b82aa7-9179-4c71-bba2-caf5c0e68db7", + "protect", + "dcb20606-ad71-40a3-80e4-ef0fafdad0c3", + "cf56bd3e-97a7-4078-b6d5-f36246333fd9", + "23902b02-5666-4ee6-8dfe-962ac09c3994", + None, + "23902b02-5666-4ee6-8dfe-962ac09c3991", + "protecting", + "2015-08-27T09:50:58-05:00", + "2015-08-27T10:50:58-05:00", + "Could not access bank", + None)] + self.assertEqual(expected_data, list(data)) + + +class TestShowOperationLog(TestOperationLogs): + def setUp(self): + super(TestShowOperationLog, self).setUp() + self.operation_logs_mock.get.return_value = ( + operation_logs.OperationLog(None, OPERATIONLOG_INFO)) + + # Command to test + self.cmd = osc_operation_logs.ShowOperationLog(self.app, None) + + def test_operation_log_show(self): + arglist = ['22b82aa7-9179-4c71-bba2-caf5c0e68db7'] + verifylist = [('operation_log', + '22b82aa7-9179-4c71-bba2-caf5c0e68db7')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Check that columns are correct + expected_columns = ( + 'checkpoint_id', 'ended_at', 'error_info', 'extra_info', + 'id', 'operation_type', 'plan_id', 'project_id', + 'provider_id', 'restore_id', 'scheduled_operation_id', + 'started_at', 'status') + self.assertEqual(expected_columns, columns) + + # Check that data is correct + self.assertEqual(OPERATIONLOG_INFO['checkpoint_id'], data[0]) + self.assertEqual(OPERATIONLOG_INFO['ended_at'], data[1]) + self.assertEqual(OPERATIONLOG_INFO['error_info'], data[2]) + self.assertEqual(OPERATIONLOG_INFO['extra_info'], data[3]) + self.assertEqual(OPERATIONLOG_INFO['id'], data[4]) + self.assertEqual(OPERATIONLOG_INFO['operation_type'], data[5]) + self.assertEqual(OPERATIONLOG_INFO['plan_id'], data[6]) + self.assertEqual(OPERATIONLOG_INFO['project_id'], data[7]) + self.assertEqual(OPERATIONLOG_INFO['provider_id'], data[8]) + self.assertEqual(OPERATIONLOG_INFO['restore_id'], data[9]) + self.assertEqual(OPERATIONLOG_INFO['scheduled_operation_id'], data[10]) + self.assertEqual(OPERATIONLOG_INFO['started_at'], data[11]) + self.assertEqual(OPERATIONLOG_INFO['status'], data[12]) diff --git a/karborclient/tests/unit/v1/test_operation_logs.py b/karborclient/tests/unit/v1/test_operation_logs.py new file mode 100644 index 0000000..7da191e --- /dev/null +++ b/karborclient/tests/unit/v1/test_operation_logs.py @@ -0,0 +1,63 @@ +# 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 karborclient.tests.unit import base +from karborclient.tests.unit.v1 import fakes + +cs = fakes.FakeClient() +mock_request_return = ({}, {'operation_log': {}}) + + +class OperationLogsTest(base.TestCaseShell): + + @mock.patch('karborclient.common.http.HTTPClient.json_request') + def test_list_operation_logs_with_marker_limit(self, mock_request): + mock_request.return_value = mock_request_return + cs.operation_logs.list(marker=1234, limit=2) + mock_request.assert_called_with( + 'GET', + '/operation_logs?limit=2&marker=1234', headers={}) + + @mock.patch('karborclient.common.http.HTTPClient.json_request') + def test_list_operation_logs_with_sort_key_dir(self, mock_request): + mock_request.return_value = mock_request_return + cs.operation_logs.list(sort_key='id', sort_dir='asc') + mock_request.assert_called_with( + 'GET', + '/operation_logs?' + 'sort_dir=asc&sort_key=id', headers={}) + + @mock.patch('karborclient.common.http.HTTPClient.json_request') + def test_list_operation_logs_with_invalid_sort_key(self, mock_request): + self.assertRaises(ValueError, + cs.operation_logs.list, + sort_key='invalid', sort_dir='asc') + + @mock.patch('karborclient.common.http.HTTPClient.json_request') + def test_show_operation_log(self, mock_request): + mock_request.return_value = mock_request_return + cs.operation_logs.get('1') + mock_request.assert_called_with( + 'GET', + '/operation_logs/1', + headers={}) + + @mock.patch('karborclient.common.http.HTTPClient.json_request') + def test_show_operation_log_with_headers(self, mock_request): + mock_request.return_value = mock_request_return + cs.operation_logs.get('1', session_id='fake_session_id') + mock_request.assert_called_with( + 'GET', + '/operation_logs/1', + headers={'X-Configuration-Session': 'fake_session_id'}) diff --git a/karborclient/v1/client.py b/karborclient/v1/client.py index 6e2dcb4..a6e6e04 100644 --- a/karborclient/v1/client.py +++ b/karborclient/v1/client.py @@ -14,6 +14,7 @@ from karborclient.common import http from karborclient.v1 import checkpoints +from karborclient.v1 import operation_logs from karborclient.v1 import plans from karborclient.v1 import protectables from karborclient.v1 import providers @@ -42,3 +43,5 @@ class Client(object): self.triggers = triggers.TriggerManager(self.http_client) self.scheduled_operations = \ scheduled_operations.ScheduledOperationManager(self.http_client) + self.operation_logs = \ + operation_logs.OperationLogManager(self.http_client) diff --git a/karborclient/v1/operation_logs.py b/karborclient/v1/operation_logs.py new file mode 100644 index 0000000..c3c49eb --- /dev/null +++ b/karborclient/v1/operation_logs.py @@ -0,0 +1,44 @@ +# 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 karborclient.common import base + + +class OperationLog(base.Resource): + def __repr__(self): + return "" % self._info + + +class OperationLogManager(base.ManagerWithFind): + resource_class = OperationLog + + def get(self, operation_log_id, session_id=None): + if session_id: + headers = {'X-Configuration-Session': session_id} + else: + headers = {} + url = "/operation_logs/{operation_log_id}".format( + operation_log_id=operation_log_id) + return self._get(url, response_key="operation_log", headers=headers) + + def list(self, detailed=False, search_opts=None, marker=None, limit=None, + sort_key=None, sort_dir=None, sort=None): + """Lists all operation_logs. + + """ + resource_type = "operation_logs" + url = self._build_list_url( + resource_type, detailed=detailed, + search_opts=search_opts, marker=marker, + limit=limit, sort_key=sort_key, + sort_dir=sort_dir, sort=sort) + return self._list(url, 'operation_logs') diff --git a/karborclient/v1/shell.py b/karborclient/v1/shell.py index 24cfda7..59c3c59 100644 --- a/karborclient/v1/shell.py +++ b/karborclient/v1/shell.py @@ -1018,3 +1018,93 @@ def do_scheduledoperation_delete(cs, args): if failure_count == len(args.scheduledoperation): raise exceptions.CommandError("Unable to find and delete any of the " "specified scheduled operation.") + + +@utils.arg('--all-tenants', + dest='all_tenants', + metavar='<0|1>', + nargs='?', + type=int, + const=1, + default=0, + help='Shows details for all tenants. Admin only.') +@utils.arg('--all_tenants', + nargs='?', + type=int, + const=1, + help=argparse.SUPPRESS) +@utils.arg('--status', + metavar='', + default=None, + help='Filters results by a status. Default=None.') +@utils.arg('--marker', + metavar='', + default=None, + help='Begin returning restores that appear later in the ' + 'operation_log list than that represented by this ' + 'operation_logs id. Default=None.') +@utils.arg('--limit', + metavar='', + default=None, + help='Maximum number of operation_logs to return. Default=None.') +@utils.arg('--sort_key', + metavar='', + default=None, + help=argparse.SUPPRESS) +@utils.arg('--sort_dir', + metavar='', + default=None, + help=argparse.SUPPRESS) +@utils.arg('--sort', + metavar='[:]', + default=None, + help=(('Comma-separated list of sort keys and directions in the ' + 'form of [:]. ' + 'Valid keys: %s. ' + 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) +@utils.arg('--tenant', + type=str, + dest='tenant', + nargs='?', + metavar='', + help='Display information from single tenant (Admin only).') +def do_operationlog_list(cs, args): + """Lists all operation_logs.""" + + all_tenants = 1 if args.tenant else \ + int(os.environ.get("ALL_TENANTS", args.all_tenants)) + search_opts = { + 'all_tenants': all_tenants, + 'project_id': args.tenant, + 'status': args.status, + } + + if args.sort and (args.sort_key or args.sort_dir): + raise exceptions.CommandError( + 'The --sort_key and --sort_dir arguments are deprecated and are ' + 'not supported with --sort.') + + operation_logs = cs.operation_logs.list( + search_opts=search_opts, marker=args.marker, + limit=args.limit, sort_key=args.sort_key, + sort_dir=args.sort_dir, sort=args.sort) + + key_list = ['Id', 'Operation Type', 'Checkpoint id', 'Plan Id', + 'Provider id', 'Restore Id', 'Scheduled Operation Id', + 'Status', 'Started At', 'Ended At', 'Error Info', 'Extra Info'] + + if args.sort_key or args.sort_dir or args.sort: + sortby_index = None + else: + sortby_index = 0 + utils.print_list(operation_logs, key_list, exclude_unavailable=True, + sortby_index=sortby_index) + + +@utils.arg('operation_log', + metavar='', + help='ID of operation_log.') +def do_operationlog_show(cs, args): + """Shows operation_log details.""" + operation_log = cs.operation_logs.get(args.operation_log) + utils.print_dict(operation_log.to_dict()) diff --git a/setup.cfg b/setup.cfg index c9e07bd..7a5cecb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,8 @@ openstack.data_protection.v1 = data_protection_scheduledoperation_show = karborclient.osc.v1.scheduled_operations:ShowScheduledOperation data_protection_scheduledoperation_create = karborclient.osc.v1.scheduled_operations:CreateScheduledOperation data_protection_scheduledoperation_delete = karborclient.osc.v1.scheduled_operations:DeleteScheduledOperation + data_protection_operationlog_list = karborclient.osc.v1.operation_logs:ListOperationLogs + data_protection_operationlog_show = karborclient.osc.v1.operation_logs:ShowOperationLog [compile_catalog] directory = karborclient/locale