diff --git a/masakariclient/common/exception.py b/masakariclient/common/exception.py new file mode 100644 index 0000000..317bd76 --- /dev/null +++ b/masakariclient/common/exception.py @@ -0,0 +1,26 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# 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. + + +class BaseException(Exception): + """An error occurred.""" + def __init__(self, message=None): + self.message = message + + def __str__(self): + return self.message or self.__class__.__doc__ + + +class CommandError(BaseException): + """Invalid usage of CLI.""" diff --git a/masakariclient/common/utils.py b/masakariclient/common/utils.py new file mode 100644 index 0000000..cbac0b5 --- /dev/null +++ b/masakariclient/common/utils.py @@ -0,0 +1,45 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# 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 masakariclient.common import exception as exc +from masakariclient.common.i18n import _ + + +def format_parameters(params, parse_semicolon=True): + """Reformat parameters into dict of format expected by the API.""" + if not params: + return {} + + if parse_semicolon: + # expect multiple invocations of --parameters but fall back to ';' + # delimited if only one --parameters is specified + if len(params) == 1: + params = params[0].split(';') + + parameters = {} + for p in params: + try: + (n, v) = p.split(('='), 1) + except ValueError: + msg = _('Malformed parameter(%s). Use the key=value format.') % p + raise exc.CommandError(msg) + + if n not in parameters: + parameters[n] = v + else: + if not isinstance(parameters[n], list): + parameters[n] = [parameters[n]] + parameters[n].append(v) + + return parameters diff --git a/masakariclient/osc/v1/notification.py b/masakariclient/osc/v1/notification.py new file mode 100644 index 0000000..da5b3ca --- /dev/null +++ b/masakariclient/osc/v1/notification.py @@ -0,0 +1,162 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# 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 openstack import exceptions as sdk_exc +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +from oslo_serialization import jsonutils + +from masakariclient.common.i18n import _ +import masakariclient.common.utils as masakari_utils + + +class ListNotification(command.Lister): + """List notifications.""" + + def get_parser(self, prog_name): + parser = super(ListNotification, self).get_parser(prog_name) + parser.add_argument( + '--limit', + metavar='', + help=_('Limit the number of policies returned') + ) + parser.add_argument( + '--marker', + metavar='', + help=_('Only return policies that appear after the given policy ' + 'ID') + ) + parser.add_argument( + '--sort', + metavar='[:]', + help=_("Sorting option which is a string containing a list of " + "keys separated by commas. Each key can be optionally " + "appended by a sort direction (:asc or :desc). The valid " + "sort keys are: ['type', 'name', 'created_at', " + "'updated_at']") + ) + parser.add_argument( + '--filters', + metavar='<"key1=value1;key2=value2...">', + help=_("Filter parameters to apply on returned policies. " + "This can be specified multiple times, or once with " + "parameters separated by a semicolon. The valid filter " + "keys are: ['type', 'name']"), + action='append' + ) + return parser + + def take_action(self, parsed_args): + masakari_client = self.app.client_manager.vmha + columns = ['notification_uuid', 'generated_time', 'status', + 'type', 'source_host_uuid', 'payload'] + queries = { + 'limit': parsed_args.limit, + 'marker': parsed_args.marker, + 'sort': parsed_args.sort, + } + if parsed_args.filters: + queries.update(masakari_utils.format_parameters( + parsed_args.filters)) + + notifications = masakari_client.notifications(**queries) + formatters = {} + return ( + columns, + (utils.get_item_properties(p, columns, formatters=formatters) + for p in notifications) + ) + + +class ShowNotification(command.ShowOne): + """Show notification details.""" + + def get_parser(self, prog_name): + parser = super(ShowNotification, self).get_parser(prog_name) + parser.add_argument( + 'notification', + metavar='', + help='Notification to display (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + masakari_client = self.app.client_manager.vmha + return _show_notification(masakari_client, + notification_uuid=parsed_args.notification) + + +class CreateNotification(command.ShowOne): + """Create notification.""" + + def get_parser(self, prog_name): + parser = super(CreateNotification, self).get_parser(prog_name) + parser.add_argument( + 'type', + metavar='', + help=_('Type of failure.') + ) + parser.add_argument( + 'hostname', + metavar='', + help=_('Hostname of notification.') + ) + parser.add_argument( + 'generated_time', + metavar='', + help=_('Timestamp for notification. e.g. 2016-01-01T01:00:00.000') + ) + parser.add_argument( + 'payload', + metavar='', + help=_('JSON string about failure event.') + ) + return parser + + def take_action(self, parsed_args): + masakari_client = self.app.client_manager.vmha + payload = jsonutils.loads(parsed_args.payload) + attrs = { + 'type': parsed_args.type, + 'hostname': parsed_args.hostname, + 'generated_time': parsed_args.generated_time, + 'payload': payload, + } + + notification = masakari_client.create_notification(**attrs) + return _show_notification(masakari_client, + notification.notification_uuid) + + +def _show_notification(masakari_client, notification_uuid): + try: + notification = masakari_client.get_notification(notification_uuid) + except sdk_exc.ResourceNotFound: + raise exceptions.CommandError(_('Notification not found: %s' + ) % notification_uuid) + + formatters = {} + columns = [ + 'created_at', + 'updated_at', + 'notification_uuid', + 'type', + 'source_host_uuid', + 'generated_time', + 'payload', + ] + return columns, utils.get_dict_properties(notification.to_dict(), columns, + formatters=formatters) diff --git a/masakariclient/sdk/vmha/v1/_proxy.py b/masakariclient/sdk/vmha/v1/_proxy.py index 5372692..840da46 100644 --- a/masakariclient/sdk/vmha/v1/_proxy.py +++ b/masakariclient/sdk/vmha/v1/_proxy.py @@ -14,9 +14,49 @@ from openstack import proxy +from masakariclient.sdk.vmha.v1 import notification as _notification + class Proxy(proxy.BaseProxy): """Proxy class for vmha resource handling. Create method for each action of each API. """ + + def notifications(self, **query): + """Retrieve notifications. + + :param kwargs \*\*query: Optional query parameters to be sent to + limit the notifications being returned. + :returns: A generator of notifications + """ + return self._list(_notification.Notification, paginated=False, **query) + + def get_notification(self, notification): + """Get a single notification. + + :param notification: The value can be the ID of a notification or a + :class: + `~masakariclient.sdk.vmha.v1 + .notification.Notification` instance. + :returns: One :class:`~masakariclient.sdk.vmha.v1 + .notification.Notification` + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + return self._get(_notification.Notification, notification) + + def create_notification(self, **attrs): + """Create a new notification. + + :param dict attrs: Keyword arguments which will be used to create + a :class: + `masakariclient.sdk.vmha.v1 + .notification.Notification`, + comprised of the propoerties on the Notification + class. + :returns: The result of notification creation + :rtype: :class: `masakariclient.sdk.vmha.v1 + .notification.Notification` + """ + return self._create(_notification.Notification, **attrs) diff --git a/masakariclient/sdk/vmha/v1/notification.py b/masakariclient/sdk/vmha/v1/notification.py new file mode 100644 index 0000000..a30ad57 --- /dev/null +++ b/masakariclient/sdk/vmha/v1/notification.py @@ -0,0 +1,51 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# 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 openstack import resource + +from masakariclient.sdk.vmha import vmha_service + + +class Notification(resource.Resource): + resource_key = "notification" + resources_key = "notifications" + base_path = "/notifications" + service = vmha_service.VMHAService() + + # capabilities + # 1] GET /v1/notifications + # 2] GET /v1/notifications/ + # 3] POST /v1/notifications + allow_list = True + allow_retrieve = True + allow_create = True + allow_update = False + allow_delete = False + + # Properties + # Refer "https://github.com/openstack/masakari/tree/ + # master/masakari/api/openstack/ha/schemas/notificaions.py" + # for properties of notifications API + + #: A ID of representing this notification. + id = resource.prop("id") + #: The type of failure. Valuse values include ''COMPUTE_HOST'', + #: ''VM'', ''PROCESS'' + type = resource.prop("type") + #: The hostname of this notification. + hostname = resource.prop("hostname") + #: The generated_time for this notitication. + generated_time = resource.prop("generated_time") + #: The payload of this notification. + payload = resource.prop("payload") diff --git a/requirements.txt b/requirements.txt index cfb2d3a..5282130 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ openstacksdk>=0.9.7 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 +oslo.serialization>=1.10.0 # Apache-2.0 pbr>=1.6 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index c547348..64a0206 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,11 @@ packages = openstack.cli.extension = vmha = masakariclient.plugin +openstack.vmha.v1 = + notification_create = masakariclient.osc.v1.notification:CreateNotification + notification_show = masakariclient.osc.v1.notification:ShowNotification + notification_list = masakariclient.osc.v1.notification:ListNotification + [build_sphinx] source-dir = doc/source build-dir = doc/build