1 # vim: set encoding=utf-8 et sw=4 sts=4 :
4 from mako.template import Template
5 from mako.runtime import Context
10 import cPickle as pickle
15 from seqtools import Sequence
21 from dispatcher import handler, HandlerError, Handler
23 class HandlerError(RuntimeError): pass
32 __ALL__ = ('DnsHandler',)
37 pickle_zones = 'zones'
39 config_filename = 'named.conf'
40 zone_filename = 'zoneX.zone'
41 zone_filename_ext = '.zone'
43 template_dir = path.join(path.dirname(__file__), 'templates')
46 class Error(HandlerError):
48 Error(command) -> Error instance :: Base DnsHandler exception class.
50 All exceptions raised by the DnsHandler inherits from this one, so you can
51 easily catch any DnsHandler exception.
53 message - A descriptive error message.
56 def __init__(self, message):
57 r"Initialize the Error object. See class documentation for more info."
58 self.message = message
63 class ZoneError(Error, KeyError):
65 ZoneError(zonename) -> ZoneError instance
67 This is the base exception for all zone related errors.
70 def __init__(self, zonename):
71 r"Initialize the object. See class documentation for more info."
72 self.message = 'Zone error: "%s"' % zonename
75 class ZoneNotFoundError(ZoneError):
77 ZoneNotFoundError(hostname) -> ZoneNotFoundError instance
79 This exception is raised when trying to operate on a zone that doesn't
83 def __init__(self, zonename):
84 r"Initialize the object. See class documentation for more info."
85 self.message = 'zone not found: "%s"' % zonename
88 class ZoneAlreadyExistsError(ZoneError):
90 ZoneAlreadyExistsError(hostname) -> ZoneAlreadyExistsError instance
92 This exception is raised when trying to add a zonename that already exists.
95 def __init__(self, zonename):
96 r"Initialize the object. See class documentation for more info."
97 self.message = 'Zone already exists: "%s"' % zonename
100 class HostError(Error, KeyError):
102 HostError(hostname) -> HostError instance
104 This is the base exception for all host related errors.
107 def __init__(self, hostname):
108 r"Initialize the object. See class documentation for more info."
109 self.message = 'Host error: "%s"' % hostname
111 class HostAlreadyExistsError(HostError):
113 HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
115 This exception is raised when trying to add a hostname that already exists.
118 def __init__(self, hostname):
119 r"Initialize the object. See class documentation for more info."
120 self.message = 'Host already exists: "%s"' % hostname
122 class HostNotFoundError(HostError):
124 HostNotFoundError(hostname) -> HostNotFoundError instance
126 This exception is raised when trying to operate on a hostname that doesn't
130 def __init__(self, hostname):
131 r"Initialize the object. See class documentation for more info."
132 self.message = 'Host not found: "%s"' % hostname
135 class MailExchangeError(Error, KeyError):
137 MailExchangeError(hostname) -> MailExchangeError instance
139 This is the base exception for all mail exchange related errors.
142 def __init__(self, mx):
143 r"Initialize the object. See class documentation for more info."
144 self.message = 'Mail Exchange error: "%s"' % mx
147 class MailExchangeAlreadyExistsError(MailExchangeError):
149 MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError instance
151 This exception is raised when trying to add a mail exchange that already exists.
154 def __init__(self, mx):
155 r"Initialize the object. See class documentation for more info."
156 self.message = 'Mail Exchange already exists: "%s"' % mx
159 class MailExchangeNotFoundError(MailExchangeError):
161 MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance
163 This exception is raised when trying to operate on a mail exchange that doesn't
167 def __init__(self, mx):
168 r"Initialize the object. See class documentation for more info."
169 self.message = 'Mail Exchange not found: "%s"' % mx
173 class NameServerError(Error, KeyError):
175 NameServerError(ns) -> NameServerError instance
177 This is the base exception for all name server related errors.
180 def __init__(self, ns):
181 r"Initialize the object. See class documentation for more info."
182 self.message = 'Name Server error: "%s"' % ns
184 class NameServerAlreadyExistsError(NameServerError):
186 NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError instance
188 This exception is raised when trying to add a name server that already exists.
191 def __init__(self, ns):
192 r"Initialize the object. See class documentation for more info."
193 self.message = 'Name server already exists: "%s"' % ns
195 class NameServerNotFoundError(NameServerError):
197 NameServerNotFoundError(hostname) -> NameServerNotFoundError instance
199 This exception is raised when trying to operate on a name server that doesn't
203 def __init__(self, ns):
204 r"Initialize the object. See class documentation for more info."
205 self.message = 'Mail Exchange not found: "%s"' % ns
208 class ParameterError(Error, KeyError):
210 ParameterError(paramname) -> ParameterError instance
212 This is the base exception for all DhcpHandler parameters related errors.
215 def __init__(self, paramname):
216 r"Initialize the object. See class documentation for more info."
217 self.message = 'Parameter error: "%s"' % paramname
219 class ParameterNotFoundError(ParameterError):
221 ParameterNotFoundError(hostname) -> ParameterNotFoundError instance
223 This exception is raised when trying to operate on a parameter that doesn't
227 def __init__(self, paramname):
228 r"Initialize the object. See class documentation for more info."
229 self.message = 'Parameter not found: "%s"' % paramname
231 class Host(Sequence):
232 def __init__(self, name, ip):
237 return (self.name, self.ip)
239 class HostHandler(Handler):
240 def __init__(self,zones):
243 @handler(u'Adds a host to a zone')
244 def add(self, name, hostname, ip):
245 if not name in self.zones:
246 raise ZoneNotFoundError(name)
247 if hostname in self.zones[name].hosts:
248 raise HostAlreadyExistsError(hostname)
249 self.zones[name].hosts[hostname] = Host(hostname, ip)
250 self.zones[name].mod = True
252 @handler(u'Updates a host ip in a zone')
253 def update(self, name, hostname, ip):
254 if not name in self.zones:
255 raise ZoneNotFoundError(name)
256 if not hostname in self.zones[name].hosts:
257 raise HostNotFoundError(name)
258 self.zones[name].hosts[hostname].ip = ip
259 self.zones[name].mod = True
261 @handler(u'Deletes a host from a zone')
262 def delete(self, name, hostname):
263 if not name in self.zones:
264 raise ZoneNotFoundError(name)
265 if not hostname in self.zones[name].hosts:
266 raise HostNotFoundError(name)
267 del self.zones[name].hosts[hostname]
268 self.zones[name].mod = True
270 @handler(u'Lists hosts')
272 return self.zones.keys()
274 @handler(u'Get insormation about all hosts')
276 return self.zones.values()
279 class MailExchange(Sequence):
281 def __init__(self, mx, prio):
286 return (self.mx, self.prio)
288 class MailExchangeHandler(Handler):
290 def __init__(self, zones):
293 @handler(u'Adds a mail exchange to a zone')
294 def add(self, zonename, mx, prio):
295 if not zonename in self.zones:
296 raise ZoneNotFoundError(zonename)
297 if mx in self.zones[zonename].mxs:
298 raise MailExchangeAlreadyExistsError(mx)
299 self.zones[zonename].mxs[mx] = MailExchange(mx, prio)
300 self.zones[zonename].mod = True
302 @handler(u'Updates a mail exchange priority')
303 def update(self, zonename, mx, prio):
304 if not zonename in self.zones:
305 raise ZoneNotFoundError(zonename)
306 if not mx in self.zones[zonename].mxs:
307 raise MailExchangeNotFoundError(mx)
308 self.zones[zonename].mxs[mx].prio = prio
309 self.zones[zonename].mod = True
311 @handler(u'Deletes a mail exchange from a zone')
312 def delete(self, zonename, mx):
313 if not zonename in self.zones:
314 raise ZoneNotFoundError(zonename)
315 if not mx in self.zones[zonename].mxs:
316 raise MailExchangeNotFoundError(mx)
317 del self.zones[zonename].mxs[mx]
318 self.zones[zonename].mod = True
320 @handler(u'Lists mail exchangers')
322 return self.zones.keys()
324 @handler(u'Get information about all mail exchangers')
326 return self.zones.values()
329 class NameServer(Sequence):
331 def __init__(self, name):
337 class NameServerHandler(Handler):
339 def __init__(self, zones):
342 @handler(u'Adds a name server to a zone')
343 def add(self, zone, ns):
344 if not zone in self.zones:
345 raise ZoneNotFoundError(zone)
346 if ns in self.zones[zone].nss:
347 raise NameServerAlreadyExistsError(ns)
348 self.zones[zone].nss[ns] = NameServer(ns)
349 self.zones[zone].mod = True
351 @handler(u'Deletes a name server from a zone')
352 def delete(self, zone, ns):
353 if not zone in self.zones:
354 raise ZoneNotFoundError(zone)
355 if not ns in self.zones[zone].nss:
356 raise NameServerNotFoundError(ns)
357 del self.zones[zone].nss[ns]
358 self.zones[zone].mod = True
360 @handler(u'Lists name servers')
362 return self.zones.keys()
364 @handler(u'Get information about all name servers')
366 return self.zones.values()
369 class Zone(Sequence):
370 def __init__(self, name):
380 return (self.name, self.hosts, self.mxs, self.nss)
382 class ZoneHandler(Handler):
384 r"""ZoneHandler(zones) -> ZoneHandler instance :: Handle a list of zones.
386 This class is a helper for DnsHandler to do all the work related to zone
389 zones - A dictionary with string keys (zone name) and Zone instances values.
391 def __init__(self, zones):
394 @handler(u'Adds a zone')
396 if name in self.zones:
397 if self.zones[name].dele == True:
398 self.zones[name].dele = False
400 raise ZoneAlreadyExistsError(name)
401 self.zones[name] = Zone(name)
402 self.zones[name].mod = True
403 self.zones[name].new = True
406 @handler(u'Deletes a zone')
407 def delete(self, name):
408 r"delete(name) -> None :: Delete a zone from the zone list."
409 if not name in self.zones:
410 raise ZoneNotFoundError(name)
411 self.zones[name].dele = True
413 @handler(u'Lists zones')
415 return self.zones.keys()
417 @handler(u'Get information about all zones')
419 return self.zones.values()
421 class DnsHandler(Handler):
422 r"""DnsHandler([pickle_dir[, config_dir]]) -> DnsHandler instance.
424 Handles DNS service commands for the dns program.
426 pickle_dir - Directory where to write the persistent configuration data.
428 config_dir - Directory where to store de generated configuration files.
430 Both defaults to the current working directory.
433 def __init__(self, pickle_dir='.', config_dir='.'):
434 r"Initialize DnsHandler object, see class documentation for details."
435 self.pickle_dir = pickle_dir
436 self.config_dir = config_dir
437 c_filename = path.join(template_dir, config_filename)
438 z_filename = path.join(template_dir, zone_filename)
439 self.config_template = Template(filename=c_filename)
440 self.zone_template = Template(filename=z_filename)
452 self.host = HostHandler(self.zones)
453 self.zone = ZoneHandler(self.zones)
454 self.mx = MailExchangeHandler(self.zones)
455 self.ns = NameServerHandler(self.zones)
458 @handler(u'Set a DNS parameter')
459 def set(self, param, value):
460 r"set(param, value) -> None :: Set a DNS parameter."
461 if not param in self.vars:
462 raise ParameterNotFoundError(param)
463 self.vars[param] = value
466 @handler(u'Get a DNS parameter')
467 def get(self, param):
468 r"get(param) -> None :: Get a DNS parameter."
469 if not param in self.vars:
470 raise ParameterNotFoundError(param)
471 return self.vars[param]
473 @handler(u'List DNS parameters')
475 return self.vars.keys()
477 @handler(u'Get all DNS parameters, with their values.')
479 return self.vars.values()
481 @handler(u'Start the service.')
483 r"start() -> None :: Start the DNS service."
484 #esto seria para poner en una interfaz
485 #y seria el hook para arrancar el servicio
488 @handler(u'Stop the service.')
490 r"stop() -> None :: Stop the DNS service."
491 #esto seria para poner en una interfaz
492 #y seria el hook para arrancar el servicio
495 @handler(u'Restart the service.')
497 r"restart() -> None :: Restart the DNS service."
498 #esto seria para poner en una interfaz
499 #y seria el hook para arrancar el servicio
502 @handler(u'Reload the service config (without restarting, if possible)')
504 r"reload() -> None :: Reload the configuration of the DNS service."
505 #esto seria para poner en una interfaz
506 #y seria el hook para arrancar el servicio
509 @handler(u'Commit the changes (reloading the service, if necessary).')
511 r"commit() -> None :: Commit the changes and reload the DNS service."
512 #esto seria para poner en una interfaz
513 #y seria que hace el pickle deberia llamarse
514 #al hacerse un commit
519 @handler(u'Discard all the uncommited changes.')
521 r"rollback() -> None :: Discard the changes not yet commited."
525 r"_dump() -> None :: Dump all persistent data to pickle files."
526 # XXX podría ir en una clase base
527 self._dump_var(self.vars, pickle_vars)
528 self._dump_var(self.zones, pickle_zones)
531 r"_load() -> None :: Load all persistent data from pickle files."
532 # XXX podría ir en una clase base
533 self.vars = self._load_var(pickle_vars)
534 self.zones = self._load_var(pickle_zones)
536 def _pickle_filename(self, name):
537 r"_pickle_filename() -> string :: Construct a pickle filename."
538 # XXX podría ir en una clase base
539 return path.join(self.pickle_dir, name) + pickle_ext
541 def _dump_var(self, var, name):
542 r"_dump_var() -> None :: Dump a especific variable to a pickle file."
543 # XXX podría ir en una clase base
544 pkl_file = file(self._pickle_filename(name), 'wb')
545 pickle.dump(var, pkl_file, 2)
548 def _load_var(self, name):
549 r"_load_var() -> object :: Load a especific pickle file."
550 # XXX podría ir en una clase base
551 return pickle.load(file(self._pickle_filename(name)))
553 def _write_config(self):
554 r"_write_config() -> None :: Generate all the configuration files."
555 # XXX podría ir en una clase base, ver como generalizar variables a
556 # reemplazar en la template
558 delete_zones = list()
559 for a_zone in self.zones.values():
562 # TODO freeze de la zona
563 print 'Freezing zone ' + a_zone.name + zone_filename_ext
564 zone_out_file = file(path.join(self.config_dir, a_zone.name + zone_filename_ext), 'w')
568 hosts = a_zone.hosts.values(),
569 mxs = a_zone.mxs.values(),
570 nss = a_zone.nss.values()
572 self.zone_template.render_context(ctx)
573 zone_out_file.close()
576 # TODO unfreeze de la zona
577 print 'Unfreezing zone ' + a_zone.name + zone_filename_ext
582 #borro el archivo .zone
585 unlink(path.join(self.config_dir, a_zone.name + zone_filename_ext))
587 #la excepcion pude darse en caso que haga un add de una zona y
588 #luego el del, como no hice commit, no se crea el archivo
590 delete_zones.append(a_zone.name)
592 for z in delete_zones:
596 cfg_out_file = file(path.join(self.config_dir, config_filename), 'w')
597 ctx = Context(cfg_out_file, zones=self.zones.values(), **self.vars)
598 self.config_template.render_context(ctx)
601 print 'Restarting service'
605 if __name__ == '__main__':
609 dns.set('isp_dns1','la_garcha.com')
610 dns.set('bind_addr1','localhost')
611 dns.zone.add('zona_loca.com')
612 #dns.zone.update('zona_loca.com','ns1.dominio.com')
614 dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
615 dns.host.update('zona_loca.com','hostname_loco','192.168.0.66')
617 dns.host.add('zona_loca.com','hostname_kuak','192.168.0.23')
618 dns.host.delete('zona_loca.com','hostname_kuak')
620 dns.host.add('zona_loca.com','hostname_kuang','192.168.0.24')
621 dns.host.add('zona_loca.com','hostname_chan','192.168.0.25')
622 dns.host.add('zona_loca.com','hostname_kaine','192.168.0.26')
624 dns.mx.add('zona_loca.com','mx1.sarasa.com',10)
625 dns.mx.update('zona_loca.com','mx1.sarasa.com',20)
626 dns.mx.add('zona_loca.com','mx2.sarasa.com',30)
627 dns.mx.add('zona_loca.com','mx3.sarasa.com',40)
628 dns.mx.delete('zona_loca.com','mx3.sarasa.com')
630 dns.ns.add('zona_loca.com','ns1.jua.com')
631 dns.ns.add('zona_loca.com','ns2.jua.com')
632 dns.ns.add('zona_loca.com','ns3.jua.com')
633 dns.ns.delete('zona_loca.com','ns3.jua.com')
635 dns.zone.add('zona_oscura')
637 dns.host.add('zona_oscura','hostname_a','192.168.0.24')
638 dns.host.add('zona_oscura','hostname_b','192.168.0.25')
639 dns.host.add('zona_oscura','hostname_c','192.168.0.26')
641 dns.zone.delete('zona_oscura')
646 print dns.zone.show() + '\n'
648 print dns.host.show()
652 dns.zone.update('zone-sarasa','lalal')
653 except ZoneNotFoundError, inst:
654 print 'Error: ', inst
657 dns.zone.delete('zone-sarasa')
658 except ZoneNotFoundError, inst:
659 print 'Error: ', inst
662 dns.zone.add('zona_loca.com','ns1.dom.com','ns2.dom.com')
663 except ZoneAlreadyExistsError, inst:
664 print 'Error: ', inst
668 dns.host.update('zone-sarasa','kuak','192.68')
669 except ZoneNotFoundError, inst:
670 print 'Error: ', inst
673 dns.host.update('zona_loca.com','kuak','192.68')
674 except HostNotFoundError, inst:
675 print 'Error: ', inst
678 dns.host.delete('zone-sarasa','lala')
679 except ZoneNotFoundError, inst:
680 print 'Error: ', inst
683 dns.host.delete('zona_loca.com','lala')
684 except HostNotFoundError, inst:
685 print 'Error: ', inst
688 dns.host.add('zona','hostname_loco','192.168.0.23')
689 except ZoneNotFoundError, inst:
690 print 'Error: ', inst
693 dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
694 except HostAlreadyExistsError, inst:
695 print 'Error: ', inst