Add 'notifications' argument
This can be used for displaying and sending notifications to Fuel. Send an error message: fuel notify -m This is wrong --topic error fuel notifications --send Hello world List all unread notifications: fuel notifications List all notifications: fuel notifications -a Mark messages as read: fuel notifications -r 1 2 Mark all messages as read: fuel notifications -r '*' DocImpact Related-Bug: #1371757 Change-Id: I6a5f05febf8f5a01a7b9415546ef56c11aedefce
This commit is contained in:
parent
2ea7b3e91c
commit
3355e188ee
|
@ -26,6 +26,8 @@ from fuelclient.cli.actions.interrupt import StopAction
|
|||
from fuelclient.cli.actions.network import NetworkAction
|
||||
from fuelclient.cli.actions.node import NodeAction
|
||||
from fuelclient.cli.actions.nodegroup import NodeGroupAction
|
||||
from fuelclient.cli.actions.notifications import NotificationsAction
|
||||
from fuelclient.cli.actions.notifications import NotifyAction
|
||||
from fuelclient.cli.actions.release import ReleaseAction
|
||||
from fuelclient.cli.actions.role import RoleAction
|
||||
from fuelclient.cli.actions.settings import SettingsAction
|
||||
|
@ -51,7 +53,9 @@ actions_tuple = (
|
|||
HealthCheckAction,
|
||||
UserAction,
|
||||
PluginAction,
|
||||
NodeGroupAction
|
||||
NodeGroupAction,
|
||||
NotificationsAction,
|
||||
NotifyAction
|
||||
)
|
||||
|
||||
actions = dict(
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.notifications import Notifications
|
||||
|
||||
|
||||
class NotificationsAction(Action):
|
||||
"""List and create notifications
|
||||
"""
|
||||
action_name = "notifications"
|
||||
|
||||
acceptable_keys = (
|
||||
"id",
|
||||
"message",
|
||||
"status",
|
||||
"topic",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super(NotificationsAction, self).__init__()
|
||||
self.args = [
|
||||
Args.group(
|
||||
Args.get_list_arg("List all available notifications."),
|
||||
Args.get_notify_send_arg("Send notification"),
|
||||
Args.get_notify_mark_as_read_arg(
|
||||
"Mark notification(s) as read ('*' to mark all as read)."),
|
||||
),
|
||||
Args.group(
|
||||
Args.get_notify_topic_arg("Notification topic (severity)"),
|
||||
),
|
||||
Args.get_notify_all_messages_arg(
|
||||
"Select all messages (only unread by default)."),
|
||||
]
|
||||
self.flag_func_map = (
|
||||
("send", self.send),
|
||||
("mark-as-read", self.mark_as_read),
|
||||
(None, self.list),
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""Print all available notifications:
|
||||
fuel notifications
|
||||
fuel notifications --list
|
||||
"""
|
||||
notifications = Notifications.get_all_data()
|
||||
|
||||
if not params.all:
|
||||
notifications = filter(
|
||||
lambda notification: notification['status'] == 'unread',
|
||||
notifications
|
||||
)
|
||||
|
||||
self.serializer.print_to_output(
|
||||
notifications,
|
||||
format_table(
|
||||
notifications,
|
||||
acceptable_keys=self.acceptable_keys
|
||||
)
|
||||
)
|
||||
|
||||
def mark_as_read(self, params):
|
||||
"""Mark given notifications as read.
|
||||
fuel notifications --mark-as-read 1 2
|
||||
fuel notifications -r 1 2
|
||||
"""
|
||||
result = Notifications.mark_as_read(
|
||||
ids=getattr(params, 'mark-as-read'))
|
||||
|
||||
self.serializer.print_to_output(
|
||||
result,
|
||||
'Notification(s) marked as read'
|
||||
)
|
||||
|
||||
def send(self, params):
|
||||
"""Send notification:
|
||||
fuel notifications --send "message" --topic done
|
||||
"""
|
||||
message = params.send
|
||||
if isinstance(message, list):
|
||||
message = ' '.join(message)
|
||||
result = Notifications.send(message, topic=params.topic)
|
||||
self.serializer.print_to_output(
|
||||
result,
|
||||
"Notification sent")
|
||||
|
||||
|
||||
class NotifyAction(NotificationsAction):
|
||||
"""Shortcut for quickly sending a notification.
|
||||
"""
|
||||
|
||||
action_name = "notify"
|
||||
|
||||
def __init__(self):
|
||||
super(NotifyAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_notify_message_arg("Notification message"),
|
||||
Args.get_notify_topic_arg("Notification topic (severity)"),
|
||||
]
|
||||
self.flag_func_map = (
|
||||
(None, self.send),
|
||||
)
|
|
@ -43,7 +43,7 @@ substitutions = {
|
|||
|
||||
def group(*args, **kwargs):
|
||||
required = kwargs.get("required", False)
|
||||
return (required, ) + args
|
||||
return (required,) + args
|
||||
|
||||
|
||||
class TaskAction(argparse.Action):
|
||||
|
@ -146,8 +146,7 @@ def get_fuel_version_arg():
|
|||
|
||||
|
||||
def get_arg(name, flags=None, aliases=None, help_=None, **kwargs):
|
||||
if "_" in name:
|
||||
name = name.replace("_", "-")
|
||||
name = name.replace("_", "-")
|
||||
args = ["--" + name, ]
|
||||
if flags is not None:
|
||||
args.extend(flags)
|
||||
|
@ -305,7 +304,7 @@ def get_skip_tasks():
|
|||
return get_arg(
|
||||
'skip',
|
||||
flags=('--skip',),
|
||||
nargs = '+',
|
||||
nargs='+',
|
||||
default=[],
|
||||
help="Get list of tasks to be skipped.")
|
||||
|
||||
|
@ -314,7 +313,7 @@ def get_tasks():
|
|||
return get_arg(
|
||||
'tasks',
|
||||
flags=('--tasks',),
|
||||
nargs = '+',
|
||||
nargs='+',
|
||||
default=[],
|
||||
help="Get list of tasks to be executed.")
|
||||
|
||||
|
@ -430,3 +429,52 @@ def get_plugin_remove_arg(help_msg):
|
|||
flags=("--remove",),
|
||||
help=help_msg
|
||||
)
|
||||
|
||||
|
||||
def get_notify_all_messages_arg(help_msg):
|
||||
return get_boolean_arg(
|
||||
'all',
|
||||
flags=('-a',),
|
||||
help=help_msg
|
||||
)
|
||||
|
||||
|
||||
def get_notify_mark_as_read_arg(help_msg):
|
||||
return get_str_arg(
|
||||
"mark-as-read",
|
||||
flags=('-r',),
|
||||
nargs='+',
|
||||
help=help_msg,
|
||||
)
|
||||
|
||||
|
||||
def get_notify_message_arg(help_msg):
|
||||
return get_str_arg(
|
||||
"send",
|
||||
nargs='+',
|
||||
flags=('-m',),
|
||||
help=help_msg,
|
||||
)
|
||||
|
||||
|
||||
def get_notify_send_arg(help_msg):
|
||||
return get_str_arg(
|
||||
"send",
|
||||
flags=("--send",),
|
||||
help=help_msg
|
||||
)
|
||||
|
||||
|
||||
def get_notify_topic_arg(help_msg):
|
||||
return get_str_arg(
|
||||
"topic",
|
||||
flags=("--topic",),
|
||||
choices=(
|
||||
'discover',
|
||||
'done',
|
||||
'error',
|
||||
'warning',
|
||||
'release'
|
||||
),
|
||||
help=help_msg
|
||||
)
|
||||
|
|
|
@ -163,7 +163,7 @@ class Parser:
|
|||
for flag in self.universal_flags:
|
||||
self.move_argument_before_action(flag, has_value=False)
|
||||
|
||||
self.move_argument_after_action("--env", )
|
||||
self.move_argument_after_action("--env",)
|
||||
|
||||
def move_argument_before_action(self, flag, has_value=True):
|
||||
"""We need to move general argument before action, we use them
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 fuelclient.cli import error
|
||||
|
||||
from fuelclient.objects import base
|
||||
|
||||
|
||||
class Notifications(base.BaseObject):
|
||||
|
||||
class_api_path = "notifications/"
|
||||
instance_api_path = "notifications/{0}"
|
||||
|
||||
default_topic = 'done'
|
||||
|
||||
@classmethod
|
||||
def mark_as_read(cls, ids=None):
|
||||
if not ids:
|
||||
raise error.BadDataException('Message id not specified.')
|
||||
|
||||
if '*' in ids:
|
||||
data = Notifications.get_all_data()
|
||||
else:
|
||||
try:
|
||||
ids = map(int, ids)
|
||||
except ValueError:
|
||||
raise error.BadDataException(
|
||||
"Numerical ids expected or the '*' symbol.")
|
||||
notifications = Notifications.get_by_ids(ids)
|
||||
|
||||
data = [notification.get_fresh_data()
|
||||
for notification in notifications]
|
||||
|
||||
for notification in data:
|
||||
notification['status'] = 'read'
|
||||
|
||||
resp = cls.connection.put_request(
|
||||
cls.class_api_path, data)
|
||||
|
||||
return resp
|
||||
|
||||
@classmethod
|
||||
def send(cls, message, topic=default_topic):
|
||||
if not topic:
|
||||
topic = cls.default_topic
|
||||
|
||||
if not message:
|
||||
raise error.BadDataException('Message not specified.')
|
||||
|
||||
resp = cls.connection.post_request(
|
||||
cls.class_api_path, {
|
||||
'message': message,
|
||||
'topic': topic,
|
||||
})
|
||||
|
||||
return resp
|
|
@ -0,0 +1,227 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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
|
||||
|
||||
from mock import Mock
|
||||
from mock import patch
|
||||
|
||||
from fuelclient.tests import base
|
||||
|
||||
|
||||
@patch('fuelclient.client.requests')
|
||||
class TestNotificationsActions(base.UnitTestCase):
|
||||
def test_notification_send(self, mrequests):
|
||||
response_mock = Mock(status_code=201)
|
||||
mrequests.post.return_value = response_mock
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'notifications', '--send', 'test message'])
|
||||
self.assertEqual(mrequests.post.call_count, 1)
|
||||
request = json.loads(mrequests.post.call_args[1]['data'])
|
||||
self.assertEqual('test message', request['message'])
|
||||
self.assertEqual('done', request['topic'])
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'notify', '-m', 'test message 2'])
|
||||
self.assertEqual(mrequests.post.call_count, 2)
|
||||
request = json.loads(mrequests.post.call_args[1]['data'])
|
||||
self.assertEqual('test message 2', request['message'])
|
||||
self.assertEqual('done', request['topic'])
|
||||
|
||||
def test_notification_send_with_topic(self, mrequests):
|
||||
response_mock = Mock(status_code=201)
|
||||
mrequests.post.return_value = response_mock
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'notifications', '--send', 'test error',
|
||||
'--topic', 'error'])
|
||||
self.assertEqual(mrequests.post.call_count, 1)
|
||||
request = json.loads(mrequests.post.call_args[1]['data'])
|
||||
self.assertEqual('test error', request['message'])
|
||||
self.assertEqual('error', request['topic'])
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'notify', '-m', 'test error 2', '--topic', 'error'])
|
||||
self.assertEqual(mrequests.post.call_count, 2)
|
||||
request = json.loads(mrequests.post.call_args[1]['data'])
|
||||
self.assertEqual('test error 2', request['message'])
|
||||
self.assertEqual('error', request['topic'])
|
||||
|
||||
def test_notification_send_no_message(self, mrequests):
|
||||
response_mock = Mock(status_code=201)
|
||||
mrequests.post.return_value = response_mock
|
||||
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
self.execute_wo_auth,
|
||||
['fuel', 'notifications', '--send']
|
||||
)
|
||||
self.assertEqual(mrequests.post.call_count, 0)
|
||||
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
self.execute_wo_auth,
|
||||
['fuel', 'notify', '-m']
|
||||
)
|
||||
self.assertEqual(mrequests.post.call_count, 0)
|
||||
|
||||
def test_notification_send_invalid_topic(self, mrequests):
|
||||
response_mock = Mock(status_code=201)
|
||||
mrequests.post.return_value = response_mock
|
||||
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
self.execute_wo_auth,
|
||||
['fuel', 'notifications', '--send', 'test message',
|
||||
'--topic', 'x']
|
||||
)
|
||||
self.assertEqual(mrequests.post.call_count, 0)
|
||||
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
self.execute_wo_auth,
|
||||
['fuel', 'notify', '-m', 'test message', '--topic', 'x']
|
||||
)
|
||||
self.assertEqual(mrequests.post.call_count, 0)
|
||||
|
||||
def test_mark_as_read(self, mrequests):
|
||||
m1 = Mock(status=200)
|
||||
m1.json.return_value = {
|
||||
'id': 1,
|
||||
'message': 'test message',
|
||||
'status': 'unread',
|
||||
'topic': 'done',
|
||||
}
|
||||
m2 = Mock(status=200)
|
||||
m2.json.return_value = {
|
||||
'id': 2,
|
||||
'message': 'test message 2',
|
||||
'status': 'unread',
|
||||
'topic': 'done',
|
||||
}
|
||||
mrequests.get.side_effect = [m1, m2]
|
||||
|
||||
mrequests.put.return_value = Mock(status_code=200)
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'notifications', '-r', '1'])
|
||||
|
||||
self.assertEqual(mrequests.get.call_count, 1)
|
||||
self.assertEqual(mrequests.put.call_count, 1)
|
||||
request = m1.json.return_value
|
||||
self.assertEqual('test message', request['message'])
|
||||
self.assertEqual('read', request['status'])
|
||||
request = m2.json.return_value
|
||||
self.assertEqual('test message 2', request['message'])
|
||||
self.assertEqual('unread', request['status'])
|
||||
|
||||
mrequests.get.side_effect = [m1, m2]
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'notifications', '-r', '1', '2'])
|
||||
|
||||
self.assertEqual(mrequests.get.call_count, 3)
|
||||
self.assertEqual(mrequests.put.call_count, 2)
|
||||
request = m1.json.return_value
|
||||
self.assertEqual('test message', request['message'])
|
||||
self.assertEqual('read', request['status'])
|
||||
request = m2.json.return_value
|
||||
self.assertEqual('test message 2', request['message'])
|
||||
self.assertEqual('read', request['status'])
|
||||
|
||||
def test_mark_all_as_read(self, mrequests):
|
||||
m = Mock(status=200)
|
||||
m.json.return_value = [
|
||||
{
|
||||
'id': 1,
|
||||
'message': 'test message',
|
||||
'status': 'unread',
|
||||
'topic': 'done',
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'message': 'test message 2',
|
||||
'status': 'unread',
|
||||
'topic': 'done',
|
||||
}
|
||||
]
|
||||
mrequests.get.return_value = m
|
||||
|
||||
mrequests.put.return_value = Mock(status_code=200)
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'notifications', '-r', '*'])
|
||||
|
||||
self.assertEqual(mrequests.get.call_count, 1)
|
||||
self.assertEqual(mrequests.put.call_count, 1)
|
||||
request = m.json.return_value
|
||||
self.assertEqual('test message', request[0]['message'])
|
||||
self.assertEqual('read', request[0]['status'])
|
||||
self.assertEqual('test message 2', request[1]['message'])
|
||||
self.assertEqual('read', request[1]['status'])
|
||||
|
||||
@patch('fuelclient.cli.actions.notifications.format_table')
|
||||
def test_list_notifications(self, mformat_table, mrequests):
|
||||
m = Mock(status=200)
|
||||
m.json.return_value = [
|
||||
{
|
||||
'id': 1,
|
||||
'message': 'test message',
|
||||
'status': 'unread',
|
||||
'topic': 'done',
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'message': 'test message 2',
|
||||
'status': 'read',
|
||||
'topic': 'done',
|
||||
}
|
||||
]
|
||||
mrequests.get.return_value = m
|
||||
|
||||
mrequests.put.return_value = Mock(status_code=200)
|
||||
self.execute_wo_auth(['fuel', 'notifications'])
|
||||
|
||||
self.assertEqual(mrequests.get.call_count, 1)
|
||||
notifications = mformat_table.call_args[0][0]
|
||||
self.assertEqual(len(notifications), 1)
|
||||
self.assertDictEqual(notifications[0], m.json.return_value[0])
|
||||
|
||||
@patch('fuelclient.cli.actions.notifications.format_table')
|
||||
def test_list_all_notifications(self, mformat_table, mrequests):
|
||||
m = Mock(status=200)
|
||||
m.json.return_value = [
|
||||
{
|
||||
'id': 1,
|
||||
'message': 'test message',
|
||||
'status': 'unread',
|
||||
'topic': 'done',
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'message': 'test message 2',
|
||||
'status': 'read',
|
||||
'topic': 'done',
|
||||
}
|
||||
]
|
||||
mrequests.get.return_value = m
|
||||
|
||||
mrequests.put.return_value = Mock(status_code=200)
|
||||
self.execute_wo_auth(['fuel', 'notifications', '-a'])
|
||||
|
||||
self.assertEqual(mrequests.get.call_count, 1)
|
||||
notifications = mformat_table.call_args[0][0]
|
||||
self.assertEqual(len(notifications), 2)
|
||||
self.assertListEqual(notifications, m.json.return_value)
|
Loading…
Reference in New Issue