]> git.llucax.com Git - software/pymin.git/blob - services/dns/__init__.py
Se agregar el dns al manejo del dispatcher.
[software/pymin.git] / services / dns / __init__.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 # TODO COMMENT
4 from mako.template import Template
5 from mako.runtime import Context
6 from os import path
7 from os import unlink
8
9 try:
10     import cPickle as pickle
11 except ImportError:
12     import pickle
13
14 try:
15     from seqtools import Sequence
16 except ImportError:
17     # NOP for testing
18     class Sequence: pass
19
20 try:
21     from dispatcher import handler, HandlerError
22 except ImportError:
23     class HandlerError(RuntimeError): pass
24     def handler(f): return f # NOP for testing
25
26
27
28 __ALL__ = ('DnsHandler',)
29
30 pickle_ext = '.pkl'
31
32 pickle_vars = 'vars'
33 pickle_zones = 'zones'
34
35 config_filename = 'named.conf'
36 zone_filename = 'zoneX.zone'
37 zone_filename_ext = '.zone'
38
39 template_dir = path.join(path.dirname(__file__), 'templates')
40
41
42 class Error(HandlerError):
43     r"""
44     Error(command) -> Error instance :: Base DnsHandler exception class.
45
46     All exceptions raised by the DnsHandler inherits from this one, so you can
47     easily catch any DnsHandler exception.
48
49     message - A descriptive error message.
50     """
51
52     def __init__(self, message):
53         r"Initialize the Error object. See class documentation for more info."
54         self.message = message
55
56     def __str__(self):
57         return self.message
58
59 class ZoneError(Error, KeyError):
60     r"""
61     ZoneError(zonename) -> ZoneError instance
62
63     This is the base exception for all zone related errors.
64     """
65
66     def __init__(self, zonename):
67         r"Initialize the object. See class documentation for more info."
68         self.message = 'Zone error: "%s"' % zonename
69
70
71 class ZoneNotFoundError(ZoneError):
72     r"""
73     ZoneNotFoundError(hostname) -> ZoneNotFoundError instance
74
75     This exception is raised when trying to operate on a zone that doesn't
76     exists.
77     """
78
79     def __init__(self, zonename):
80         r"Initialize the object. See class documentation for more info."
81         self.message = 'zone not found: "%s"' % zonename
82
83
84 class ZoneAlreadyExistsError(ZoneError):
85     r"""
86     ZoneAlreadyExistsError(hostname) -> ZoneAlreadyExistsError instance
87
88     This exception is raised when trying to add a zonename that already exists.
89     """
90
91     def __init__(self, zonename):
92         r"Initialize the object. See class documentation for more info."
93         self.message = 'Zone already exists: "%s"' % zonename
94
95
96 class HostError(Error, KeyError):
97     r"""
98     HostError(hostname) -> HostError instance
99
100     This is the base exception for all host related errors.
101     """
102
103     def __init__(self, hostname):
104         r"Initialize the object. See class documentation for more info."
105         self.message = 'Host error: "%s"' % hostname
106
107 class HostAlreadyExistsError(HostError):
108     r"""
109     HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
110
111     This exception is raised when trying to add a hostname that already exists.
112     """
113
114     def __init__(self, hostname):
115         r"Initialize the object. See class documentation for more info."
116         self.message = 'Host already exists: "%s"' % hostname
117
118 class HostNotFoundError(HostError):
119     r"""
120     HostNotFoundError(hostname) -> HostNotFoundError instance
121
122     This exception is raised when trying to operate on a hostname that doesn't
123     exists.
124     """
125
126     def __init__(self, hostname):
127         r"Initialize the object. See class documentation for more info."
128         self.message = 'Host not found: "%s"' % hostname
129
130
131 class MailExchangeError(Error, KeyError):
132     r"""
133     MailExchangeError(hostname) -> MailExchangeError instance
134
135     This is the base exception for all mail exchange related errors.
136     """
137
138     def __init__(self, mx):
139         r"Initialize the object. See class documentation for more info."
140         self.message = 'Mail Exchange error: "%s"' % mx
141
142
143 class MailExchangeAlreadyExistsError(MailExchangeError):
144     r"""
145     MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError instance
146
147     This exception is raised when trying to add a mail exchange that already exists.
148     """
149
150     def __init__(self, mx):
151         r"Initialize the object. See class documentation for more info."
152         self.message = 'Mail Exchange already exists: "%s"' % mx
153
154
155 class MailExchangeNotFoundError(MailExchangeError):
156     r"""
157     MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance
158
159     This exception is raised when trying to operate on a mail exchange that doesn't
160     exists.
161     """
162
163     def __init__(self, mx):
164         r"Initialize the object. See class documentation for more info."
165         self.message = 'Mail Exchange not found: "%s"' % mx
166
167
168
169 class NameServerError(Error, KeyError):
170     r"""
171     NameServerError(ns) -> NameServerError instance
172
173     This is the base exception for all name server related errors.
174     """
175
176     def __init__(self, ns):
177         r"Initialize the object. See class documentation for more info."
178         self.message = 'Name Server error: "%s"' % ns
179
180 class NameServerAlreadyExistsError(NameServerError):
181     r"""
182     NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError instance
183
184     This exception is raised when trying to add a name server that already exists.
185     """
186
187     def __init__(self, ns):
188         r"Initialize the object. See class documentation for more info."
189         self.message = 'Name server already exists: "%s"' % ns
190
191 class NameServerNotFoundError(NameServerError):
192     r"""
193     NameServerNotFoundError(hostname) -> NameServerNotFoundError instance
194
195     This exception is raised when trying to operate on a name server that doesn't
196     exists.
197     """
198
199     def __init__(self, ns):
200         r"Initialize the object. See class documentation for more info."
201         self.message = 'Mail Exchange not found: "%s"' % ns
202
203
204 class ParameterError(Error, KeyError):
205     r"""
206     ParameterError(paramname) -> ParameterError instance
207
208     This is the base exception for all DhcpHandler parameters related errors.
209     """
210
211     def __init__(self, paramname):
212         r"Initialize the object. See class documentation for more info."
213         self.message = 'Parameter error: "%s"' % paramname
214
215 class ParameterNotFoundError(ParameterError):
216     r"""
217     ParameterNotFoundError(hostname) -> ParameterNotFoundError instance
218
219     This exception is raised when trying to operate on a parameter that doesn't
220     exists.
221     """
222
223     def __init__(self, paramname):
224         r"Initialize the object. See class documentation for more info."
225         self.message = 'Parameter not found: "%s"' % paramname
226
227 class Host(Sequence):
228     def __init__(self, name, ip):
229         self.name = name
230         self.ip = ip
231
232     def as_tuple(self):
233         return (self.name, self.ip)
234
235 class HostHandler:
236     def __init__(self,zones):
237         self.zones = zones
238
239     @handler
240     def add(self, name, hostname, ip):
241         if not name in self.zones:
242             raise ZoneNotFoundError(name)
243         if hostname in self.zones[name].hosts:
244             raise HostAlreadyExistsError(hostname)
245         self.zones[name].hosts[hostname] = Host(hostname, ip)
246         self.zones[name].mod = True
247
248     @handler
249     def update(self, name, hostname, ip):
250         if not name in self.zones:
251             raise ZoneNotFoundError(name)
252         if not hostname in self.zones[name].hosts:
253              raise HostNotFoundError(name)
254         self.zones[name].hosts[hostname].ip = ip
255         self.zones[name].mod = True
256
257     @handler
258     def delete(self, name, hostname):
259         if not name in self.zones:
260             raise ZoneNotFoundError(name)
261         if not hostname in self.zones[name].hosts:
262              raise HostNotFoundError(name)
263         del self.zones[name].hosts[hostname]
264         self.zones[name].mod = True
265
266     @handler
267     def list(self):
268         return self.zones.keys()
269
270     @handler
271     def show(self):
272         return self.zones.values()
273
274
275 class MailExchange(Sequence):
276
277     def __init__(self, mx, prio):
278         self.mx = mx
279         self.prio = prio
280
281     def as_tuple(self):
282         return (self.mx, self.prio)
283
284 class MailExchangeHandler:
285
286     def __init__(self, zones):
287         self.zones = zones
288
289     @handler
290     def add(self, zonename, mx, prio):
291         if not zonename in self.zones:
292             raise ZoneNotFoundError(zonename)
293         if mx in self.zones[zonename].mxs:
294             raise MailExchangeAlreadyExistsError(mx)
295         self.zones[zonename].mxs[mx] = MailExchange(mx, prio)
296         self.zones[zonename].mod = True
297
298     @handler
299     def update(self, zonename, mx, prio):
300         if not zonename in self.zones:
301             raise ZoneNotFoundError(zonename)
302         if not mx in self.zones[zonename].mxs:
303             raise MailExchangeNotFoundError(mx)
304         self.zones[zonename].mxs[mx].prio = prio
305         self.zones[zonename].mod = True
306
307     @handler
308     def delete(self, zonename, mx):
309         if not zonename in self.zones:
310             raise ZoneNotFoundError(zonename)
311         if not mx in self.zones[zonename].mxs:
312             raise MailExchangeNotFoundError(mx)
313         del self.zones[zonename].mxs[mx]
314         self.zones[zonename].mod = True
315
316     @handler
317     def list(self):
318         return self.zones.keys()
319
320     @handler
321     def show(self):
322         return self.zones.values()
323
324
325 class NameServer(Sequence):
326
327     def __init__(self, name):
328         self.name = name
329
330     def as_tuple(self):
331         return (self.name)
332
333 class NameServerHandler:
334
335     def __init__(self, zones):
336         self.zones = zones
337
338     @handler
339     def add(self, zone, ns):
340         if not zone in self.zones:
341             raise ZoneNotFoundError(zone)
342         if ns in self.zones[zone].nss:
343             raise NameServerAlreadyExistsError(ns)
344         self.zones[zone].nss[ns] = NameServer(ns)
345         self.zones[zone].mod = True
346
347     @handler
348     def delete(self, zone, ns):
349         if not zone in self.zones:
350             raise ZoneNotFoundError(zone)
351         if not ns in self.zones[zone].nss:
352             raise NameServerNotFoundError(ns)
353         del self.zones[zone].nss[ns]
354         self.zones[zone].mod = True
355
356     @handler
357     def list(self):
358         return self.zones.keys()
359
360     @handler
361     def show(self):
362         return self.zones.values()
363
364
365 class Zone(Sequence):
366     def __init__(self, name):
367         self.name = name
368         self.hosts = dict()
369         self.mxs = dict()
370         self.nss = dict()
371         self.new = False
372         self.mod = False
373         self.dele = False
374
375     def as_tuple(self):
376         return (self.name, self.hosts, self.mxs, self.nss)
377
378 class ZoneHandler:
379
380     r"""ZoneHandler(zones) -> ZoneHandler instance :: Handle a list of zones.
381
382     This class is a helper for DnsHandler to do all the work related to zone
383     administration.
384
385     zones - A dictionary with string keys (zone name) and Zone instances values.
386     """
387     def __init__(self, zones):
388         self.zones = zones
389
390     @handler
391     def add(self, name):
392         if name in self.zones:
393             raise ZoneAlreadyExistsError(name)
394         self.zones[name] = Zone(name)
395         self.zones[name].mod = True
396         self.zones[name].new = True
397
398
399     @handler
400     def delete(self, name):
401         r"delete(name) -> None :: Delete a zone from the zone list."
402         if not name in self.zones:
403             raise ZoneNotFoundError(name)
404         self.zones[name].dele = True
405
406     @handler
407     def list(self):
408         return self.zones.keys()
409
410     @handler
411     def show(self):
412         return self.zones.values()
413
414 class DnsHandler:
415     r"""DnsHandler([pickle_dir[, config_dir]]) -> DnsHandler instance.
416
417     Handles DNS service commands for the dns program.
418
419     pickle_dir - Directory where to write the persistent configuration data.
420
421     config_dir - Directory where to store de generated configuration files.
422
423     Both defaults to the current working directory.
424     """
425
426     def __init__(self, pickle_dir='.', config_dir='.'):
427         r"Initialize DnsHandler object, see class documentation for details."
428         self.pickle_dir = pickle_dir
429         self.config_dir = config_dir
430         c_filename = path.join(template_dir, config_filename)
431         z_filename = path.join(template_dir, zone_filename)
432         self.config_template = Template(filename=c_filename)
433         self.zone_template = Template(filename=z_filename)
434         try :
435             self._load()
436         except IOError:
437             self.zones = dict()
438             self.vars = dict(
439                 isp_dns1 = '',
440                 isp_dns2 = '',
441                 bind_addr1 = '',
442                 bind_addr2 = ''
443             )
444
445         self.host = HostHandler(self.zones)
446         self.zone = ZoneHandler(self.zones)
447         self.mx = MailExchangeHandler(self.zones)
448         self.ns = NameServerHandler(self.zones)
449         self.mod = False
450
451     @handler
452     def set(self, param, value):
453         r"set(param, value) -> None :: Set a DNS parameter."
454         if not param in self.vars:
455             raise ParameterNotFoundError(param)
456         self.vars[param] = value
457         self.mod = True
458
459     @handler
460     def get(self, param):
461         r"get(param) -> None :: Get a DNS parameter."
462         if not param in self.vars:
463             raise ParameterNotFoundError(param)
464         return self.vars[param]
465
466     @handler
467     def list(self):
468         return self.vars.keys()
469
470     @handler
471     def show(self):
472         return self.vars.values()
473
474     @handler
475     def start(self):
476         r"start() -> None :: Start the DNS service."
477         #esto seria para poner en una interfaz
478         #y seria el hook para arrancar el servicio
479         pass
480
481     @handler
482     def stop(self):
483         r"stop() -> None :: Stop the DNS service."
484         #esto seria para poner en una interfaz
485         #y seria el hook para arrancar el servicio
486         pass
487
488     @handler
489     def restart(self):
490         r"restart() -> None :: Restart the DNS service."
491         #esto seria para poner en una interfaz
492         #y seria el hook para arrancar el servicio
493         pass
494
495     @handler
496     def reload(self):
497         r"reload() -> None :: Reload the configuration of the DNS service."
498         #esto seria para poner en una interfaz
499         #y seria el hook para arrancar el servicio
500         pass
501
502     @handler
503     def commit(self):
504         r"commit() -> None :: Commit the changes and reload the DNS service."
505         #esto seria para poner en una interfaz
506         #y seria que hace el pickle deberia llamarse
507         #al hacerse un commit
508         self._dump()
509         self._write_config()
510         self.reload()
511
512     @handler
513     def rollback(self):
514         r"rollback() -> None :: Discard the changes not yet commited."
515         self._load()
516
517     def _dump(self):
518         r"_dump() -> None :: Dump all persistent data to pickle files."
519         # XXX podría ir en una clase base
520         self._dump_var(self.vars, pickle_vars)
521         self._dump_var(self.zones, pickle_zones)
522
523     def _load(self):
524         r"_load() -> None :: Load all persistent data from pickle files."
525         # XXX podría ir en una clase base
526         self.vars = self._load_var(pickle_vars)
527         self.zones = self._load_var(pickle_zones)
528
529     def _pickle_filename(self, name):
530         r"_pickle_filename() -> string :: Construct a pickle filename."
531         # XXX podría ir en una clase base
532         return path.join(self.pickle_dir, name) + pickle_ext
533
534     def _dump_var(self, var, name):
535         r"_dump_var() -> None :: Dump a especific variable to a pickle file."
536         # XXX podría ir en una clase base
537         pkl_file = file(self._pickle_filename(name), 'wb')
538         pickle.dump(var, pkl_file, 2)
539         pkl_file.close()
540
541     def _load_var(self, name):
542         r"_load_var() -> object :: Load a especific pickle file."
543         # XXX podría ir en una clase base
544         return pickle.load(file(self._pickle_filename(name)))
545
546     def _write_config(self):
547         r"_write_config() -> None :: Generate all the configuration files."
548         # XXX podría ir en una clase base, ver como generalizar variables a
549         # reemplazar en la template
550         #archivos de zona
551         delete_zones = list()
552         for a_zone in self.zones.values():
553             if a_zone.mod:
554                 if not a_zone.new:
555                     # TODO freeze de la zona
556                     print 'Freezing zone ' + a_zone.name + zone_filename_ext
557                 zone_out_file = file(path.join(self.config_dir, a_zone.name + zone_filename_ext), 'w')
558                 ctx = Context(
559                     zone_out_file,
560                     zone = a_zone,
561                     hosts = a_zone.hosts.values(),
562                     mxs = a_zone.mxs.values(),
563                     nss = a_zone.nss.values()
564                     )
565                 self.zone_template.render_context(ctx)
566                 zone_out_file.close()
567                 a_zone.mod = False
568                 if not a_zone.new:
569                     # TODO unfreeze de la zona
570                     print 'Unfreezing zone ' + a_zone.name + zone_filename_ext
571                 else :
572                     self.mod = True
573                     a_zone.new = False
574             if a_zone.dele:
575                 #borro el archivo .zone
576                 try:
577                     self.mod = True
578                     unlink(path.join(self.config_dir, a_zone.name + zone_filename_ext))
579                 except OSError:
580                     #la excepcion pude darse en caso que haga un add de una zona y
581                     #luego el del, como no hice commit, no se crea el archivo
582                     pass
583                 delete_zones.append(a_zone.name)
584         #borro las zonas
585         for z in delete_zones:
586             del self.zones[z]
587         #archivo general
588         if self.mod :
589             cfg_out_file = file(path.join(self.config_dir, config_filename), 'w')
590             ctx = Context(cfg_out_file, zones=self.zones.values(), **self.vars)
591             self.config_template.render_context(ctx)
592             cfg_out_file.close()
593             self.mod = False
594             print 'Restarting service'
595
596
597
598 if __name__ == '__main__':
599
600     dns = DnsHandler();
601
602     dns.set('isp_dns1','la_garcha.com')
603     dns.set('bind_addr1','localhost')
604     dns.zone.add('zona_loca.com')
605     #dns.zone.update('zona_loca.com','ns1.dominio.com')
606
607     dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
608     dns.host.update('zona_loca.com','hostname_loco','192.168.0.66')
609
610     dns.host.add('zona_loca.com','hostname_kuak','192.168.0.23')
611     dns.host.delete('zona_loca.com','hostname_kuak')
612
613     dns.host.add('zona_loca.com','hostname_kuang','192.168.0.24')
614     dns.host.add('zona_loca.com','hostname_chan','192.168.0.25')
615     dns.host.add('zona_loca.com','hostname_kaine','192.168.0.26')
616
617     dns.mx.add('zona_loca.com','mx1.sarasa.com',10)
618     dns.mx.update('zona_loca.com','mx1.sarasa.com',20)
619     dns.mx.add('zona_loca.com','mx2.sarasa.com',30)
620     dns.mx.add('zona_loca.com','mx3.sarasa.com',40)
621     dns.mx.delete('zona_loca.com','mx3.sarasa.com')
622
623     dns.ns.add('zona_loca.com','ns1.jua.com')
624     dns.ns.add('zona_loca.com','ns2.jua.com')
625     dns.ns.add('zona_loca.com','ns3.jua.com')
626     dns.ns.delete('zona_loca.com','ns3.jua.com')
627
628     dns.zone.add('zona_oscura')
629
630     dns.host.add('zona_oscura','hostname_a','192.168.0.24')
631     dns.host.add('zona_oscura','hostname_b','192.168.0.25')
632     dns.host.add('zona_oscura','hostname_c','192.168.0.26')
633
634     dns.zone.delete('zona_oscura')
635
636     dns.commit()
637
638     print 'ZONAS :'
639     print dns.zone.show() + '\n'
640     print 'HOSTS :'
641     print dns.host.show()
642
643     #test zone errors
644     try:
645         dns.zone.update('zone-sarasa','lalal')
646     except ZoneNotFoundError, inst:
647         print 'Error: ', inst
648
649     try:
650         dns.zone.delete('zone-sarasa')
651     except ZoneNotFoundError, inst:
652         print 'Error: ', inst
653
654     try:
655         dns.zone.add('zona_loca.com','ns1.dom.com','ns2.dom.com')
656     except ZoneAlreadyExistsError, inst:
657         print 'Error: ', inst
658
659     #test hosts errors
660     try:
661         dns.host.update('zone-sarasa','kuak','192.68')
662     except ZoneNotFoundError, inst:
663         print 'Error: ', inst
664
665     try:
666         dns.host.update('zona_loca.com','kuak','192.68')
667     except HostNotFoundError, inst:
668         print 'Error: ', inst
669
670     try:
671         dns.host.delete('zone-sarasa','lala')
672     except ZoneNotFoundError, inst:
673         print 'Error: ', inst
674
675     try:
676         dns.host.delete('zona_loca.com','lala')
677     except HostNotFoundError, inst:
678         print 'Error: ', inst
679
680     try:
681         dns.host.add('zona','hostname_loco','192.168.0.23')
682     except ZoneNotFoundError, inst:
683         print 'Error: ', inst
684
685     try:
686         dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
687     except HostAlreadyExistsError, inst:
688         print 'Error: ', inst