[PATCH V3] [Wok] Implement Asynchronous Notifications backend

* There is no POST method: a notification is added through add_notification() method. * Notifications are always stored in Wok object store under 'notification' type, even if added by a plugin. * Every time Wok is started, all notifications are erased, since this is a UI feature, intended to be showed to the user asynchronously, independent of which tab is opened. Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- docs/API/notifications.md | 36 ++++++++++++++++++++++++++ src/wok/control/notifications.py | 37 ++++++++++++++++++++++++++ src/wok/i18n.py | 3 +++ src/wok/model/notifications.py | 56 ++++++++++++++++++++++++++++++++++++++++ src/wok/objectstore.py | 36 ++++++++++++++++++++++++++ src/wok/server.py | 4 +++ 6 files changed, 172 insertions(+) create mode 100644 docs/API/notifications.md create mode 100644 src/wok/control/notifications.py create mode 100644 src/wok/model/notifications.py Changes in V3: - applied code review suggestions diff --git a/docs/API/notifications.md b/docs/API/notifications.md new file mode 100644 index 0000000..85e8463 --- /dev/null +++ b/docs/API/notifications.md @@ -0,0 +1,36 @@ +## REST API Specification for Notifications + +### Collection: Notifications + +**URI:** /notifications + +**Methods:** + +* **GET**: Retrieve a summarized list of current Notifications + +#### Examples +GET /notifications +[{Notification1}, {Notification2}, ...] + +### Resource: Notification + +**URI:** /notifications/*:id* + +A notification represents an asynchronous warning message sent to the web UI. + +**Methods:** + +* **GET**: Retrieve the full description of the Notification + * code: message ID + * message: message text already translated + * timestamp: first time notification was emitted + +* **DELETE**: Delete the Notification + +#### Examples +GET /notifications/KCHLIBVIRT0001W +{ + code: "KCHLIBVIRT0001W", + message: "KCHLIBVIRT0001W: Lack of storage space in guest vm-1", + timestamp: first time notification was emitted +} diff --git a/src/wok/control/notifications.py b/src/wok/control/notifications.py new file mode 100644 index 0000000..37d45f2 --- /dev/null +++ b/src/wok/control/notifications.py @@ -0,0 +1,37 @@ +# +# Project Wok +# +# Copyright IBM Corp, 2016 +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from wok.control.base import Collection, Resource +from wok.control.utils import UrlSubNode + + +@UrlSubNode('notifications', True) +class Notifications(Collection): + def __init__(self, model): + super(Notifications, self).__init__(model) + self.resource = Notification + + +class Notification(Resource): + def __init__(self, model, id): + super(Notification, self).__init__(model, id) + + @property + def data(self): + return self.info diff --git a/src/wok/i18n.py b/src/wok/i18n.py index e6087f4..5a2876f 100644 --- a/src/wok/i18n.py +++ b/src/wok/i18n.py @@ -45,6 +45,9 @@ messages = { "WOKLOG0001E": _("Invalid filter parameter. Filter parameters allowed: %(filters)s"), "WOKLOG0002E": _("Creation of log file failed: %(err)s"), + "WOKNOT0001E": _("Unable to find notification %(id)s"), + "WOKNOT0002E": _("Unable to delete notification %(id)s: %(message)s"), + "WOKOBJST0001E": _("Unable to find %(item)s in datastore"), "WOKUTILS0001E": _("Unable to reach %(url)s. Make sure it is accessible and try again."), diff --git a/src/wok/model/notifications.py b/src/wok/model/notifications.py new file mode 100644 index 0000000..77184db --- /dev/null +++ b/src/wok/model/notifications.py @@ -0,0 +1,56 @@ +# +# Project Wok +# +# Copyright IBM Corp, 2016 +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from wok.exception import NotFoundError, OperationFailed +from wok.message import WokMessage + + +class NotificationsModel(object): + def __init__(self, **kargs): + self.objstore = kargs['objstore'] + + def get_list(self): + with self.objstore as session: + return session.get_list('notification') + + +class NotificationModel(object): + def __init__(self, **kargs): + self.objstore = kargs['objstore'] + + def lookup(self, id): + with self.objstore as session: + notification = session.get('notification', str(id)) + + # use WokMessage to translate the notification + if notification: + timestamp = notification['timestamp'] + plugin = notification.pop('_plugin_name', None) + message = WokMessage(id, notification, plugin).get_text() + return {"code": id, "message": message, "timestamp": timestamp} + + raise NotFoundError("WOKNOT0001E", {'id': str(id)}) + + def delete(self, id): + try: + with self.objstore as session: + session.delete('notification', str(id)) + except Exception as e: + raise OperationFailed("WOKNOT0002E", {'id': str(id), + 'msg': e.msg()}) diff --git a/src/wok/objectstore.py b/src/wok/objectstore.py index 59354f3..ff3796c 100644 --- a/src/wok/objectstore.py +++ b/src/wok/objectstore.py @@ -23,6 +23,8 @@ import sqlite3 import threading import traceback +from datetime import datetime + try: from collections import OrderedDict except ImportError: @@ -144,3 +146,37 @@ class ObjectStore(object): # exception again wok_log.error(traceback.format_exc()) return False + + +def add_notification(code, args={}, plugin_name=None): + if not code: + wok_log.error("Unable to add notification: invalid code '%(code)s'" % + {'code': str(code)}) + return + + try: + with ObjectStore() as session: + notification = session.get('notification', code) + except NotFoundError: + notification = None + + try: + # do not update timestamp if notification already exists + timestamp = datetime.now().isoformat() if notification is None else \ + notification['timestamp'] + args.update({"_plugin_name": plugin_name, "timestamp": timestamp}) + + with ObjectStore() as session: + session.store('notification', code, args) + except Exception as e: + wok_log.error("Unable to store notification: %s" % e.message) + + +def clean_notifications(): + try: + with ObjectStore() as session: + notifications = session.get_list('notification') + for item in notifications: + session.delete('notification', item) + except Exception as e: + wok_log.error("Unable to clean notifications: %s" % e.message) diff --git a/src/wok/server.py b/src/wok/server.py index 902d4bf..a329ed4 100644 --- a/src/wok/server.py +++ b/src/wok/server.py @@ -33,6 +33,7 @@ from wok.config import config as configParser from wok.config import paths, PluginConfig, WokConfig from wok.control import sub_nodes from wok.model import model +from wok.objectstore import clean_notifications from wok.proxy import start_proxy, terminate_proxy from wok.reqlogger import RequestLogger from wok.root import WokRoot @@ -106,6 +107,9 @@ class Server(object): if dev_env: cherrypy.log.screen = True + # clean object store notifications + clean_notifications() + # close standard file handlers because we are going to use a # watchedfiled handler, otherwise we will have two file handlers # pointing to the same file, duplicating log enries -- 1.9.1
participants (2)
-
Aline Manera
-
Lucio Correia