понедельник, 6 декабря 2010 г.

Twisted - ням-нямка

Производственная необходимость (да и любопытство) требует изучения сетевых возможностей python. Наткнулся на фреймворк Twisted, значительно упрощающий асинхронное сетевое программирование. Ням-нямка.

PS: Отдельное спасибо программистам IBM за добротные статьи, позволяющие быстро въехать в новую технологию.

Простой пингер с использованием джанго

Задача: периодически пинговать N произвольно заданных серверов, статус их доступности публиковать в вебе.

За добавление/редактирование/удаление хостов будет отвечать джанго с ее удобной админкой. Создаю джанго-приложение dpinger, в нем создаю две простые модели:


class Host(models.Model):
domain_or_ip = models.CharField(u'Доменное имя или IP-адрес', max_length=255)
desc = models.TextField(u'Краткое описание')
full_desc = models.TextField(u'Полное описание', null = True, blank = True)

class Meta:
verbose_name = u'хост'
verbose_name_plural = u'хосты'

#сообщения о доступности хостов
class Report(models.Model):
host = models.ForeignKey(Host)
status = models.CharField(u'Статус', max_length=255)
date = models.DateTimeField(u'Дата/Время', auto_now = True)

class Meta:
verbose_name = u'сообщение'
verbose_name_plural = u'сообщения'


Для Host еще создаем простую админку, для Report она не нужна. Так же делаю простой view, выводящий последние N сообщений для каждого хоста:

from django.shortcuts import render_to_response
from django.db.models import Q
from models import *

def lastreports(request, count):
hcount = int(count) * Host.objects.count()
reports = Report.objects.all().order_by('-date')[:hcount]
return render_to_response('lastreports.html', {'reports' : reports, 'count' : hcount})



Дальше немного интереснее - собственно пингер.
Джанго позволяет создавать собственные команды для manage.py, что является очень вкусной фичей, т.к. позволяет использовать всю мощь джанго в обычном скрипте.

В приложение dping создаем директорию management, в ней еще одну - commands, а там уже создаем скрипт ping.py. Соответственно команда наша будет называться ping и будет вызываться так:

python /path/to/project/manage.py ping

В ping.py обязательно должен быть класс Command, наследник BaseCommand, а в нем должен быть определен метод handle(), собственно срабатывающий при вызове команды.

Теперь нужно как-то отправлять пинги до хостов. В питоне реализован raw ICMP, но если пойти этим путем, то скрипту потребуются права рута (например, setuid root). Это меня несколько не устраивает, потому что ситуации разные бывают, да и помнить про это требование не хочется. Поэтому пошел более простым путем: вызываю комманду ping и читаю ее вывод.


# -*- coding:utf-8 -*-
from django.core.management.base import BaseCommand, CommandError
from dping.dpinger.models import Host, Report
import subprocess

class Command(BaseCommand):
args = 'None'
help = 'Ping hosts and write reports'

def ping(self, domain_or_ip):
all_is_good = " 0% packet loss"
all_is_bad = "100% packet loss"
dns_error = "ping: unknown host"
ping = subprocess.Popen(
["ping", "-c", "1", domain_or_ip],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
out, error = ping.communicate()

if all_is_good in out:
return u'Все отлично'
elif all_is_bad in out or dns_error in out:
return u'Невозможно достучаться'
else:
return u'Куда-то пропадают пакеты'

def handle(self, *args, **options):
hosts = Host.objects.all()
for host in hosts:
report = Report.objects.create(host=host, status='')
report.status = self.ping(host.domain_or_ip)
print host.domain_or_ip + ": " + report.status #на всякий случай
report.save()


Вот такой вот получился простенький пингер. Надеюсь кому-то эта информация окажется полезной.