Add Alarm Definition

Make changes to UI to differentiate between alarms and alarm definitions.
At some point the backend of Monasca will change to support this and then
we will use the new APIs. But for now, this exposes the user model but
still uses the existing underlying alarm data model.

Change-Id: I30c71be813f06bef06d13ae618d89114e450e305
This commit is contained in:
Rob Raymond 2014-09-22 09:19:13 -06:00
parent a0fb0a5773
commit 0454625eaf
20 changed files with 1062 additions and 5 deletions

View File

View File

@ -0,0 +1,19 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
URL_PREFIX = 'horizon:monitoring:alarmdefs:'
TEMPLATE_PREFIX = 'monitoring/alarmdefs/'

View File

@ -0,0 +1,299 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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 re
from django import forms as django_forms
from django.template.loader import get_template
from django.template import Context
from django.utils import html
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import exceptions
from horizon import forms
from horizon import messages
from monitoring import api
from monitoring.alarmdefs import constants
class ExpressionWidget(forms.Widget):
def __init__(self, initial, attrs):
super(ExpressionWidget, self).__init__(attrs)
self.initial = initial
def render(self, name, value, attrs):
final_attrs = self.build_attrs(attrs, name=name)
t = get_template(constants.TEMPLATE_PREFIX + 'expression_field.html')
local_attrs = {'service': ''}
local_attrs.update(final_attrs)
context = Context(local_attrs)
return t.render(context)
class SimpleExpressionWidget(django_forms.MultiWidget):
def __init__(self, initial, attrs=None):
comparators = [('>', '>'), ('>=', '>='), ('<', '<'), ('<=', '<=')]
func = [('min', _('min')), ('max', _('max')), ('sum', _('sum')),
('count', _('count')), ('avg', _('avg'))]
_widgets = (
django_forms.widgets.Select(attrs=attrs, choices=func),
ExpressionWidget(initial, attrs={}),
django_forms.widgets.Select(attrs=attrs, choices=comparators),
django_forms.widgets.TextInput(),
)
super(SimpleExpressionWidget, self).__init__(_widgets, attrs)
def decompress(self, expr):
if expr:
return re.search('^(\w+)\((.*)\) ([<>=]*) (.*)$', expr).groups()
else:
return [None, None, None, None]
def format_output(self, rendered_widgets):
return ''.join(rendered_widgets)
def value_from_datadict(self, data, files, name):
values = [
widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
try:
expression = '%s(%s) %s %s' % (values[0],
values[1],
values[2],
values[3])
except ValueError:
return ''
else:
return expression
class NotificationField(forms.MultiValueField):
def __init__(self, *args, **kwargs):
super(NotificationField, self).__init__(*args, **kwargs)
def _get_choices(self):
return self._choices
def _set_choices(self, value):
# Setting choices also sets the choices on the widget.
# choices can be any iterable, but we call list() on it because
# it will be consumed more than once.
self._choices = self.widget.choices = list(value)
choices = property(_get_choices, _set_choices)
def compress(self, data_list):
return data_list
def clean(self, value):
return value
class NotificationCreateWidget(forms.Select):
def __init__(self, *args, **kwargs):
super(NotificationCreateWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None, choices=()):
output = '<table id="notification_table" ' + \
'class="table table-condensed">'
output += '<thead><tr><th>%s</th></tr></thead>' % \
unicode(_("Name"))
if value:
idx = 1
for notification in value:
output += '<tr><td>'
output += ('<select id="id_notifications_%d" ' +
'name="notifications_%d"> ') % (idx, idx)
options = self.render_options(
choices,
[notification['id']])
if options:
output += options
output += '</select>'
output += '<td><a href="" id="remove_notif_button">X</a></td>'
output += '</td></tr>'
idx += 1
else:
output += '<tr><td>'
output += '<select id="id_notifications_1" '
output += 'name="notifications_1"> '
options = self.render_options(choices, [value])
if options:
output += options
output += '</select>'
output += '<td><a href="" id="remove_notif_button">X</a></td>'
output += '</td></tr>'
output += '</table>'
label = unicode(_("+ Add more"))
output += '<a href="" id="add_notification_button">%s</a>' % (label)
return html.format_html(output)
def value_from_datadict(self, data, files, name):
notifications = []
i = 0
while True:
i += 1
notification_id = "%s_%d" % (name, i)
if notification_id in data:
if len(data[notification_id]) > 0:
notifications.append({"id":
data[notification_id]})
else:
break
return notifications
class BaseAlarmForm(forms.SelfHandlingForm):
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def _init_fields(self, readOnly=False, create=False, initial=None):
required = True
textWidget = None
textAreaWidget = forms.Textarea(attrs={'class': 'large-text-area'})
choiceWidget = forms.Select
if create:
expressionWidget = SimpleExpressionWidget(initial)
notificationWidget = NotificationCreateWidget()
else:
expressionWidget = textAreaWidget
notificationWidget = NotificationCreateWidget()
self.fields['name'] = forms.CharField(label=_("Name"),
required=required,
max_length=250,
widget=textWidget)
self.fields['expression'] = forms.CharField(label=_("Expression"),
required=required,
widget=expressionWidget)
self.fields['description'] = forms.CharField(label=_("Description"),
required=False,
widget=textAreaWidget)
sev_choices = [("LOW", _("Low")),
("MEDIUM", _("Medium")),
("HIGH", _("High")),
("CRITICAL", _("Critical"))]
self.fields['severity'] = forms.ChoiceField(label=_("Severity"),
choices=sev_choices,
widget=choiceWidget,
required=False)
self.fields['state'] = forms.CharField(label=_("State"),
required=False,
widget=textWidget)
self.fields['actions_enabled'] = \
forms.BooleanField(label=_("Notifications Enabled"),
required=False,
initial=True)
self.fields['notifications'] = NotificationField(
label=_("Notifications"),
required=False,
widget=notificationWidget)
def set_notification_choices(self, request):
try:
notifications = api.monitor.notification_list(request)
except Exception as e:
notifications = []
exceptions.handle(request,
_('Unable to retrieve notifications: %s') % e)
notification_choices = [(notification['id'], notification['name'])
for notification in notifications]
if notification_choices:
if len(notification_choices) > 1:
notification_choices.insert(
0, ("", unicode(_("Select Notification"))))
else:
notification_choices.insert(
0, ("", unicode(_("No notifications available."))))
self.fields['notifications'].choices = notification_choices
def clean_expression(self):
data = self.cleaned_data['expression']
value = data.split(' ')[2]
if not value.isdigit():
raise forms.ValidationError("Value must be an integer")
# Always return the cleaned data, whether you have changed it or
# not.
return data
class CreateAlarmForm(BaseAlarmForm):
def __init__(self, request, *args, **kwargs):
super(CreateAlarmForm, self).__init__(request, *args, **kwargs)
super(CreateAlarmForm, self)._init_fields(readOnly=False, create=True,
initial=kwargs['initial'])
super(CreateAlarmForm, self).set_notification_choices(request)
self.fields.pop('state')
def handle(self, request, data):
try:
alarm_actions = [notification.get('id')
for notification in data['notifications']]
api.monitor.alarm_create(
request,
name=data['name'],
expression=data['expression'],
description=data['description'],
severity=data['severity'],
alarm_actions=alarm_actions,
ok_actions=alarm_actions,
undetermined_actions=alarm_actions,
)
messages.success(request,
_('Alarm Definition has been created successfully.'))
except Exception as e:
exceptions.handle(request, _('Unable to create the alarm definition: %s') % e.message)
return False
return True
class EditAlarmForm(BaseAlarmForm):
def __init__(self, request, *args, **kwargs):
super(EditAlarmForm, self).__init__(request, *args, **kwargs)
super(EditAlarmForm, self)._init_fields(readOnly=False)
super(EditAlarmForm, self).set_notification_choices(request)
self.fields.pop('state')
def handle(self, request, data):
try:
alarm_actions = []
if data['notifications']:
alarm_actions = [notification.get('id')
for notification in data['notifications']]
api.monitor.alarm_update(
request,
alarm_id=self.initial['id'],
state=self.initial['state'],
severity=data['severity'],
name=data['name'],
expression=data['expression'],
description=data['description'],
actions_enabled=data['actions_enabled'],
alarm_actions=alarm_actions,
ok_actions=alarm_actions,
undetermined_actions=alarm_actions,
)
messages.success(request,
_('Alarm definition has been updated.'))
except Exception as e:
exceptions.handle(request, _('Unable to edit the alarm definition: %s') % e)
return False
return True

View File

@ -0,0 +1,29 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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 _ # noqa
import horizon
from monitoring import dashboard
class AlarmDefinitions(horizon.Panel):
name = _("Alarm Definitions")
slug = 'alarmdefs'
dashboard.Monitoring.register(AlarmDefinitions)

View File

@ -0,0 +1,134 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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 logging
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import tables
from monitoring.alarmdefs import constants
from monitoring import api
LOG = logging.getLogger(__name__)
def show_by_dimension(data, dim_name):
if 'dimensions' in data['expression_data']:
dimensions = data['expression_data']['dimensions']
if dim_name in dimensions:
return str(data['expression_data']['dimensions'][dim_name])
return ""
class CreateAlarm(tables.LinkAction):
name = "create_alarm"
verbose_name = _("Create Alarm Definition")
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (("alarm", "alarm:create"),)
ajax = True
def get_link_url(self):
return urlresolvers.reverse(constants.URL_PREFIX + 'alarm_create',
args=())
def allowed(self, request, datum=None):
return True
class EditAlarm(tables.LinkAction):
name = "edit_alarm"
verbose_name = _("Edit Alarm Definition")
classes = ("ajax-modal", "btn-create")
def get_link_url(self, datum):
return urlresolvers.reverse(constants.URL_PREFIX + 'alarm_edit',
args=(
datum['id'], ))
def allowed(self, request, datum=None):
return True
class GraphMetric(tables.LinkAction):
name = "graph_alarm"
verbose_name = _("Graph Metric")
icon = "dashboard"
def render(self):
self.attrs['target'] = 'newtab'
return super(self, GraphMetric).render()
def get_link_url(self, datum):
name = datum['expression_data']['metric_name']
threshold = datum['expression_data']['threshold']
self.attrs['target'] = '_blank'
return "/static/grafana/index.html#/dashboard/script/detail.js?token=%s&name=%s&threshold=%s" % \
(self.table.request.user.token.id, name, threshold)
def allowed(self, request, datum=None):
return 'metric_name' in datum['expression_data']
class DeleteAlarm(tables.DeleteAction):
name = "delete_alarm"
verbose_name = _("Delete Alarm Definition")
data_type_singular = _("Alarm Definition")
data_type_plural = _("Alarm Definitions")
def allowed(self, request, datum=None):
return True
def delete(self, request, obj_id):
api.monitor.alarm_delete(request, obj_id)
class AlarmsFilterAction(tables.FilterAction):
def filter(self, table, alarms, filter_string):
"""Naive case-insensitive search."""
q = filter_string.lower()
return [alarm for alarm in alarms
if q in alarm.name.lower()]
class AlarmsTable(tables.DataTable):
target = tables.Column('name', verbose_name=_('Name'),
link=constants.URL_PREFIX + 'alarm_detail',
)
description = tables.Column('description', verbose_name=_('Description'))
enabled = tables.Column('actions_enabled',
verbose_name=_('Notifications Enabled'))
def get_object_id(self, obj):
return obj['id']
def get_object_display(self, obj):
return obj['name']
class Meta:
name = "alarms"
verbose_name = _("Alarm Definitions")
row_actions = (GraphMetric,
EditAlarm,
DeleteAlarm,
)
table_actions = (CreateAlarm,
AlarmsFilterAction,
DeleteAlarm,
)

View File

@ -0,0 +1,56 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}create_alarm_form{% endblock %}
{% block form_action %}{{ action_url }}{% endblock %}
{% block modal-header %}{% trans "Create Alarm" %}{% endblock %}
{% block modal-body %}
<h3>{% trans "Description" %}:</h3>
<p>{% blocktrans %}
The Name field is used to identify the notification method.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Expression field which if true, triggers a notification to be sent.
See <a href="http://docs.hpcloud.com/api/v13/monitoring/#AlarmExpressions-jumplink-span" target="_blank">Alarm Expressions</a> for how to write an expression.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Alarm Actions field contains the list of Notification that should be sent when transitioning to an ALARM state.
{% endblocktrans %}</p>
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
<script type="text/javascript">
$('#add_notification_button').click(function(){
num = $('#notification_table tr').length;
id = "id_notifications_" + num;
name = "notifications_" + num;
options = $("#id_notifications_" + (num - 1)).html();
options = options.replace('selected="selected"','');
row = '<tr><td><select id="' + id +'" name="' + name + '">' + options + '</select></td><td><a href="" id="remove_notif_button">X</a></td></tr>'
$('#notification_table tr:last').after(row);
return false;
});
$('#notification_table').on('click', '#remove_notif_button', (function(event){
var target = $(event.target.parentNode.parentNode);
target.remove();
return false;
}));
metricsList = {{ metrics | safe }}
</script>
<style>
#id_expression_2 {
width: 60px;
}
</style>
<link href='{{ STATIC_URL }}/monitoring/css/ng-tags-input.css' type="text/css" rel="stylesheet"/>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Alarm" %}" />
<a href="{{ cancel_url }}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% load i18n sizeformat %}
<div class="info row detail">
<h4>{% trans "Info" %}</h4>
<hr class="header_rule">
<dl>
<dt>{% trans "Name" %}</dt>
<dd>{{ alarm.name|default:_("None") }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ alarm.description }}</dd>
<dt>{% trans "Expression" %}</dt>
<dd>{{ alarm.expression }}</dd>
<dt>{% trans "Severity" %}</dt>
<dd>{{ alarm.severity }}</dd>
<dt>{% trans "Notifications Enabled" %}</dt>
<dd>{{ alarm.actions_enabled }}</dd>
</dl>
</div>
<div class="specs row detail">
<h4>{% trans "Notifications" %}</h4>
<hr class="header_rule">
<table class="table table-condensed">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Address" %}</th>
</tr>
</thead>
{% for row in alarm.notifications %}
<tr>
<td>{{ row.name }}</td>
<td>{{ row.type }}</td>
<td>{{ row.address }}</td>
</tr>
{% endfor %}
</table>
</div>

View File

@ -0,0 +1,50 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}edit_alarm_form{% endblock %}
{% block form_action %}{{ action_url }}{% endblock %}
{% block modal-header %}{% trans "Edit Alarm Definition" %}{% endblock %}
{% block modal-body %}
<h3>{% trans "Description" %}:</h3>
<p>{% blocktrans %}
The Name field is used to identify the notification method.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Expression field which if true, triggers a notification to be sent.
See <a href="http://docs.hpcloud.com/api/v13/monitoring/#AlarmExpressions-jumplink-span" target="_blank">Alarm Expressions</a> for how to write an expression.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Alarm Actions field contains the list of Notification that should be sent when transitioning to an ALARM state.
{% endblocktrans %}</p>
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
<script type="text/javascript">
$('#add_notification_button').click(function(){
num = $('#notification_table tr').length;
id = "id_notifications_" + num;
name = "notifications_" + num;
options = $("#id_notifications_" + (num - 1)).html();
options = options.replace('selected="selected"','');
row = '<tr><td><select id="' + id +'" name="' + name + '">' + options + '</select></td><td><a href="" id="remove_notif_button">X</a></td></tr>'
$('#notification_table tr:last').after(row);
return false;
});
$('#notification_table').on('click', '#remove_notif_button', (function(event){
var target = $(event.target.parentNode.parentNode);
target.remove();
return false;
}));
</script>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save" %}" />
<a href="{{ cancel_url }}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% load i18n %}
{% load url from future %}
{% block title %}{% trans 'Alarm Definitions' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Alarm Definitions" %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% load i18n %}
{% load url from future %}
{% block title %}{% trans 'Alarm History' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Alarm History" %}
{% endblock page_header %}
{% block manage_overview %}
{% block page_title %}
<div class="page_title table_header">
<div>
<h3>
<a href="{% url 'horizon:noc_tools:service_health:index' %}" class="showspinner">
{% trans "Service Health" %}</a> : {{ region_name }} - &gt;
{% trans "Service :" %} {{ service }}
</h3>
</div>
<div class="table_actions">
<a href="{% url 'horizon:noc_tools:service_health:index' %}" class="close showspinner">&times;</a>
</div>
</div>
{% endblock page_title %}
{% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,52 @@
{% extends 'base.html' %}
{% load i18n %}
{% load url from future %}
{% block title %}{% trans 'Measurements for Alarms' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Measurements that Triggered Alarm" %}
{% endblock page_header %}
{% block manage_overview %}
{% block page_title %}
<div class="page_title table_header">
<div>
<h3>
<a href="{% url 'horizon:noc_tools:service_health:index' %}" class="showspinner">
{% trans "Service Health" %}</a> : {{ region_name }} - &gt;
{% trans "Service :" %} {{ service }}
</h3>
</div>
<div class="table_actions">
<a href="{% url 'horizon:noc_tools:service_health:index' %}" class="close showspinner">&times;</a>
</div>
</div>
{% endblock page_title %}
{% endblock %}
{% block main %}
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Time', 'Response Time', 'Threshold'],
['8:42', 1000, 1200],
['8:43', 1170, 1200],
['8:44', 1190, 1200],
['8:45', 1230, 1200]
]);
var options = {
title: 'API Response Time'
};
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
<div id="chart_div" style="width: 900px; height: 500px;"></div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Create Alarm Definition' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Create Alarm Definition" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/alarmdefs/_create.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Alarm Definition Details' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Alarm Definition Details" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/alarmdefs/_detail.html' %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Edit Alarm Definitions' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Edit Alarm Definitions" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/alarmdefs/_edit.html' %}
{% endblock %}

View File

@ -0,0 +1,31 @@
{% load i18n %}
<div ng-controller="alarmEditController" ng-init="init('{{ service }}')">
<input type="hidden" name="{{ name }}" id="dimension"/>
<select id="metric-chooser" class="form-control"
ng-model="currentMetric"
ng-options="metric for metric in metricNames"
ng-change="metricChanged()"></select>
<tags-input id="dimension-chooser" ng-model="tags"
placeholder="{% trans 'Add a dimension' %}"
add-from-autocomplete-only="true"
on-tag-added="saveDimension()" on-tag-removed="saveDimension()">
<auto-complete source="possibleDimensions()"
max-results-to-show="30" min-length="1">
</auto-complete>
</tags-input>
<div class="topologyBalloon" id="metrics" style="position:static;display: block;">
<div class="contentBody">
<table class="detailInfoTable">
<caption>Matching Metrics</caption>
<tbody>
<tr>
<th ng-repeat="name in dimnames">{$name$}</th>
</tr>
<tr ng-repeat="metric in matchingMetrics">
<td ng-repeat="dim in dimnames">{$metric[dim]$}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,52 @@
from django.core import urlresolvers
from mock import patch, call # noqa
from monitoring.test import helpers
from monitoring.alarmdefs import constants
INDEX_URL = urlresolvers.reverse(
constants.URL_PREFIX + 'index')
CREATE_URL = urlresolvers.reverse(
constants.URL_PREFIX + 'alarm_create', args=())
DETAIL_URL = urlresolvers.reverse(
constants.URL_PREFIX + 'alarm_detail', args=('12345',))
class AlarmDefinitionsTest(helpers.TestCase):
def test_alarmdefs_get(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['alarm_list'],
'alarm_list.return_value': [],
}) as mock:
res = self.client.get(INDEX_URL)
self.assertEqual(mock.alarm_list.call_count, 1)
self.assertTemplateUsed(
res, 'monitoring/alarmdefs/alarm.html')
def test_alarmdefs_create(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['notification_list', 'metrics_list'],
'notification_list.return_value': [],
'metrics_list.return_value': [],
}) as mock:
res = self.client.get(CREATE_URL)
self.assertEqual(mock.notification_list.call_count, 1)
self.assertEqual(mock.metrics_list.call_count, 1)
self.assertTemplateUsed(
res, 'monitoring/alarmdefs/_create.html')
def test_alarmdefs_detail(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['alarm_get'],
'alarm_get.return_value': {
'alarm_actions': []
}
}) as mock:
res = self.client.get(DETAIL_URL)
self.assertEqual(mock.alarm_get.call_count, 1)
self.assertTemplateUsed(
res, 'monitoring/alarmdefs/_detail.html')

View File

@ -0,0 +1,33 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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 patterns # noqa
from django.conf.urls import url # noqa
from monitoring.alarmdefs import views
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^alarm/create$',
views.AlarmCreateView.as_view(),
name='alarm_create'),
url(r'^(?P<id>[^/]+)/alarm_detail/$',
views.AlarmDetailView.as_view(),
name='alarm_detail'),
url(r'^alarm/(?P<id>[^/]+)/alarm_edit/$',
views.AlarmEditView.as_view(),
name='alarm_edit'),
)

View File

@ -0,0 +1,190 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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 logging
import json
from django.contrib import messages
from django.core.urlresolvers import reverse_lazy, reverse # noqa
from django.template import defaultfilters as filters
from django.utils.translation import ugettext as _ # noqa
from django.views.generic import View # noqa
from django.views.generic import TemplateView # noqa
from horizon import exceptions
from horizon import forms
from horizon import tables
import monascaclient.exc as exc
from monitoring.alarmdefs import constants
from monitoring.alarmdefs import forms as alarm_forms
from monitoring.alarmdefs import tables as alarm_tables
from monitoring import api
class IndexView(tables.DataTableView):
table_class = alarm_tables.AlarmsTable
template_name = constants.TEMPLATE_PREFIX + 'alarm.html'
def get_data(self):
results = []
try:
results = api.monitor.alarm_list(self.request)
except Exception:
messages.error(self.request, _("Could not retrieve alarm definitions"))
return results
class AlarmCreateView(forms.ModalFormView):
form_class = alarm_forms.CreateAlarmForm
template_name = constants.TEMPLATE_PREFIX + 'create.html'
def get_context_data(self, **kwargs):
context = super(AlarmCreateView, self).get_context_data(**kwargs)
context["cancel_url"] = self.get_success_url()
context["action_url"] = reverse(constants.URL_PREFIX + 'alarm_create',
args=())
metrics = api.monitor.metrics_list(self.request)
context["metrics"] = json.dumps(metrics)
return context
def get_success_url(self):
return reverse_lazy(constants.URL_PREFIX + 'index',
args=())
def transform_alarm_data(obj):
return obj
return {'id': getattr(obj, 'id', None),
'name': getattr(obj, 'name', None),
'expression': getattr(obj, 'expression', None),
'state': filters.title(getattr(obj, 'state', None)),
'severity': filters.title(getattr(obj, 'severity', None)),
'actions_enabled': filters.title(getattr(obj, 'actions_enabled',
None)),
'notifications': getattr(obj, 'alarm_actions', None), }
def transform_alarm_history(results, name):
newlist = []
for item in results:
temp = {}
temp['alarm_id'] = item['alarm_id']
temp['name'] = name
temp['old_state'] = item['old_state']
temp['new_state'] = item['new_state']
temp['timestamp'] = item['timestamp']
temp['reason'] = item['reason']
temp['reason_data'] = item['reason_data']
newlist.append(temp)
return newlist
class AlarmDetailView(TemplateView):
template_name = constants.TEMPLATE_PREFIX + 'detail.html'
def get_object(self):
id = self.kwargs['id']
try:
if hasattr(self, "_object"):
return self._object
self._object = None
self._object = api.monitor.alarm_get(self.request, id)
notifications = []
# Fetch the notification object for each alarm_actions
for id in self._object["alarm_actions"]:
try:
notification = api.monitor.notification_get(
self.request,
id)
notifications.append(notification)
# except exceptions.NOT_FOUND:
except exc.HTTPException:
msg = _("Notification %s has already been deleted.") % id
notifications.append({"id": id,
"name": unicode(msg),
"type": "",
"address": ""})
self._object["notifications"] = notifications
return self._object
except Exception:
redirect = self.get_success_url()
exceptions.handle(self.request,
_('Unable to retrieve alarm details.'),
redirect=redirect)
return None
def get_initial(self):
self.alarm = self.get_object()
return transform_alarm_data(self.alarm)
def get_context_data(self, **kwargs):
context = super(AlarmDetailView, self).get_context_data(**kwargs)
self.get_initial()
context["alarm"] = self.alarm
context["cancel_url"] = self.get_success_url()
return context
def get_success_url(self):
return reverse_lazy(constants.URL_PREFIX + 'index')
class AlarmEditView(forms.ModalFormView):
form_class = alarm_forms.EditAlarmForm
template_name = constants.TEMPLATE_PREFIX + 'edit.html'
def get_object(self):
id = self.kwargs['id']
try:
if hasattr(self, "_object"):
return self._object
self._object = None
self._object = api.monitor.alarm_get(self.request, id)
notifications = []
# Fetch the notification object for each alarm_actions
for id in self._object["alarm_actions"]:
try:
notification = api.monitor.notification_get(
self.request,
id)
notifications.append(notification)
# except exceptions.NOT_FOUND:
except exc.HTTPException:
msg = _("Notification %s has already been deleted.") % id
messages.warning(self.request, msg)
self._object["notifications"] = notifications
return self._object
except Exception:
redirect = self.get_success_url()
exceptions.handle(self.request,
_('Unable to retrieve alarm details.'),
redirect=redirect)
return None
def get_initial(self):
self.alarm = self.get_object()
return transform_alarm_data(self.alarm)
def get_context_data(self, **kwargs):
context = super(AlarmEditView, self).get_context_data(**kwargs)
id = self.kwargs['id']
context["cancel_url"] = self.get_success_url()
context["action_url"] = reverse(constants.URL_PREFIX + 'alarm_edit',
args=(id,))
return context
def get_success_url(self):
return reverse_lazy(constants.URL_PREFIX + 'index',
args=())

View File

@ -173,8 +173,6 @@ class AlarmsTable(tables.DataTable):
host = tables.Column(transform=show_host, verbose_name=_('Host'))
service = tables.Column(transform=show_service, verbose_name=_('Service'))
state = tables.Column('state', verbose_name=_('State'))
enabled = tables.Column('actions_enabled',
verbose_name=_('Notifications Enabled'))
def get_object_id(self, obj):
return obj['id']
@ -187,10 +185,9 @@ class AlarmsTable(tables.DataTable):
verbose_name = _("Alarms")
row_actions = (GraphMetric,
ShowAlarmHistory,
EditAlarm,
DeleteAlarm,
)
table_actions = (CreateAlarm,
table_actions = (
AlarmsFilterAction,
DeleteAlarm,
)

View File

@ -22,7 +22,7 @@ import horizon
class Monitoring(horizon.Dashboard):
name = _("Monitoring")
slug = "monitoring"
panels = ('overview', 'alarms', 'notifications')
panels = ('overview', 'alarmdefs', 'alarms', 'notifications',)
default_panel = 'overview'