Commit 709099f7 authored by Élie Bouttier's avatar Élie Bouttier

ip resources states history

parent 2c1c2734
......@@ -26,9 +26,9 @@ from datetime import timedelta
from djadhere.utils import get_active_filter
from adhesions.models import Adhesion
from banking.models import PaymentUpdate
from .models import Service, ServiceType, IPPrefix, IPResource, Route, Tunnel, \
from .models import Service, ServiceType, IPPrefix, IPResource, IPResourceState, \
ServiceAllocation, Antenna, AntennaAllocation, Allocation, \
Switch, Port
Route, Tunnel, Switch, Port
from .utils.notifications import notify_allocation
from .forms import AntennaForm
......@@ -67,13 +67,15 @@ class ResourcePingFilter(admin.SimpleListFilter):
def queryset(self, request, queryset):
if self.value() == 'up':
return queryset.filter(up=True)
return queryset.filter(last_state__state=IPResourceState.STATE_UP)
if self.value() == 'down':
return queryset.filter(models.Q(up__isnull=True) | models.Q(up=False))
return queryset.exclude(last_state__state=IPResourceState.STATE_UP) # DOWN + UNKNOWN
if self.value() == 'down-since':
return queryset.filter(up=False)
queryset = queryset.exclude(last_state__state=IPResourceState.STATE_UP)
return queryset.filter(last_time_up__isnull=False)
if self.value() == 'never-up':
return queryset.filter(up__isnull=True)
queryset = queryset.exclude(last_state__state=IPResourceState.STATE_UP) # DOWN + UNKWON
return queryset.filter(last_time_up__isnull=True)
class ActiveServiceFilter(admin.SimpleListFilter):
......@@ -265,6 +267,22 @@ class InactiveAntennaAllocationInline(AntennaAllocationMixin, InactiveAllocation
pass
class IPResourceStateInline(admin.TabularInline):
model = IPResourceState
verbose_name_plural = 'Historique des changements d’état'
fields = ['date']
ordering = ['-date']
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj):
return False
def has_delete_permission(self, request, obj=None):
return False
class PortInline(admin.TabularInline):
model = Port
max_num = 0
......@@ -385,27 +403,32 @@ class IPResourceAdmin(admin.ModelAdmin):
)
search_fields = ('=ip', 'notes',)
actions = ['contact_ip_owners']
ordering = ['ip']
inlines = [ IPResourceStateInline ]
def get_fields(self, request, obj=None):
return self.get_readonly_fields(request, obj)
def get_readonly_fields(self, request, obj=None):
fields = ['ip']
if obj and obj.reserved:
fields += ['reserved']
if obj and not obj.in_use:
fields += ['last_use']
if obj and obj.last_time_up and obj.last_check:
fields += ['last_time_up', 'last_check']
if obj.category == IPResource.CATEGORY_PUBLIC:
fields += ['password']
if obj and obj.checkmk_label:
fields += ['checkmk']
if obj and obj.notes:
fields += ['notes']
if obj:
if obj.reserved:
fields += ['reserved']
if not obj.in_use:
fields += ['last_use']
fields += ['last_state']
if obj.last_state.state != IPResourceState.STATE_UP:
fields += ['last_time_up']
if obj.category == IPResource.CATEGORY_PUBLIC:
fields += ['password']
if obj.checkmk_label:
fields += ['checkmk']
if obj.notes:
fields += ['notes']
return fields
def get_inline_instances(self, request, obj=None):
super_inlines = super().get_inline_instances(request, obj)
if obj:
if obj.category == 0:
inlines = (ActiveServiceAllocationInline, InactiveServiceAllocationInline,)
......@@ -413,7 +436,7 @@ class IPResourceAdmin(admin.ModelAdmin):
inlines = (ActiveAntennaAllocationInline, InactiveAntennaAllocationInline,)
else:
inlines = ()
return [inline(self.model, self.admin_site) for inline in inlines]
return [inline(self.model, self.admin_site) for inline in inlines] + super_inlines
def get_queryset(self, request):
qs = super().get_queryset(request)
......@@ -426,13 +449,12 @@ class IPResourceAdmin(admin.ModelAdmin):
default=None,
))
qs = qs.annotate(
up=models.Case(
models.When(last_check__isnull=False, last_time_up__isnull=False, last_time_up=models.F('last_check'), then=True),
models.When(last_check__isnull=False, last_time_up__isnull=False, then=False),
downtime=models.Case(
models.When(last_state__state=IPResourceState.STATE_UP, then=models.F('last_state__date')-models.Value(now)),
models.When(last_state__state=IPResourceState.STATE_DOWN, then=models.Value(now)-models.F('last_time_up')),
default=None,
output_field=models.NullBooleanField(),
output_field=models.DurationField(),
))
qs = qs.annotate(downtime=models.F('last_check') - models.F('last_time_up'))
qs = qs.annotate(
route=models.Case(
models.When(
......@@ -461,12 +483,13 @@ class IPResourceAdmin(admin.ModelAdmin):
last_use.admin_order_field = 'last_use'
def ping(self, obj):
if obj.up:
if obj.last_state.state == IPResourceState.STATE_UP:
label = 'UP'
elif obj.last_time_up:
label = 'dernier ping : ' + naturaltime(obj.last_time_up)
else:
label = 'DOWN'
if obj.last_time_up:
label = 'dernier ping : ' + naturaltime(obj.last_time_up)
else:
label = 'DOWN'
if obj.checkmk_url:
return format_html('<a href="{}">{}</a>', obj.checkmk_url, label)
else:
......
# Generated by Django 2.1.1 on 2019-02-19 20:46
from django.db import migrations, models
from django.utils import timezone
class Migration(migrations.Migration):
dependencies = [
('services', '0051_auto_20180602_1346'),
]
operations = [
migrations.CreateModel(
name='IPResourceState',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(default=timezone.now)),
('state', models.IntegerField(choices=[(0, 'DOWN'), (1, 'UP'), (2, 'Inconnu')])),
('ip', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='state_set', to='services.IPResource')),
],
),
migrations.AddField(
model_name='ipresource',
name='last_state',
field=models.ForeignKey(null=True, on_delete=models.deletion.PROTECT, to='services.IPResourceState', related_name='+'),
),
]
# Generated by Django 2.1.1 on 2019-02-19 20:47
from django.db import migrations
from django.utils import timezone
from datetime import timedelta
def forward(apps, schema_editor):
db_alias = schema_editor.connection.alias
IPResource = apps.get_model('services', 'IPResource')
IPResourceState = apps.get_model('services', 'IPResourceState')
now = timezone.now()
for ip in IPResource.objects.all():
if ip.last_check:
if ip.last_time_up:
if ip.last_check == ip.last_time_up: # UP
ip.last_state = IPResourceState.objects.create(ip=ip, date=ip.last_time_up, state=1) # UP
ip.save()
else: # DOWN but UP some time before
ip.last_state = IPResourceState.objects.create(ip=ip, date=ip.last_time_up, state=0) # DOWN
ip.save()
else: # Always DOWN
ip.last_state = IPResourceState.objects.create(ip=ip, date=ip.last_check, state=0) # DOWN
ip.save()
else:
ip.last_state = IPResourceState.objects.create(ip=ip, date=now, state=2) # UNKNOWN
ip.save()
def backward(apps, schema_editor):
db_alias = schema_editor.connection.alias
IPResource = apps.get_model('services', 'IPResource')
IPResourceState = apps.get_model('services', 'IPResourceState')
for ip in IPResource.objects.all():
if ip.last_state.state != 2:
ip.last_check = ip.last_state.date
if ip.last_state.state == 1:
ip.last_time_up = ip.last_check
ip.save()
class Migration(migrations.Migration):
dependencies = [
('services', '0052_auto_20190219_2146'),
]
operations = [
migrations.RunPython(forward, backward)
]
# Generated by Django 2.1.1 on 2019-02-19 21:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('services', '0053_auto_20190219_2147'),
]
operations = [
migrations.RemoveField(
model_name='ipresource',
name='last_check',
),
migrations.AlterField(
model_name='ipresource',
name='last_state',
field=models.ForeignKey(on_delete=models.deletion.PROTECT, to='services.IPResourceState', related_name='+'),
),
]
......@@ -102,8 +102,8 @@ class IPResource(models.Model):
category = models.IntegerField(choices=CATEGORIES, verbose_name='catégorie')
notes = models.TextField(blank=True, default='')
checkmk_label = models.CharField(max_length=128, blank=True, default='')
last_state = models.ForeignKey("IPResourceState", on_delete=models.PROTECT, related_name='+', verbose_name='dernier état')
last_time_up = models.DateTimeField(null=True, blank=True, verbose_name='Dernière réponse au ping')
last_check = models.DateTimeField(null=True, blank=True, verbose_name='Dernier contrôle')
objects = IPResourceManager()
......@@ -135,6 +135,23 @@ class IPResource(models.Model):
return str(self.ip)
class IPResourceState(models.Model):
STATE_DOWN = 0
STATE_UP = 1
STATE_UNKNOWN = 2
STATE_CHOICES = (
(STATE_DOWN, 'DOWN'),
(STATE_UP, 'UP'),
(STATE_UNKNOWN, 'Inconnu'),
)
ip = models.ForeignKey(IPResource, on_delete=models.CASCADE, related_name='state_set')
date = models.DateTimeField(default=timezone.now)
state = models.IntegerField(choices=STATE_CHOICES)
def __str__(self):
return self.get_state_display()
class ServiceType(models.Model):
name = models.CharField(max_length=64, verbose_name='Nom', unique=True)
contact = models.CharField(max_length=64, verbose_name='Contact en cas de problème', blank=True, default='')
......
from django.db.models import Q, F
from django.utils import timezone
from services.models import IPResource
from services.models import IPResource, IPResourceState, ServiceAllocation
import re
from ipaddress import IPv4Address, AddressValueError
import logging
from itertools import groupby
logger = logging.getLogger(__name__)
def fastpinger_update(f):
now = timezone.now()
regex = re.compile('^(?P<ip>[0-9.]+)[ ]+: (?P<p1>([0-9]+.[0-9]+|-)) (?P<p2>([0-9]+.[0-9]+|-))'
' (?P<p3>([0-9]+.[0-9]+|-)) (?P<p4>([0-9]+.[0-9]+|-)) (?P<p5>([0-9]+.[0-9]+|-))$')
up_count = IPResource.objects.filter(last_check__isnull=False).filter(last_time_up=F('last_check')).count()
down_count = IPResource.objects.filter(last_check__isnull=False).exclude(last_time_up=F('last_check')).count()
unknown_count = IPResource.objects.filter(last_check__isnull=True).count()
up, down, unknown = set(), set(), set()
for line in f:
......@@ -35,21 +39,39 @@ def fastpinger_update(f):
up = up - down # suppression des doublons
up = IPResource.objects.filter(ip__in=up)
down = IPResource.objects.filter(ip__in=down)
unknown = IPResource.objects.exclude(Q(pk__in=up) | Q(pk__in=down))
up.exclude(last_time_up=F('last_check')).count()
down.filter(last_time_up=F('last_check')).count()
leaves = {
IPResourceState.STATE_DOWN: 0,
IPResourceState.STATE_UP: 0,
IPResourceState.STATE_UNKNOWN: 0,
}
now = timezone.now()
become_down = IPResource.objects.exclude(last_state__state=IPResourceState.STATE_DOWN).filter(ip__in=down)
for ip in become_down:
leaves[ip.last_state.state] += 1
if ip.last_state.state == IPResourceState.STATE_UP:
ip.last_time_up = now
ip.last_state = IPResourceState.objects.create(ip=ip, date=now, state=IPResourceState.STATE_DOWN)
ip.save()
become_up = IPResource.objects.exclude(last_state__state=IPResourceState.STATE_UP).filter(ip__in=up)
for ip in become_up:
leaves[ip.last_state.state] += 1
ip.last_state = IPResourceState.objects.create(ip=ip, date=now, state=IPResourceState.STATE_UP)
ip.save()
become_unknown = IPResource.objects.exclude(last_state__state=IPResourceState.STATE_UNKNOWN).exclude(Q(pk__in=up) | Q(pk__in=down))
for ip in become_unknown:
leaves[ip.last_state.state] += 1
if ip.last_state.state == IPResourceState.STATE_UP:
ip.last_time_up = now
ip.last_state = IPResourceState.objects.create(ip=ip, date=now, state=IPResourceState.STATE_UNKNOWN)
ip.save()
up.update(last_time_up=now, last_check=now)
down.update(last_check=now)
upped = len(up) - up_count
downed = len(down) - down_count
unknowned = len(unknown) - unknown_count
down_count = IPResource.objects.filter(last_state__state=IPResourceState.STATE_DOWN).count()
up_count = IPResource.objects.filter(last_state__state=IPResourceState.STATE_UP).count()
unknown_count = IPResource.objects.filter(last_state__state=IPResourceState.STATE_UNKNOWN).count()
return "UP: %d (%+d), DOWN: %d (%+d), UNKNOWN: %d (%+d)" \
% (len(up), upped, len(down), downed, len(unknown), unknowned)
stats = "UP: %d (-%d+%d), DOWN: %d (-%d+%d), UNKNOWN: %d (-%d+%d)" \
% (up_count, leaves[IPResourceState.STATE_UP], len(become_up), \
down_count, leaves[IPResourceState.STATE_DOWN], len(become_down), \
unknown_count, leaves[IPResourceState.STATE_UNKNOWN], len(become_unknown))
return stats
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment