Added notification panel

Implemented notification list.

Implements: blueprint masakari-dashboard

Change-Id: I9a2558025fe3e49632016ce69b426aefb4975b51
This commit is contained in:
nirajsingh 2018-01-30 18:41:40 +05:30
parent f463ab19f8
commit f53bdbb031
14 changed files with 452 additions and 5 deletions

View File

@ -161,3 +161,23 @@ def update_host(request, host_uuid, failover_segment_id, fields_to_update):
def get_host(request, host_id, segment_id):
"""return single host """
return openstack_connection(request).get_host(host_id, segment_id)
def notification_list(request, filters=None, marker='', paginate=False):
"""return notifications list """
page_size = utils.get_page_size(request)
kwargs = get_request_param(marker, paginate, filters, page_size)
entities_iter = openstack_connection(request).notifications(**kwargs)
has_prev_data = has_more_data = False
if paginate:
entities, has_more_data, has_prev_data = pagination_process(
entities_iter, kwargs['limit'], page_size, marker)
else:
entities = list(entities_iter)
return entities, has_more_data, has_prev_data
def get_notification(request, notification_id):
"""return single notifications"""
return openstack_connection(request).get_notification(notification_id)

View File

@ -23,7 +23,7 @@ from masakaridashboard.default import panel
class MasakariDashboard(horizon.Dashboard):
slug = "masakaridashboard"
name = _("Instance-ha")
panels = ('default', 'segments', 'hosts')
panels = ('default', 'segments', 'hosts', 'notifications')
default_panel = 'default'
policy_rules = (('instance-ha', 'context_is_admin'),)

View File

@ -32,6 +32,7 @@ from masakaridashboard.hosts import tabs as host_tab
class IndexView(tables.DataTableView):
table_class = masakari_tab.HostTable
template_name = 'masakaridashboard/hosts/index.html'
page_title = _("Hosts")
def needs_filter_first(self, table):
return self._needs_filter_first

View File

@ -0,0 +1,27 @@
# Copyright (c) 2018 NTT DATA
#
# 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 django.utils.translation import ugettext_lazy as _
import horizon
from masakaridashboard import dashboard
class Notification(horizon.Panel):
name = _("Notifications")
slug = 'notifications'
dashboard.MasakariDashboard.register(Notification)

View File

@ -0,0 +1,51 @@
# Copyright (c) 2018 NTT DATA
#
# 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 django.utils.translation import ugettext_lazy as _
from horizon import tables
NOTIFICATION_FILTER_CHOICES = (
('source_host_uuid', _("Source Host UUID ="), True),
('type', _("Type ="), True),
('status', _("Status ="), True),
('generated_since', _("Generated Since ="), True),
)
class NotificationFilterAction(tables.FilterAction):
filter_type = "server"
filter_choices = NOTIFICATION_FILTER_CHOICES
class NotificationsTable(tables.DataTable):
source_host_uuid = tables.Column(
'source_host_uuid', verbose_name=_("Host"))
notification_uuid = tables.Column(
'notification_uuid', verbose_name=_("UUID"),
link="horizon:masakaridashboard:notifications:detail")
type = tables.Column('type', verbose_name=_("Type"))
status = tables.Column('status', verbose_name=_("Status"))
payload = tables.Column(
'payload', verbose_name=_("Payload"), truncate=40)
def get_object_id(self, datum):
return datum.notification_uuid
class Meta(object):
name = "notifications"
verbose_name = _("Notifications")
table_actions = (NotificationFilterAction,)

View File

@ -0,0 +1,31 @@
# Copyright (c) 2018 NTT DATA
#
# 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 django.utils.translation import ugettext_lazy as _
from horizon import tabs
class OverviewTab(tabs.Tab):
name = _("Notifications")
slug = "notifications"
template_name = ("masakaridashboard/notifications/_detail_overview.html")
def get_context_data(self, request):
return {"notification": self.tab_group.kwargs['notification']}
class NotificationDetailTabs(tabs.DetailTabsGroup):
slug = "notification_details"
tabs = (OverviewTab,)

View File

@ -0,0 +1,24 @@
{% load i18n sizeformat parse_date %}
<div class="detail">
<h4>{% trans "Notification Detail" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Notification UUID" %}</dt>
<dd>{{ notification.notification_uuid }}</dd>
<dt>{% trans "Source Host UUID" %}</dt>
<dd>{{ notification.source_host_uuid }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ notification.type }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ notification.status }}</dd>
<dt>{% trans "Generated Time" %}</dt>
<dd>{{ notification.generated_time|parse_isotime }}</dd>
<dt>{% trans "Created At" %}</dt>
<dd>{{ notification.created_at|parse_isotime }}</dd>
<dt>{% trans "Updated At" %}</dt>
<dd>{{ notification.updated_at|parse_isotime }}</dd>
<dt>{% trans "Payload" %}</dt>
<dd>{{ notification.payload }}</dd>
</dl>
</div>
</div>

View File

@ -0,0 +1,7 @@
{% extends 'masakaridashboard/default/table.html' %}
{% load i18n %}
{% block title %}{% trans "Notifications" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Notifications") %}
{% endblock page_header %}

View File

@ -0,0 +1,125 @@
# Copyright (c) 2018 NTT DATA
#
# 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 django.conf import settings
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from django.utils.http import urlunquote
import mock
from masakaridashboard.notifications import tables as notification_tab
from masakaridashboard.test import helpers as test
INDEX_URL = reverse('horizon:masakaridashboard:notifications:index')
class NotificationTest(test.TestCase):
def test_index(self):
notifications = self.masakari_notification.list()
with mock.patch(
'masakaridashboard.api.api.notification_list',
return_value=[
notifications, False, False]) as mock_notification_list:
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res,
'masakaridashboard/notifications/index.html')
notifications_from_res = res.context['notifications_table'].data
self.assertItemsEqual(notifications_from_res, notifications)
self.assertEqual(res.status_code, 200)
mock_notification_list.assert_called_once_with(
mock.ANY, filters={}, marker=None, paginate=True)
def _test_notifications_index_paginated(
self, filters, marker, notifications, url, has_more, has_prev):
with mock.patch(
'masakaridashboard.api.api.notification_list',
return_value=[notifications,
has_more, has_prev]) as mock_notification_list:
res = self.client.get(urlunquote(url))
self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res,
'masakaridashboard/notifications/index.html')
mock_notification_list.assert_called_once_with(
mock.ANY, filters=filters, marker=marker, paginate=True)
return res
@override_settings(API_RESULT_PAGE_SIZE=1)
@mock.patch('masakaridashboard.api.api.get_notification')
def test_notifications_index_paginated(self, mock_get_notification):
get_single_notification = self.masakari_notification.list()[0]
mock_get_notification.return_value = get_single_notification
notification_list = self.masakari_notification.list()
size = settings.API_RESULT_PAGE_SIZE
base_url = INDEX_URL
next = notification_tab.NotificationsTable._meta.pagination_param
# get first page
expected_notifications = notification_list[:size]
res = self._test_notifications_index_paginated(
filters={}, marker=None, notifications=expected_notifications,
url=base_url, has_more=True, has_prev=False)
notifications = res.context['notifications_table'].data
self.assertItemsEqual(notifications, expected_notifications)
# get second page
expected_notifications = notification_list[size:2 * size]
marker = expected_notifications[0].id
url = base_url + "?%s=%s" % (next, marker)
res = self._test_notifications_index_paginated(
filters={}, marker=marker, notifications=expected_notifications,
url=url, has_more=True, has_prev=True)
notifications = res.context['notifications_table'].data
self.assertItemsEqual(notifications, expected_notifications)
# get last page
expected_notifications = notification_list[-size:]
marker = expected_notifications[0].id
url = base_url + "?%s=%s" % (next, marker)
res = self._test_notifications_index_paginated(
filters={}, marker=marker, notifications=expected_notifications,
url=url, has_more=False, has_prev=True)
notifications = res.context['notifications_table'].data
self.assertItemsEqual(notifications, expected_notifications)
@override_settings(API_RESULT_PAGE_SIZE=1)
def test_notifications_index_paginated_prev_page(self):
notification_list = self.masakari_notification.list()
size = settings.API_RESULT_PAGE_SIZE
base_url = INDEX_URL
prev = notification_tab.NotificationsTable._meta.prev_pagination_param
# prev from some page
expected_notifications = notification_list[size:2 * size]
marker = expected_notifications[0].id
url = base_url + "?%s=%s" % (prev, marker)
res = self._test_notifications_index_paginated(
filters={}, marker=marker, notifications=expected_notifications,
url=url, has_more=True, has_prev=True)
notifications = res.context['notifications_table'].data
self.assertItemsEqual(notifications, expected_notifications)
# back to first page
expected_notifications = notification_list[:size]
marker = expected_notifications[0].id
url = base_url + "?%s=%s" % (prev, marker)
res = self._test_notifications_index_paginated(
filters={}, marker=marker, notifications=expected_notifications,
url=url, has_more=True, has_prev=False)
notifications = res.context['notifications_table'].data
self.assertItemsEqual(notifications, expected_notifications)

View File

@ -0,0 +1,24 @@
# Copyright (c) 2018 NTT DATA
#
# 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 django.conf.urls import url
from masakaridashboard.notifications import views
NOTIFICATION = r'^(?P<notification_id>[^/]+)/%s$'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(NOTIFICATION % 'detail', views.DetailView.as_view(), name='detail'),
]

View File

@ -0,0 +1,115 @@
# Copyright (c) 2018 NTT DATA
#
# 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 django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tables
from horizon import tabs
from horizon.utils import memoized
from masakaridashboard.api import api
from masakaridashboard.notifications import tables as notification_tab
from masakaridashboard.notifications import tabs as not_tab
class IndexView(tables.DataTableView):
table_class = notification_tab.NotificationsTable
template_name = 'masakaridashboard/notifications/index.html'
page_title = _("Notifications")
_more = False
_prev = False
def needs_filter_first(self, table):
return self._needs_filter_first
def has_more_data(self, table):
return self._more
def has_prev_data(self, table):
return self._prev
def get_data(self):
notification_list = []
marker = self.request.GET.get(
notification_tab.NotificationsTable._meta.pagination_param,
None
)
if marker is not None:
try:
notification = api.get_notification(self.request, marker)
marker = notification.id
except Exception:
msg = _('Unable to get notification "%s".') % marker
redirect = reverse(
'horizon:masakaridashboard:notifications:index')
exceptions.handle(self.request, msg, redirect=redirect)
filters = self.get_filters()
self._needs_filter_first = True
filter_first = getattr(settings, 'FILTER_DATA_FIRST', {})
if filter_first.get('masakaridashboard.notifications', False) and len(
filters) == 0:
self._needs_filter_first = True
self._more = False
return notification_list
try:
notification_list, self._more, self._prev = api.notification_list(
self.request, filters=filters, marker=marker, paginate=True)
except Exception:
self._prev = False
self._more = False
msg = _('Unable to retrieve notification list.')
exceptions.handle(self.request, msg)
return notification_list
class DetailView(tabs.TabbedTableView):
tab_group_class = not_tab.NotificationDetailTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ notification.notification_uuid }}"
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
notification = self.get_data()
table = notification_tab.NotificationsTable(self.request)
context["notification"] = notification
context["url"] = self.get_redirect_url()
context["actions"] = table.render_row_actions(notification)
return context
@memoized.memoized_method
def get_data(self):
try:
notification_id = self.kwargs['notification_id']
notification = api.get_notification(self.request, notification_id)
except Exception:
msg = _('Unable to get notification "%s".') % notification_id
redirect = reverse('horizon:masakaridashboard:notifications:index')
exceptions.handle(self.request, msg, redirect=redirect)
return notification
def get_redirect_url(self):
return reverse('horizon:masakaridashboard:notifications:index')
def get_tabs(self, request, *args, **kwargs):
notification = self.get_data()
return self.tab_group_class(
request, notification=notification, **kwargs)

View File

@ -21,8 +21,8 @@ from masakaridashboard.hosts import tables as host_table
class OverviewTab(tabs.Tab):
name = _("Segments")
slug = "segments"
name = _("Segment")
slug = "segment"
template_name = ("masakaridashboard/segments/_detail_overview.html")
def get_context_data(self, request):

View File

@ -13,15 +13,20 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack.instance_ha.v1 import host
from openstack.instance_ha.v1 import segment
import datetime
from oslo_utils import timeutils
from openstack.instance_ha.v1 import host
from openstack.instance_ha.v1 import notification
from openstack.instance_ha.v1 import segment
from openstack_dashboard.test.test_data import utils as test_data_utils
from masakaridashboard.test import uuidsentinel
from novaclient.v2.hypervisors import Hypervisor
from novaclient.v2.hypervisors import HypervisorManager
NOW = timeutils.utcnow().replace(microsecond=0)
def data(TEST):
@ -57,3 +62,20 @@ def data(TEST):
HypervisorManager, {'id': '1', 'hypervisor_hostname': "test"})
TEST.hypervisors.add(hypervisor1)
TEST.masakari_notification = test_data_utils.TestDataContainer()
notification1 = notification.Notification(
notification_uuid=uuidsentinel.notification1, status='new',
generated_time=(NOW - datetime.timedelta(seconds=2)),
payload='test', type='type1', source_host_uuid=uuidsentinel.host1)
notification2 = notification.Notification(
notification_uuid=uuidsentinel.notification2, status='running',
generated_time=(NOW - datetime.timedelta(seconds=3)),
payload='test', type='type2', source_host_uuid=uuidsentinel.host2)
notification3 = notification.Notification(
notification_uuid=uuidsentinel.notification3, status='error',
generated_time=(NOW - datetime.timedelta(seconds=4)),
payload='test', type='type3', source_host_uuid=uuidsentinel.host3)
TEST.masakari_notification.add(notification1)
TEST.masakari_notification.add(notification2)
TEST.masakari_notification.add(notification3)