Added notification panel
Implemented notification list. Implements: blueprint masakari-dashboard Change-Id: I9a2558025fe3e49632016ce69b426aefb4975b51
This commit is contained in:
parent
f463ab19f8
commit
f53bdbb031
|
@ -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)
|
||||
|
|
|
@ -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'),)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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,)
|
|
@ -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,)
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -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)
|
|
@ -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'),
|
||||
]
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue