pvl/dhcp/leases.py
changeset 174 6f339a8a87dc
parent 169 a81ca751664d
child 211 cf74bbb95d2b
equal deleted inserted replaced
173:5fc4c5e83b72 174:6f339a8a87dc
   203 
   203 
   204         for line in lines :
   204         for line in lines :
   205             for item in self.parse(line) :
   205             for item in self.parse(line) :
   206                 yield item
   206                 yield item
   207 
   207 
   208 class DHCPLeasesDatabase (object) :
   208 class DHCPLeases (object) :
   209     """
   209     """
   210         Process log-structured leases file.
   210         Process log-structured leases file, updated by dhcpd.
   211     """
   211     """
   212 
   212 
   213     LEASE_DATES = ('starts', 'ends', 'tstp', 'tsfp', 'atsfp', 'cltt')
   213     LEASE_DATES = ('starts', 'ends', 'tstp', 'tsfp', 'atsfp', 'cltt')
   214 
   214 
   215     # default format
   215     # default format
   222         """
   222         """
   223             path        - path to dhcpd.leases file
   223             path        - path to dhcpd.leases file
   224         """
   224         """
   225 
   225 
   226         # tail; handles file re-writes
   226         # tail; handles file re-writes
   227         self.source = pvl.syslog.tail.TailFile(path)
   227         self.source = pvl.syslog.tail.Tail(path)
   228 
   228 
   229         # parser state
   229         # parser state
   230         self.parser = DHCPLeasesParser()
   230         self.parser = DHCPLeasesParser()
   231 
   231 
   232         # initial leases state
   232         # initial leases state
   233         self.leases = None
   233         self._leases = None
   234 
   234 
   235     def reset (self) :
   235     def reset (self) :
   236         """
   236         """
   237             Reset state, if we started to read a new file.
   237             Reset state, if we started to read a new file.
   238         """
   238         """
   239 
   239 
   240         self.leases = {}
   240         self._leases = {}
   241 
   241 
   242     def process_lease_item_date (self, args) :
   242     def process_lease_item_date (self, args) :
   243         """
   243         """
   244             Process lease-item date spec into datetime.
   244             Process lease-item date spec into datetime.
   245 
   245 
   294 
   294 
   295             Returns the lease object, and a possible old lease.
   295             Returns the lease object, and a possible old lease.
   296         """
   296         """
   297 
   297 
   298         # replace any existing
   298         # replace any existing
   299         lease = self.leases[lease_name] = {}
   299         lease = self._leases[lease_name] = {}
   300 
   300 
   301         # meta
   301         # meta
   302         lease['lease'] = lease_name
   302         lease['lease'] = lease_name
   303 
   303 
   304         # parse items
   304         # parse items
   352             # the lease address
   352             # the lease address
   353             lease, = args
   353             lease, = args
   354 
   354 
   355             log.debug("lease: %s: %s", lease, items)
   355             log.debug("lease: %s: %s", lease, items)
   356             
   356             
   357             if lease in self.leases :
   357             if lease in self._leases :
   358                 old = self.leases[lease]
   358                 old = self._leases[lease]
   359             else :
   359             else :
   360                 old = None
   360                 old = None
   361 
   361 
   362             new = self.process_lease(lease, items)
   362             new = self.process_lease(lease, items)
   363 
   363 
   367             return new
   367             return new
   368 
   368 
   369         else :
   369         else :
   370             log.warn("unknown block: %s: %s", type, args)
   370             log.warn("unknown block: %s: %s", type, args)
   371 
   371 
   372     def process (self) :
   372     def readleases (self) :
   373         """
   373         """
   374             Read new lines from the leases database and update our state.
   374             Read new lines from the leases database and update our state.
   375             
   375 
   376             XXX: Returns
   376             Yields changed leases. On startup and on periodic database reset, all leases are yielded.
   377                 (sync, leases)
       
   378 
       
   379             whereby sync is normally False, and leases the set of (possibly) changed leases, unless during initial
       
   380             startup and on database replacement, when the sync is True, and the entire set of valid leases is returned.
       
   381         """
   377         """
   382         
   378         
   383         # handle file replace by reading until EOF
   379         # handle file replace by reading until EOF
   384         sync = False
   380         sync = False
   385 #        leases = []
   381 #        leases = []
   386 
   382 
   387         if self.leases is None :
   383         if self._leases is None :
   388             # initial sync
   384             # initial sync
   389             self.reset()
   385             self.reset()
   390             sync = True
   386             sync = True
   391         
   387         
   392         # parse in any new lines from TailFile... yields None if the file was replaced
   388         # parse in any new lines from TailFile... yields None if the file was replaced
   411 #        if sync :
   407 #        if sync :
   412 #            return True, self.leases.values()
   408 #            return True, self.leases.values()
   413 #        else :
   409 #        else :
   414 #            return False, leases
   410 #            return False, leases
   415 
   411 
   416     def __iter__ (self) :
   412     __iter__ = readleases
       
   413 
       
   414     def leases (self) :
   417         """
   415         """
   418             Iterate over all leases.
   416             Iterate over all leases.
   419         """
   417         """
   420 
   418 
   421         return self.leases.itervalues()
   419         return self.leases.itervalues()
   438             # XXX: mark as, "expired", even they next-binding-state is probably "free"
   436             # XXX: mark as, "expired", even they next-binding-state is probably "free"
   439             state = 'expired' # lease['next-binding-state']
   437             state = 'expired' # lease['next-binding-state']
   440 
   438 
   441         return state
   439         return state
   442 
   440 
       
   441 # XXX: from db.dhcp_leases instead?
       
   442 import pvl.verkko.db as db
       
   443 
       
   444 class DHCPLeasesDatabase (object) :
       
   445     """
       
   446         pvl.verkko.Database dhcp_leases model for updates.
       
   447     """
       
   448 
       
   449     def __init__ (self, db) :
       
   450         """
       
   451             db      - pvl.verkko.Database
       
   452         """
       
   453 
       
   454         self.db = db
       
   455 
       
   456     def create (self) :
       
   457         """
       
   458             CREATE TABLEs
       
   459         """
       
   460 
       
   461         log.info("Creating database tables: dhcp_leases")
       
   462         db.dhcp_leases.create(self.db.engine, checkfirst=True)
       
   463 
       
   464     def update (self, lease) :
       
   465         """
       
   466             Try an extend an existing lease?
       
   467         """
       
   468 
       
   469         c = db.dhcp_leases.c
       
   470 
       
   471         ip = lease['lease']
       
   472         mac = lease.get('hwaddr')
       
   473         starts = lease['starts']
       
   474         ends = lease.get('ends')
       
   475 
       
   476         update = db.dhcp_leases.update()
       
   477         
       
   478         # XXX: if ends is None?
       
   479         if mac :
       
   480             # renew lease..?
       
   481             update = update.where((c.ip == ip) & (c.mac == mac) & ((starts < c.ends) | (c.ends == None)))
       
   482         else :
       
   483             # new state for lease..?
       
   484             update = update.where((c.ip == ip) & ((starts < c.ends) | (c.ends == ends)))
       
   485 
       
   486         update = update.values(
       
   487                 state       = lease['binding-state'],
       
   488                 next        = lease.get('next-binding-state'),
       
   489                 ends        = ends,
       
   490         )
       
   491 
       
   492         if lease.get('client-hostname') :
       
   493             update = update.values(hostname = lease['client-hostname'])
       
   494 
       
   495         return self.db.update(update) > 0
       
   496 
       
   497     def insert (self, lease) :
       
   498         """
       
   499             Record a new lease.
       
   500         """
       
   501 
       
   502         c = db.dhcp_leases.c
       
   503 
       
   504         query = db.dhcp_leases.insert().values(
       
   505             ip          = lease['lease'],
       
   506             mac         = lease['hwaddr'],
       
   507             hostname    = lease.get('client-hostname'),
       
   508 
       
   509             starts      = lease['starts'],
       
   510             ends        = lease.get('ends'),
       
   511 
       
   512             state       = lease['binding-state'],
       
   513             next        = lease.get('next-binding-state'),
       
   514         )
       
   515 
       
   516         return self.db.insert(query)
       
   517 
       
   518     def __call__ (self, lease) :
       
   519         """
       
   520             Process given DHCP lease to update currently active lease, or insert a new one.
       
   521 
       
   522             XXX: transaction? *leases?
       
   523         """
       
   524 
       
   525         # update existing?
       
   526         if self.update(lease) :
       
   527             log.info("Update: %s", lease)
       
   528 
       
   529         elif lease.get('hwaddr') :
       
   530             # new
       
   531             id = self.insert(lease)
       
   532 
       
   533             log.info("Insert: %s -> %d", lease, id)
       
   534 
       
   535         else :
       
   536             # may be a free lease
       
   537             log.warn("Ignored lease: %s", lease)
       
   538 
       
   539 
   443 if __name__ == '__main__' :
   540 if __name__ == '__main__' :
   444     import logging
   541     import logging
   445 
   542 
   446     logging.basicConfig()
   543     logging.basicConfig()
   447 
   544