pvl/verkko/hosts.py
changeset 14 02c21749cb4f
parent 12 7ffb92a57092
child 16 51509b5ce1c0
equal deleted inserted replaced
13:a2f245750700 14:02c21749cb4f
     1 from pvl.verkko import db, web
     1 from pvl.verkko import db, web
       
     2 from pvl.verkko.utils import parse_timedelta, IPv4Network
     2 
     3 
     3 from pvl.html import tags as html
     4 from pvl.html import tags as html
     4 
     5 
     5 import re
     6 import re
       
     7 import datetime
     6 import socket # dns
     8 import socket # dns
     7 
     9 
     8 import logging; log = logging.getLogger('pvl.verkko.hosts')
    10 import logging; log = logging.getLogger('pvl.verkko.hosts')
     9 
    11 
    10 # XXX: this should actually be DHCPHost
    12 # XXX: this should actually be DHCPHost
    12     DATE_FMT = '%Y%m%d'
    14     DATE_FMT = '%Y%m%d'
    13     
    15     
    14     MAC_HEX = r'([A-Za-z0-9]{2})'
    16     MAC_HEX = r'([A-Za-z0-9]{2})'
    15     MAC_SEP = r'[-:.]?'
    17     MAC_SEP = r'[-:.]?'
    16     MAC_RE = re.compile(MAC_SEP.join([MAC_HEX] * 6))
    18     MAC_RE = re.compile(MAC_SEP.join([MAC_HEX] * 6))
       
    19 
       
    20     @classmethod
       
    21     def query (cls, session, seen=None) :
       
    22         """
       
    23             seen        - select hosts seen during given timedelta period
       
    24         """
       
    25         
       
    26         query = session.query(cls)
       
    27 
       
    28         return query
    17 
    29 
    18     @classmethod
    30     @classmethod
    19     def normalize_mac (cls, mac) :
    31     def normalize_mac (cls, mac) :
    20         match = cls.MAC_RE.search(mac)
    32         match = cls.MAC_RE.search(mac)
    21 
    33 
    43     def render_name (self) :
    55     def render_name (self) :
    44         if self.name :
    56         if self.name :
    45             return self.name.decode('ascii', 'replace')
    57             return self.name.decode('ascii', 'replace')
    46         else :
    58         else :
    47             return None
    59             return None
       
    60     
       
    61     STATES = {
       
    62         'DHCPACK':      'ack',
       
    63         'DHCPNAK':      'nak',
       
    64         'DHCPRELEASE':  'release',
       
    65         'DHCPDISCOVER': 'search',
       
    66         'DHCPREQUEST':  'search',
       
    67         'DHCPOFFER':    'search',
       
    68     }
       
    69 
       
    70     def state_class (self) :
       
    71         if self.state in self.STATES :
       
    72             return 'dhcp-' + self.STATES[self.state]
       
    73 
       
    74         else :
       
    75             return None
    48 
    76 
    49     def when (self) :
    77     def when (self) :
    50         return '{frm} - {to}'.format(
    78         return '{frm} - {to}'.format(
    51                 frm = self.first_seen.strftime(self.DATE_FMT),
    79                 frm = self.first_seen.strftime(self.DATE_FMT),
    52                 to  = self.last_seen.strftime(self.DATE_FMT),
    80                 to  = self.last_seen.strftime(self.DATE_FMT),
    73     def __unicode__ (self) :
   101     def __unicode__ (self) :
    74         return u"{host.ip} ({host.mac})".format(host=self)
   102         return u"{host.ip} ({host.mac})".format(host=self)
    75 
   103 
    76 db.mapper(Host, db.dhcp_hosts, properties=dict(
   104 db.mapper(Host, db.dhcp_hosts, properties=dict(
    77     id      = db.dhcp_hosts.c.rowid,
   105     id      = db.dhcp_hosts.c.rowid,
    78     #_mac    = db.dhcp_hosts.c.mac,
   106     state   = db.dhcp_hosts.c.last_msg,
    79     #_name   = db.dhcp_hosts.c.name,
       
    80 ))
   107 ))
    81 
   108 
    82 class BaseHandler (web.Handler) :
   109 class BaseHandler (web.Handler) :
    83     HOST_ATTRS = {
   110     HOST_ATTRS = {
    84         'id':   Host.id,
   111         'id':       Host.id,
    85         'ip':   Host.ip,
   112         'net':      Host.gw,
    86         'mac':  Host.mac,
   113         'ip':       Host.ip,
    87         'name': Host.name,
   114         'mac':      Host.mac,
    88         'seen': Host.last_seen,
   115         'name':     Host.name,
       
   116         'seen':     Host.last_seen,
       
   117         'state':    Host.state,
    89     }
   118     }
    90 
   119 
    91     HOST_SORT = Host.last_seen.desc()
   120     HOST_SORT = Host.last_seen.desc()
    92 
   121 
    93     def query (self) :
   122     def query (self) :
   112         COLS = (
   141         COLS = (
   113             #title          sort        filter      class
   142             #title          sort        filter      class
   114             ('IP',          'ip',       'ip',       'ip'    ),
   143             ('IP',          'ip',       'ip',       'ip'    ),
   115             ('MAC',         'mac',      'mac',      'mac'   ),
   144             ('MAC',         'mac',      'mac',      'mac'   ),
   116             ('Hostname',    'name',     False,      False   ),
   145             ('Hostname',    'name',     False,      False   ),
   117             ('Seen',        'seen',     False,      False   ),
   146             ('Network',     'net',      'net',      False   ),
       
   147             ('Seen',        'seen',     'seen',     False   ),
       
   148             ('State',       'state',    'state',    False   ), 
   118         )
   149         )
   119 
   150 
   120         def url (**opts) :
   151         def url (**opts) :
   121             args = dict()
   152             args = dict()
   122 
   153 
   165                         html.a(href=self.url(ListHandler, mac=host.mac))(
   196                         html.a(href=self.url(ListHandler, mac=host.mac))(
   166                             host.render_mac()
   197                             host.render_mac()
   167                         )
   198                         )
   168                     ),
   199                     ),
   169                     html.td(host.render_name()),
   200                     html.td(host.render_name()),
       
   201                     html.td(
       
   202                         host.gw
       
   203                     ),
   170                     html.td(host.when()),
   204                     html.td(host.when()),
       
   205                     html.td(class_=host.state_class())(host.state),
   171                 ) for i, host in enumerate(hosts)
   206                 ) for i, host in enumerate(hosts)
   172             ),
   207             ),
   173             html.tfoot(
   208             html.tfoot(
   174                 html.tr(
   209                 html.tr(
   175                     html.td(colspan=(1 + len(COLS)))(
   210                     html.td(colspan=(1 + len(COLS)))(
   188                 table,
   223                 table,
   189             )
   224             )
   190 
   225 
   191     def render_host (self, host, hosts) :
   226     def render_host (self, host, hosts) :
   192         attrs = (
   227         attrs = (
       
   228                 ('Network',     host.gw),
   193                 ('IP',          host.ip),
   229                 ('IP',          host.ip),
   194                 ('MAC',         host.mac),
   230                 ('MAC',         host.mac),
   195                 ('Hostname',    host.name),
   231                 ('Hostname',    host.name),
   196                 ('DNS',         host.dns()),
   232                 ('DNS',         host.dns()),
   197                 ('First seen',  host.first_seen),
   233                 ('First seen',  host.first_seen),
   198                 ('Last seen',   host.last_seen),
   234                 ('Last seen',   host.last_seen),
       
   235                 ('Last state',  host.state),
   199         )
   236         )
   200 
   237 
   201         return (
   238         return (
   202             html.h2('Host'),
   239             html.h2('Host'),
   203             html.dl(
   240             html.dl(
   226     def render (self) :
   263     def render (self) :
   227         return self.render_host(self.host, self.hosts)
   264         return self.render_host(self.host, self.hosts)
   228 
   265 
   229 class ListHandler (BaseHandler) :
   266 class ListHandler (BaseHandler) :
   230     def process (self) :
   267     def process (self) :
   231         self.hosts = self.query()
   268         hosts = self.query()
   232 
   269 
   233         # filter?
   270         # filter?
   234         self.filters = {}
   271         self.filters = {}
   235 
   272 
   236         for attr in self.HOST_ATTRS :
   273         for attr in self.HOST_ATTRS :
   237             value = self.request.args.get(attr)
   274             value = self.request.args.get(attr)
   238 
   275 
   239             if not value :
   276             if not value :
   240                 continue
   277                 continue
   241 
   278 
   242             # preprocess
   279             if attr == 'seen' :
   243             like = False
   280                 if value.isdigit() :
   244 
   281                     # specific date
   245             if value.endswith('*') :
   282                     date = datetime.datetime.strptime(value, Host.DATE_FMT).date()
   246                 like = value.replace('*', '%')
   283 
   247 
   284                     filter = db.between(date.strftime(Host.DATE_FMT), 
   248             elif attr == 'mac' :
   285                             db.func.strftime(Host.DATE_FMT, Host.first_seen),
   249                 value = Host.normalize_mac(value)
   286                             db.func.strftime(Host.DATE_FMT, Host.last_seen)
   250 
   287                     )
   251             # filter
   288                 else :
   252             col = self.HOST_ATTRS[attr]
   289                     # recent
   253 
   290                     timedelta = parse_timedelta(value)
   254             if like :
   291 
   255                 filter = (col.like(like))
   292                     # to seconds
       
   293                     timeout = timedelta.days * (24 * 60 * 60) + timedelta.seconds
       
   294                     
       
   295                     # XXX: for sqlite, pgsql should handle this natively?
       
   296                     # WHERE strftime('%s', 'now') - strftime('%s', last_seen) < :timeout
       
   297                     filter = (db.func.strftime('%s', 'now') - db.func.strftime('%s', Host.last_seen) < timeout)
       
   298             
       
   299             elif attr == 'ip' :
       
   300                 # parse as network expression
       
   301                 ip = IPv4Network(value)
       
   302 
       
   303                 if ip.masklen == 32 :
       
   304                     filter = (Host.ip == value)
       
   305                 else :
       
   306                     # XXX: column is IPv4 string literal format...
       
   307                     filter = ((Host.ip.op('&')(ip.mask)) == ip.base)
       
   308 
   256             else :
   309             else :
   257                 filter = (col == value)
   310                 # preprocess
       
   311                 like = False
       
   312 
       
   313                 if value.endswith('*') :
       
   314                     like = value.replace('*', '%')
       
   315 
       
   316                 elif attr == 'mac' :
       
   317                     value = Host.normalize_mac(value)
       
   318 
       
   319                 # filter
       
   320                 col = self.HOST_ATTRS[attr]
       
   321 
       
   322                 if like :
       
   323                     filter = (col.like(like))
       
   324                 else :
       
   325                     filter = (col == value)
   258             
   326             
   259             self.hosts = self.hosts.filter(filter)
   327             hosts = hosts.filter(filter)
   260             self.filters[attr] = value
   328             self.filters[attr] = value
       
   329        
       
   330         self.hosts = hosts
   261 
   331 
   262     def title (self) :
   332     def title (self) :
   263         if self.filters :
   333         if self.filters :
   264             return "DHCP Hosts: {filters}".format(filters=', '.join(self.filters.itervalues()))
   334             return "DHCP Hosts: {filters}".format(filters=', '.join(self.filters.itervalues()))
   265         else :
   335         else :