Browse Source

Multiple changes:

1) Makes mixin for API views.
2) Makes device monitoring notification.
And little bit fixes
devel
bashmak 8 years ago
parent
commit
ffc5a6d5ac
  1. 68
      agent/monitoring_agent.py
  2. 7
      chatbot/telebot.py
  3. 2
      devapp/forms.py
  4. 44
      devapp/locale/ru/LC_MESSAGES/django.po
  5. 34
      devapp/migrations/0006_auto_20180129_1625.py
  6. 17
      devapp/models.py
  7. 6
      devapp/templates/devapp/add_dev.html
  8. 2
      devapp/templates/devapp/dev.html
  9. 20
      devapp/templates/devapp/devices.html
  10. 6
      devapp/urls.py
  11. 112
      devapp/views.py
  12. 57
      djing/global_base_views.py
  13. 6
      djing/settings.py
  14. 4
      djing/views.py
  15. 1
      requirements.txt
  16. 6
      taskapp/handle.py

68
agent/monitoring_agent.py

@ -0,0 +1,68 @@
#!/usr/bin/env python3
import sys
import re
from hashlib import sha256
import requests
API_AUTH_SECRET = 'asihdfaoisydoiayosidyaoisydoiasydaisydasd'
SERVER_DOMAIN = 'http://localhost:8000'
IP_REGEXP = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
def calc_hash(data):
if type(data) is str:
result_data = data.encode('utf-8')
else:
result_data = bytes(data)
return sha256(result_data).hexdigest()
def check_sign(get_list, sign):
hashed = '_'.join(get_list)
my_sign = calc_hash(hashed)
return sign == my_sign
def validate(regexp, string):
if not bool(re.match(regexp, string)):
raise ValueError
return string
def validate_status(text):
if not text in ('UP', 'DOWN', 'UNREACHABLE'):
raise ValueError
return text
def send_request(ip, status, sign):
r = requests.get(
"%(domain)s/dev/on_device_down/" % {'domain': SERVER_DOMAIN},
params={
'ip': ip,
'status': status,
'sign': sign
})
if r.status_code == 200:
print(r.json())
else:
print('Status:', r.status_code, r.text)
if __name__ == '__main__':
if len(sys.argv) < 3:
print('You forget parameters, example of usage:\n'
'$ python3 ./monitoring_agent.py 192.168.0.100 DOWN|UP|UNREACHABLE')
exit(0)
dev_ip = validate(IP_REGEXP, sys.argv[1])
status = validate_status(sys.argv[2])
sign = calc_hash('_'.join((dev_ip, status, API_AUTH_SECRET)))
send_request(dev_ip, status, sign)

7
chatbot/telebot.py

@ -115,7 +115,6 @@ class DjingTelebot(helper.ChatHandler):
self._dialog_fn = None
self._chat_id = 0
# пингуем адрес
def ping(self, ip=None):
if ip is None:
self._question(_("Let's ping, write ip. It will be necessary to wait 10 seconds"), self.ping)
@ -131,7 +130,7 @@ class DjingTelebot(helper.ChatHandler):
self._sent_reply(_("You're '%s', right?") % self._current_user.get_full_name())
# Просто отправляем текст оповещения указанной учётке
# Just sending text to specified account
def send_notify(msg_text, account, tag='none'):
try:
MessageQueue.objects.push(msg=msg_text, user=account, tag=tag)
@ -143,6 +142,6 @@ def send_notify(msg_text, account, tag='none'):
except TelegramBot.DoesNotExist:
raise ChatException(_("Recipient '%s' does not subscribed on notifications") % account.get_full_name())
except ProtocolError as e:
raise ChatException(e)
raise ChatException('ProtocolError: %s' % e)
except TelegramError as e:
raise ChatException("%s - %s" % (e, tb.user.get_full_name()))
raise ChatException('Telegram error: %s' % e)

2
devapp/forms.py

@ -19,7 +19,7 @@ class DeviceForm(forms.ModelForm):
class Meta:
model = models.Device
exclude = ['map_dot']
exclude = ['map_dot', 'status']
widgets = {
'ip_address': forms.TextInput(attrs={
'pattern': ip_addr_regex,

44
devapp/locale/ru/LC_MESSAGES/django.po

@ -221,7 +221,7 @@ msgstr "Прикрепленный абонент"
#: templates/devapp/custom_dev_page/onu.html:47
msgid "ONU Status"
msgstr ""
msgstr "Состояние ONU"
#: templates/devapp/custom_dev_page/onu.html:57
msgid "ONU error"
@ -229,7 +229,7 @@ msgstr "ONU ошибка"
#: templates/devapp/custom_dev_page/onu.html:71
msgid "Name on OLT"
msgstr ""
msgstr "Имя на OLT"
#: templates/devapp/custom_dev_page/onu.html:72
msgid "Distance(m)"
@ -238,11 +238,11 @@ msgstr "Расстояние (м)"
#: templates/devapp/custom_dev_page/onu.html:77
#: templates/devapp/custom_dev_page/onu.html:85
msgid "Mac on OLT"
msgstr ""
msgstr "MAC адрес на OLT"
#: templates/devapp/custom_dev_page/onu.html:78
msgid "Mac-addresses does not match"
msgstr ""
msgstr "MAC адреса не совпадают"
#: templates/devapp/custom_dev_page/onu.html:79
#: templates/devapp/custom_dev_page/onu.html:81
@ -312,7 +312,7 @@ msgstr "Устройства без группы"
#: templates/devapp/fix_dev_group.html:17
msgid "Fix device group"
msgstr ""
msgstr "Поправить группу устройства"
#: templates/devapp/group_list.html:18
msgid "Group title"
@ -408,11 +408,11 @@ msgstr "Инфа о точке сохранена"
#: views.py:110
msgid "You have redirected to existing device"
msgstr ""
msgstr "Вы были переадресованы на существующее устройство"
#: views.py:113 views.py:329 views.py:419
msgid "Please attach user group for device"
msgstr ""
msgstr "Пожалуйста назначте устройству группу в настройках"
#: views.py:117 views.py:275 views.py:304 views.py:421
msgid "Form is invalid, check fields and try again"
@ -473,11 +473,29 @@ msgstr "Исправлено, обновите страницу"
msgid "Parent device not found"
msgstr "Вышестоящее устройство не найдено"
#~ msgid "Map point"
#~ msgstr "Точка топологии"
msgid "Send notify when monitoring state changed"
msgstr "Отправлять уведомления при событиях мониторинга"
msgid "Device %(device_name)s is up"
msgstr "%(device_name)s в сети"
msgid "Device %(device_name)s is down"
msgstr "%(device_name)s не в сети"
msgid "Device %(device_name)s is unreachable"
msgstr "%(device_name)s недостижим"
msgid "Device %(device_name)s getting undefined status code"
msgstr "Устройство %(device_name)s получило не определённый код состояния"
msgid "Undefined"
msgstr "Не определено"
msgid "Up"
msgstr "В сети"
#~ msgid "Find the subscriber"
#~ msgstr "Найти абонента"
msgid "Unreachable"
msgstr "Не доступно"
#~ msgid "View the device"
#~ msgstr "Посмотреть устройство"
msgid "Down"
msgstr "Не в сети"

34
devapp/migrations/0006_auto_20180129_1625.py

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-29 16:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('devapp', '0005_device_snmp_item_num'),
]
operations = [
migrations.AlterModelOptions(
name='device',
options={'ordering': ['comment'], 'permissions': (('can_view_device', 'Can view device'),), 'verbose_name': 'Device', 'verbose_name_plural': 'Devices'},
),
migrations.AddField(
model_name='device',
name='is_noticeable',
field=models.BooleanField(default=False, verbose_name='Send notify when monitoring state changed'),
),
migrations.AddField(
model_name='device',
name='status',
field=models.CharField(choices=[('und', 'Undefined'), ('up', 'Up'), ('unr', 'Unreachable'), ('dwn', 'Down')], default='und', max_length=3, verbose_name='Status'),
),
migrations.AlterField(
model_name='device',
name='snmp_item_num',
field=models.PositiveSmallIntegerField(blank=True, default=0, verbose_name='SNMP Number'),
),
]

17
devapp/models.py

@ -54,7 +54,17 @@ class Device(models.Model):
user_group = models.ForeignKey('abonapp.AbonGroup', verbose_name=_('User group'), on_delete=models.SET_NULL, null=True, blank=True)
parent_dev = models.ForeignKey('self', verbose_name=_('Parent device'), blank=True, null=True, on_delete=models.SET_NULL)
snmp_item_num = models.PositiveSmallIntegerField(_('SNMP Number'), default=0)
snmp_item_num = models.PositiveSmallIntegerField(_('SNMP Number'), default=0, blank=True)
NETWORK_STATES = (
('und', _('Undefined')),
('up', _('Up')),
('unr', _('Unreachable')),
('dwn', _('Down'))
)
status = models.CharField(_('Status'), max_length=3, choices=NETWORK_STATES, default='und')
is_noticeable = models.BooleanField(_('Send notify when monitoring state changed'), default=False)
objects = DeviceManager()
@ -65,14 +75,13 @@ class Device(models.Model):
)
verbose_name = _('Device')
verbose_name_plural = _('Devices')
ordering = ['comment']
def get_abons(self):
pass
def get_status(self):
url = getattr(settings, 'NAGIOS_URL')
if url:
return requests.get('%s/host/status?addr=%s' % (url, self.ip_address))
return self.status
def get_manager_klass(self):
klasses = [kl for kl in DEVICE_TYPES if kl[0] == self.devtype]

6
devapp/templates/devapp/add_dev.html

@ -54,8 +54,10 @@
</div>
</div>
{% bootstrap_icon 'list-alt' as ic %}
{% bootstrap_field form.snmp_item_num addon_before=ic %}
{% bootstrap_icon 'list-alt' as ic %}
{% bootstrap_field form.snmp_item_num addon_before=ic %}
{% bootstrap_field form.is_noticeable %}
<div class="btn-group btn-group-sm">
<button type="submit" class="btn btn-primary">

2
devapp/templates/devapp/dev.html

@ -54,6 +54,8 @@
{% bootstrap_icon 'list-alt' as ic %}
{% bootstrap_field form.snmp_item_num addon_before=ic %}
{% bootstrap_field form.is_noticeable %}
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}

20
devapp/templates/devapp/devices.html

@ -29,7 +29,7 @@
{% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th class="col-md-3">{% trans 'Mac address' %}</th>
<th class="col-md-3 hidden-xs hidden-sm">{% trans 'Plugin output' %}</th>
{# <th class="col-md-3 hidden-xs hidden-sm">{% trans 'Plugin output' %}</th> #}
<th class="col-md-1">
<a href="{% url 'devapp:devs' group.pk %}?order_by=devtype&dir={{ dir|default:"down" }}">
{% trans 'Device type' %}
@ -43,20 +43,24 @@
<tbody>
{% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device %}
{% for dev in devices %}
<tr>
<td>
{% if dev.mon %}
{% if dev.mon.current_state == '0' %}
{% if dev.status == 'und' %}&ndash;
{% else %}
{% if dev.status == 'unr' or dev.status == 'dwn' %}
<span class="glyphicon glyphicon-exclamation-sign text-danger"></span>
{% elif dev.status == 'up' %}
<span class="glyphicon glyphicon-ok-circle text-success"></span>
{% else %}
<span class="glyphicon glyphicon-exclamation-sign text-danger"></span>
<span class="glyphicon glyphicon-question-sign text-warning"></span>
{% endif %}
{% else %}&ndash;{% endif %}
{% endif %}
</td>
<td><a href="{% url 'devapp:view' dev.user_group.pk dev.pk %}">{{ dev.ip_address }}</a></td>
<td>{{ dev.comment }}</td>
<td>{{ dev.mac_addr|default:_('Not assigned') }}</td>
<td class="hidden-xs hidden-sm">{{ dev.mon.plugin_output|default:'&ndash;' }}</td>
{# <td class="hidden-xs hidden-sm">{{ dev.mon.plugin_output|default:'&ndash;' }}</td> #}
<td>{{ dev.get_devtype_display }}</td>
<td class="btn-group btn-group-xs btn-group-justified">
{% if can_del_dev %}
@ -73,7 +77,7 @@
</tr>
{% empty %}
<tr>
<td colspan="7">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' group.pk %}">{% trans 'Create' %}</a></td>
<td colspan="6">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' group.pk %}">{% trans 'Create' %}</a></td>
</tr>
{% endfor %}
{% endwith %}
@ -81,7 +85,7 @@
<tfoot>
<tr>
<td colspan="7">
<td colspan="6">
<a href="{% url 'devapp:add' group.pk %}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %}
</a>

6
devapp/urls.py

@ -1,5 +1,4 @@
from django.conf.urls import url
from . import views
@ -23,5 +22,8 @@ urlpatterns = [
url(r'^(?P<group_id>\d+)/(?P<device_id>\d+)/(?P<portid>\d+)/del$', views.delete_single_port, name='del_port'),
url(r'^(?P<group_id>\d+)/(?P<device_id>\d+)/(?P<port_id>\d+)/edit$', views.edit_single_port, name='edit_port'),
url(r'^fix_device_group/(?P<device_id>\d+)$', views.fix_device_group, name='fix_device_group'),
url(r'^search_dev$', views.search_dev)
url(r'^search_dev$', views.search_dev),
# Monitoring api
url(r'^on_device_down/$', views.OnDevDown.as_view())
]

112
devapp/views.py

@ -1,23 +1,28 @@
# -*- coding: utf-8 -*-
import re
from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError
from django.db.models import Q, Count
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _, gettext
from easysnmp import EasySNMPTimeoutError, EasySNMPError
from json import dumps
from .models import Device, Port, DeviceDBException, DeviceMonitoringException
from mydefs import pag_mn, res_success, res_error, only_admins, ping, order_helper
from .forms import DeviceForm, PortForm
from mydefs import pag_mn, res_success, res_error, only_admins, ping, order_helper, ip_addr_regex
from abonapp.models import AbonGroup, Abon
from django.conf import settings
from guardian.decorators import permission_required_or_403 as permission_required
from guardian.shortcuts import get_objects_for_user
from chatbot.telebot import send_notify
from chatbot.models import ChatException
from jsonview.decorators import json_view
from djing.global_base_views import HashAuthView, AllowedSubnetMixin
from .models import Device, Port, DeviceDBException, DeviceMonitoringException
from .forms import DeviceForm, PortForm
@login_required
@ -27,15 +32,16 @@ def devices(request, group_id):
if not request.user.has_perm('abonapp.can_view_abongroup', group):
raise PermissionDenied
try:
devs = Device.objects.filter(user_group=group).select_related('user_group').only('comment', 'mac_addr', 'devtype', 'user_group', 'pk', 'ip_address')
devs = Device.objects.filter(user_group=group) \
.select_related('user_group') \
.only('comment', 'mac_addr', 'devtype', 'user_group', 'pk', 'ip_address')
# фильтр
dr, field = order_helper(request)
if field:
devs = devs.order_by(field)
devs = pag_mn(request, devs)
devs = Device.objects.wrap_monitoring_info(devs)
#devs = Device.objects.wrap_monitoring_info(devs)
except (DeviceDBException, DeviceMonitoringException) as e:
messages.error(request, e)
@ -52,7 +58,7 @@ def devices(request, group_id):
@only_admins
def devices_null_group(request):
devs = Device.objects.filter(user_group=None).only('comment', 'devtype', 'user_group', 'pk', 'ip_address')
# фильтр
dr, field = order_helper(request)
if field:
devs = devs.order_by(field)
@ -99,11 +105,8 @@ def dev(request, group_id, device_id=0):
try:
frm = DeviceForm(request.POST, instance=devinst)
if frm.is_valid():
ndev = frm.save()
ndev.update_dhcp()
messages.success(request, _('Device info has been saved'))
return redirect('devapp:edit', ndev.user_group.pk, ndev.pk)
else:
# check if that device is exist
try:
already_dev = Device.objects.get(mac_addr=request.POST.get('mac_addr'))
if already_dev.user_group:
@ -114,6 +117,14 @@ def dev(request, group_id, device_id=0):
return redirect('devapp:fix_device_group', already_dev.pk)
except Device.DoesNotExist:
pass
# else update device info
ndev = frm.save()
# change device info in dhcpd.conf
ndev.update_dhcp()
messages.success(request, _('Device info has been saved'))
return redirect('devapp:edit', ndev.user_group.pk, ndev.pk)
else:
messages.error(request, _('Form is invalid, check fields and try again'))
except IntegrityError as e:
if 'unique constraint' in e.message:
@ -129,7 +140,7 @@ def dev(request, group_id, device_id=0):
'comment': request.GET.get('c'),
'ip_address': request.GET.get('ip'),
'man_passw': getattr(settings, 'DEFAULT_SNMP_PASSWORD', ''),
'snmp_item_num': request.GET.get('n')
'snmp_item_num': request.GET.get('n') or 0
})
else:
frm = DeviceForm(instance=devinst)
@ -401,7 +412,7 @@ def search_dev(request):
Q(comment__icontains=word) | Q(ip_address=word)
).only('pk', 'ip_address', 'comment')[:16]
results = [{'id': dev.pk, 'text': "%s: %s" % (dev.ip_address, dev.comment)} for dev in results]
return HttpResponse(dumps(results, ensure_ascii=False))
return JsonResponse(results, json_dumps_params={'ensure_ascii': False})
@login_required
@ -453,10 +464,10 @@ def fix_onu(request):
text = text + ' %s' % _('Parent device not found')
except Device.DoesNotExist:
pass
return HttpResponse(dumps({
return JsonResponse({
'status': status,
'dat': text
}))
})
@login_required
@ -471,3 +482,66 @@ def fix_port_confict(request, group_id, device_id, port_id):
'device': device,
'port': port
})
class OnDevDown(AllowedSubnetMixin, HashAuthView):
#
# Api view for monitoring devices
#
http_method_names = ['get']
@method_decorator(json_view)
def get(self, request):
try:
dev_ip = request.GET.get('ip')
dev_status = request.GET.get('status')
if dev_ip is None or dev_ip == '':
return {'text': 'ip does not passed'}
if not bool(re.match(ip_addr_regex, dev_ip)):
return {'text': 'ip address is not valid'}
possible_devices = Device.objects.filter(ip_address=dev_ip)
if possible_devices.count() < 1:
return {
'text': 'Devices with ip %s does not exist' % dev_ip
}
else:
device_down = possible_devices[0]
recipients = device_down.user_group.profiles.all()
names = list()
if dev_status == 'UP':
device_down.status = 'up'
notify_text = 'Device %(device_name)s is up'
elif dev_status == 'DOWN':
device_down.status = 'dwn'
notify_text = 'Device %(device_name)s is down'
elif dev_status == 'UNREACHABLE':
device_down.status = 'unr'
notify_text = 'Device %(device_name)s is unreachable'
else:
device_down.status = 'und'
notify_text = 'Device %(device_name)s getting undefined status code'
device_down.save(update_fields=['status'])
for recipient in recipients:
send_notify(
msg_text=gettext(notify_text) % {
'device_name': "%s %s" % (device_down.ip_address, device_down.comment)
},
account=recipient,
tag='devmon'
)
names.append(recipient.username)
return {
'text': 'notification successfully sent',
'recipients': names
}
except ChatException as e:
return {
'text': str(e)
}

57
djing/global_base_views.py

@ -0,0 +1,57 @@
from hashlib import sha256
from django.views.generic.base import View
from django.http.response import HttpResponseForbidden
from django.conf import settings
from netaddr import IPNetwork, IPAddress
API_AUTH_SECRET = getattr(settings, 'API_AUTH_SECRET')
API_AUTH_SUBNET = getattr(settings, 'API_AUTH_SUBNET')
class HashAuthView(View):
@staticmethod
def calc_hash(data):
if type(data) is str:
result_data = data.encode('utf-8')
else:
result_data = bytes(data)
return sha256(result_data).hexdigest()
@staticmethod
def check_sign(get_list, sign):
hashed = '_'.join(get_list)
my_sign = HashAuthView.calc_hash(hashed)
return sign == my_sign
def __init__(self, *args, **kwargs):
if API_AUTH_SECRET is None:
raise ImportError('You must specified API_AUTH_SECRET is settings')
else:
super(HashAuthView, self).__init__(*args, **kwargs)
def dispatch(self, request, *args, **kwargs):
sign = request.GET.get('sign')
# Transmittent get list without sign
get_values = request.GET.copy()
del get_values['sign']
if HashAuthView.check_sign(list(get_values.values()) + [API_AUTH_SECRET], sign):
return super(HashAuthView, self).dispatch(request, *args, **kwargs)
else:
return HttpResponseForbidden('Access Denied')
class AllowedSubnetMixin(object):
def dispatch(self, request, *args, **kwargs):
"""
Check if user ip in allowed subnet.
Return 403 denied otherwise.
"""
ip = IPAddress(request.META.get('REMOTE_ADDR'))
if ip in IPNetwork(API_AUTH_SUBNET):
return super(AllowedSubnetMixin, self).dispatch(request, *args, **kwargs)
else:
return HttpResponseForbidden('Access Denied')

6
djing/settings.py

@ -180,3 +180,9 @@ TELEGRAM_BOT_TOKEN = local_settings.TELEGRAM_BOT_TOKEN
TELEPHONE_REGEXP = r'^\+[7,8,9,3]\d{10,11}$'
ASTERISK_MANAGER_AUTH = local_settings.ASTERISK_MANAGER_AUTH
# Secret word for auth to api views by hash
API_AUTH_SECRET = local_settings.API_AUTH_SECRET
# Allowed subnet for api
API_AUTH_SUBNET = local_settings.API_AUTH_SUBNET

4
djing/views.py

@ -8,7 +8,3 @@ def home(request):
return redirect('acc_app:profile')
else:
return redirect('client_side:home')
def finance_report(request):
pass

1
requirements.txt

@ -20,3 +20,4 @@ django-jsonfield
requests
webdavclient
pyst2
django-jsonview

6
taskapp/handle.py

@ -15,10 +15,10 @@ def handle(task, author, recipients, abon_group):
try:
dst_account = recipient
text = _('Task')
# Если сигнал самому себе то молчим
# If signal to myself then quietly
if author == recipient:
return
# Если задача завершена или провалена
# If task completed or failed
elif task.state == 'F' or task.state == 'C':
text = _('Task completed')
if task.abon is not None:
@ -36,7 +36,7 @@ def handle(task, author, recipients, abon_group):
fulltext += task.descr if task.descr else ''
if task.state == 'F' or task.state == 'C':
# Если задача завершена или провалена то отправляем одно оповещение автору
# If task completed or failed than send one message to author
try:
send_notify(fulltext, author, tag='taskap')
except ChatException as e:

Loading…
Cancel
Save