From 289370ab389ae346b0d1b3235e5df07abafd0ea6 Mon Sep 17 00:00:00 2001 From: yushangbin Date: Wed, 5 Jul 2017 11:49:05 +0800 Subject: [PATCH] Add OpenStackClient plugin for triggers This patch adds data protection support to the python-openstackclient through a plugin for trigger. Change-Id: I1a19340f47cbb1713ee8da1b238ea18ea5f3bc92 Partially-Implements: blueprint karbor-support-python-openstackclient --- karborclient/osc/v1/triggers.py | 231 ++++++++++++++++++ .../tests/unit/osc/v1/test_triggers.py | 186 ++++++++++++++ setup.cfg | 5 + 3 files changed, 422 insertions(+) create mode 100644 karborclient/osc/v1/triggers.py create mode 100644 karborclient/tests/unit/osc/v1/test_triggers.py diff --git a/karborclient/osc/v1/triggers.py b/karborclient/osc/v1/triggers.py new file mode 100644 index 0000000..68dccb2 --- /dev/null +++ b/karborclient/osc/v1/triggers.py @@ -0,0 +1,231 @@ +# 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 triggers action implementations""" + +import six + +from osc_lib.command import command +from osc_lib import utils as osc_utils +from oslo_log import log as logging + +from karborclient.common.apiclient import exceptions +from karborclient.i18n import _ +from karborclient import utils + + +class ListTriggers(command.Lister): + _description = _("List triggers.") + + log = logging.getLogger(__name__ + ".ListTriggers") + + def get_parser(self, prog_name): + parser = super(ListTriggers, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Shows details for all tenants. Admin only.'), + ) + parser.add_argument( + '--name', + metavar='', + default=None, + help=_('Filters results by a name. Default=None.'), + ) + parser.add_argument( + '--type', + metavar='', + default=None, + help=_('Filters results by a type. Default=None.'), + ) + parser.add_argument( + '--properties', + metavar='', + default=None, + help=_('Filters results by a properties. Default=None.'), + ) + parser.add_argument( + '--marker', + metavar='', + help=_('The last trigger ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + metavar='', + help=_('Maximum number of triggers to display'), + ) + parser.add_argument( + '--sort', + metavar="[:]", + default=None, + help=_("Sort output by selected keys and directions(asc or desc) " + "(default: name:asc), multiple keys and directions can be " + "specified separated by comma"), + ) + parser.add_argument( + '--project', + metavar='', + help=_('Display information from single tenant (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, + 'name': parsed_args.name, + 'type': parsed_args.type, + 'properties': parsed_args.properties, + } + + data = data_protection_client.triggers.list( + search_opts=search_opts, marker=parsed_args.marker, + limit=parsed_args.limit, sort=parsed_args.sort) + + column_headers = ['Id', 'Name', 'Type', 'Properties'] + + return (column_headers, + (osc_utils.get_item_properties( + s, column_headers + ) for s in data)) + + +class ShowTrigger(command.ShowOne): + _description = "Shows trigger details" + + def get_parser(self, prog_name): + parser = super(ShowTrigger, self).get_parser(prog_name) + parser.add_argument( + 'trigger', + metavar="", + help=_('The UUID of the trigger.') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.data_protection + trigger = osc_utils.find_resource(client.triggers, parsed_args.trigger) + + trigger._info.pop("links", None) + return zip(*sorted(six.iteritems(trigger._info))) + + +class CreateTrigger(command.ShowOne): + _description = "Creates a trigger" + + def get_parser(self, prog_name): + parser = super(CreateTrigger, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('The name of the trigger.') + ) + parser.add_argument( + 'type', + metavar='', + help=_('Type of trigger.') + ) + parser.add_argument( + 'properties', + metavar='', + help=_('Properties of trigger.') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.data_protection + trigger = client.triggers.create(parsed_args.name, parsed_args.type, + parsed_args.properties) + + trigger._info.pop("links", None) + return zip(*sorted(six.iteritems(trigger._info))) + + +class UpdateTrigger(command.ShowOne): + _description = "Update a trigger" + + def get_parser(self, prog_name): + parser = super(UpdateTrigger, self).get_parser(prog_name) + parser.add_argument( + "trigger_id", + metavar="", + help=_("Id of trigger to update.") + ) + parser.add_argument( + "--name", + metavar="", + help=_("A name to which the trigger will be renamed.") + ) + parser.add_argument( + "--properties", + metavar="", + help=_("Properties of trigger which will be updated.") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.data_protection + data = {} + if parsed_args.name is not None: + data['name'] = parsed_args.name + if parsed_args.properties is not None: + trigger_properties = utils.extract_properties(parsed_args) + data['properties'] = trigger_properties + try: + trigger = osc_utils.find_resource(client.triggers, + parsed_args.trigger_id) + trigger = client.triggers.update(trigger.id, data) + except exceptions.NotFound: + raise exceptions.CommandError( + "Trigger %s not found" % parsed_args.trigger_id) + else: + trigger._info.pop("links", None) + return zip(*sorted(six.iteritems(trigger._info))) + + +class DeleteTrigger(command.Command): + _description = "Delete trigger" + + log = logging.getLogger(__name__ + ".DeleteTrigger") + + def get_parser(self, prog_name): + parser = super(DeleteTrigger, self).get_parser(prog_name) + parser.add_argument( + 'trigger', + metavar='', + nargs="+", + help=_('ID of trigger.') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.data_protection + failure_count = 0 + for trigger_id in parsed_args.trigger: + try: + trigger = osc_utils.find_resource(client.triggers, trigger_id) + client.triggers.delete(trigger.id) + except exceptions.NotFound: + failure_count += 1 + self.log.error( + "Failed to delete '{0}'; trigger not found". + format(trigger_id)) + if failure_count == len(parsed_args.trigger): + raise exceptions.CommandError( + "Unable to find and delete any of the " + "specified trigger.") diff --git a/karborclient/tests/unit/osc/v1/test_triggers.py b/karborclient/tests/unit/osc/v1/test_triggers.py new file mode 100644 index 0000000..08a821e --- /dev/null +++ b/karborclient/tests/unit/osc/v1/test_triggers.py @@ -0,0 +1,186 @@ +# 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 triggers as osc_triggers +from karborclient.tests.unit.osc.v1 import fakes +from karborclient.v1 import triggers + + +TRIGGER_INFO = { + "id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa398", + "name": "My backup trigger", + "type": "time", + "properties": { + "format": "calendar", + "pattern": "BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT", + "start_time": "2015-12-17T08:30:00", + "end_time": "2016-03-17T08:30:00", + "window": "3600" + } +} + + +class TestTriggers(fakes.TestDataProtection): + def setUp(self): + super(TestTriggers, self).setUp() + self.triggers_mock = self.app.client_manager.data_protection.triggers + self.triggers_mock.reset_mock() + + +class TestListTriggers(TestTriggers): + def setUp(self): + super(TestListTriggers, self).setUp() + self.triggers_mock.list.return_value = [triggers.Trigger( + None, TRIGGER_INFO)] + + # Command to test + self.cmd = osc_triggers.ListTriggers(self.app, None) + + def test_triggers_list(self): + arglist = ['--name', 'My backup trigger'] + verifylist = [('name', 'My backup trigger')] + + 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', 'Name', 'Type', 'Properties']) + self.assertEqual(expected_columns, columns) + + # Check that data is correct + expected_data = [("2a9ce1f3-cc1a-4516-9435-0ebb13caa398", + "My backup trigger", + "time", + {"format": "calendar", + "pattern": "BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT", # noqa + "start_time": "2015-12-17T08:30:00", + "end_time": "2016-03-17T08:30:00", + "window": "3600"})] + self.assertEqual(expected_data, list(data)) + + +class TestCreateTrigger(TestTriggers): + def setUp(self): + super(TestCreateTrigger, self).setUp() + self.triggers_mock.create.return_value = triggers.Trigger( + None, TRIGGER_INFO) + # Command to test + self.cmd = osc_triggers.CreateTrigger(self.app, None) + + def test_trigger_create(self): + arglist = ['My backup trigger', + 'time', + "'format'='calendar'," + "'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa + "'start_time'='2015-12-17T08:30:00'," + "'end_time'='2016-03-17T08:30:00'," + "'window'='3600'"] + verifylist = [('name', 'My backup trigger'), + ('type', 'time'), + ('properties', "'format'='calendar'," + "'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa + "'start_time'='2015-12-17T08:30:00'," + "'end_time'='2016-03-17T08:30:00'," + "'window'='3600'")] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Check that correct arguments were passed + self.triggers_mock.create.assert_called_once_with( + 'My backup trigger', + 'time', + "'format'='calendar'," + "'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa + "'start_time'='2015-12-17T08:30:00'," + "'end_time'='2016-03-17T08:30:00'," + "'window'='3600'") + + +class TestUpdateTrigger(TestTriggers): + def setUp(self): + super(TestUpdateTrigger, self).setUp() + self.triggers_mock.get.return_value = triggers.Trigger( + None, TRIGGER_INFO) + self.triggers_mock.update.return_value = triggers.Trigger( + None, TRIGGER_INFO) + # Command to test + self.cmd = osc_triggers.UpdateTrigger(self.app, None) + + def test_trigger_update(self): + arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398', + '--name', 'My backup trigger'] + verifylist = [('trigger_id', '2a9ce1f3-cc1a-4516-9435-0ebb13caa398'), + ('name', 'My backup trigger')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Check that correct arguments were passed + self.triggers_mock.update.assert_called_once_with( + '2a9ce1f3-cc1a-4516-9435-0ebb13caa398', + {'name': 'My backup trigger'}) + + +class TestDeleteTrigger(TestTriggers): + def setUp(self): + super(TestDeleteTrigger, self).setUp() + self.triggers_mock.get.return_value = triggers.Trigger( + None, TRIGGER_INFO) + # Command to test + self.cmd = osc_triggers.DeleteTrigger(self.app, None) + + def test_trigger_delete(self): + arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398'] + verifylist = [('trigger', ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398'])] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Check that correct arguments were passed + self.triggers_mock.delete.assert_called_once_with( + '2a9ce1f3-cc1a-4516-9435-0ebb13caa398') + + +class TestShowTrigger(TestTriggers): + def setUp(self): + super(TestShowTrigger, self).setUp() + self.triggers_mock.get.return_value = triggers.Trigger( + None, TRIGGER_INFO) + + # Command to test + self.cmd = osc_triggers.ShowTrigger(self.app, None) + + def test_trigger_show(self): + arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398'] + verifylist = [('trigger', '2a9ce1f3-cc1a-4516-9435-0ebb13caa398')] + + 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', 'name', 'properties', 'type') + self.assertEqual(expected_columns, columns) + + # Check that data is correct + self.assertEqual(TRIGGER_INFO['id'], data[0]) + self.assertEqual(TRIGGER_INFO['name'], data[1]) + self.assertEqual(TRIGGER_INFO['properties'], data[2]) + self.assertEqual(TRIGGER_INFO['type'], data[3]) diff --git a/setup.cfg b/setup.cfg index cca94e1..4aacfdf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,6 +48,11 @@ openstack.data_protection.v1 = data_protection_protectable_show = karborclient.osc.v1.protectables.ShowProtectable data_protection_protectable_instance_list = karborclient.osc.v1.protectables.ListProtectableInstances data_protection_protectable_instance_show = karborclient.osc.v1.protectables.ShowProtectableInstance + data_protection_trigger_list = karborclient.osc.v1.triggers:ListTriggers + data_protection_trigger_show = karborclient.osc.v1.triggers:ShowTrigger + data_protection_trigger_create = karborclient.osc.v1.triggers:CreateTrigger + data_protection_trigger_update = karborclient.osc.v1.triggers:UpdateTrigger + data_protection_trigger_delete = karborclient.osc.v1.triggers:DeleteTrigger [compile_catalog] directory = karborclient/locale