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 |
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 |
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 |