pvl/dhcp/hosts.py
changeset 174 6f339a8a87dc
child 193 4db45cb35dc3
equal deleted inserted replaced
173:5fc4c5e83b72 174:6f339a8a87dc
       
     1 """
       
     2     Track active DHCP hosts on network by dhcp messages.
       
     3 """
       
     4 
       
     5 import logging; log = logging.getLogger('pvl.dhcp.hosts')
       
     6 
       
     7 # XXX: from db.dhcp_leases instead?
       
     8 import pvl.verkko.db as db
       
     9 
       
    10 class DHCPHostsDatabase (object) :
       
    11     """
       
    12         pvl.verkko.Database dhcp_hosts model for updates.
       
    13     """
       
    14 
       
    15     def __init__ (self, db) :
       
    16         self.db = db
       
    17 
       
    18     def create (self) :
       
    19         """
       
    20             CREATE TABLEs
       
    21         """
       
    22 
       
    23         log.info("Creating database tables: dhcp_hosts")
       
    24         db.dhcp_hosts.create(self.db.engine, checkfirst=True)
       
    25 
       
    26     def insert (self, attrs) :
       
    27         """
       
    28             INSERT new host
       
    29         """
       
    30 
       
    31         query = db.dhcp_hosts.insert().values(
       
    32                 ip          = attrs['ip'],
       
    33                 mac         = attrs['mac'],
       
    34                 gw          = attrs['gw'],
       
    35 
       
    36                 first_seen  = attrs['timestamp'],
       
    37                 count       = 1,
       
    38 
       
    39                 last_seen   = attrs['timestamp'],
       
    40                 state       = attrs['state'],
       
    41                 
       
    42                 name        = attrs.get('name'),
       
    43                 error       = attrs.get('error'),
       
    44         )
       
    45         
       
    46         # -> id
       
    47         return self.db.insert(query)
       
    48 
       
    49     def update (self, attrs) :
       
    50         """
       
    51             UPDATE existing host, or return False if not found.
       
    52         """
       
    53 
       
    54         table = db.dhcp_hosts
       
    55 
       
    56         query = table.update()
       
    57         query = query.where((table.c.ip == attrs['ip']) & (table.c.mac == attrs['mac']) & (table.c.gw == attrs['gw']))
       
    58         query = query.values(
       
    59                 count       = db.func.coalesce(table.c.count, 0) + 1,
       
    60 
       
    61                 # set
       
    62                 last_seen   = attrs['timestamp'],
       
    63                 state       = attrs['state'],
       
    64         )
       
    65         
       
    66         if 'name' in attrs :
       
    67             query = query.values(name = attrs['name'])
       
    68         
       
    69         if 'error' in attrs :
       
    70             query = query.values(error = attrs['error'])
       
    71 
       
    72         # any matched rows?
       
    73         return self.db.update(query)
       
    74 
       
    75     def __call__ (self, item) :
       
    76         """
       
    77             Process given DHCP syslog message to update the hosts table.
       
    78         """
       
    79 
       
    80         attrs = {}
       
    81         
       
    82         # ignore unless we have enough info to fully identify the client
       
    83         # this means that we omit DHCPDISCOVER messages, but we get the OFFER/REQUEST/ACK
       
    84         if any(name not in item for name in ('lease', 'hwaddr', 'gateway')) :
       
    85             # ignore; we require these
       
    86             return
       
    87 
       
    88         # do not override error from request on NAK; clear otherwise
       
    89         if item.get('type') == 'DHCPNAK' :
       
    90             pass
       
    91         else :
       
    92             attrs['error'] = item.get('error')
       
    93 
       
    94         # do not override name unless known
       
    95         if item.get('name') :
       
    96             attrs['name'] = item.get('name')
       
    97 
       
    98         # db: syslog
       
    99         ATTR_MAP = (
       
   100             ('ip',          'lease'),
       
   101             ('mac',         'hwaddr'),
       
   102             ('gw',          'gateway'),
       
   103 
       
   104             ('timestamp',   'timestamp'),
       
   105             ('state',       'type'),
       
   106         )
       
   107 
       
   108         # generic attrs
       
   109         for key, name in ATTR_MAP :
       
   110             attrs[key] = item.get(name)
       
   111 
       
   112         # update existing?
       
   113         if self.update(attrs) :
       
   114             log.info("Update: %s", attrs)
       
   115 
       
   116         else :
       
   117             # new
       
   118             log.info("Insert: %s", attrs)
       
   119             self.insert(attrs)
       
   120 
       
   121