]> git.llucax.com Git - software/pymin.git/blob - services/dns/__init__.py
Add a ParametersHandler to services.util.
[software/pymin.git] / services / dns / __init__.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 # TODO COMMENT
4 from os import path
5 from os import unlink
6 from new import instancemethod
7
8 from seqtools import Sequence
9 from dispatcher import handler, HandlerError, Handler
10 from services.util import Restorable, ConfigWriter, call
11 from services.util import InitdHandler, TransactionalHandler, ParametersHandler
12
13 __ALL__ = ('DnsHandler', 'Error',
14             'ZoneError', 'ZoneNotFoundError', 'ZoneAlreadyExistsError',
15             'HostError', 'HostAlreadyExistsError', 'HostNotFoundError',
16             'MailExchangeError', 'MailExchangeAlreadyExistsError',
17             'MailExchangeNotFoundError', 'NameServerError',
18             'NameServerAlreadyExistsError', 'NameServerNotFoundError')
19
20 template_dir = path.join(path.dirname(__file__), 'templates')
21
22
23 class Error(HandlerError):
24     r"""
25     Error(command) -> Error instance :: Base DnsHandler exception class.
26
27     All exceptions raised by the DnsHandler inherits from this one, so you can
28     easily catch any DnsHandler exception.
29
30     message - A descriptive error message.
31     """
32
33     def __init__(self, message):
34         r"Initialize the Error object. See class documentation for more info."
35         self.message = message
36
37     def __str__(self):
38         return self.message
39
40 class ZoneError(Error, KeyError):
41     r"""
42     ZoneError(zonename) -> ZoneError instance
43
44     This is the base exception for all zone related errors.
45     """
46
47     def __init__(self, zonename):
48         r"Initialize the object. See class documentation for more info."
49         self.message = 'Zone error: "%s"' % zonename
50
51
52 class ZoneNotFoundError(ZoneError):
53     r"""
54     ZoneNotFoundError(hostname) -> ZoneNotFoundError instance
55
56     This exception is raised when trying to operate on a zone that doesn't
57     exists.
58     """
59
60     def __init__(self, zonename):
61         r"Initialize the object. See class documentation for more info."
62         self.message = 'zone not found: "%s"' % zonename
63
64
65 class ZoneAlreadyExistsError(ZoneError):
66     r"""
67     ZoneAlreadyExistsError(hostname) -> ZoneAlreadyExistsError instance
68
69     This exception is raised when trying to add a zonename that already exists.
70     """
71
72     def __init__(self, zonename):
73         r"Initialize the object. See class documentation for more info."
74         self.message = 'Zone already exists: "%s"' % zonename
75
76
77 class HostError(Error, KeyError):
78     r"""
79     HostError(hostname) -> HostError instance
80
81     This is the base exception for all host related errors.
82     """
83
84     def __init__(self, hostname):
85         r"Initialize the object. See class documentation for more info."
86         self.message = 'Host error: "%s"' % hostname
87
88 class HostAlreadyExistsError(HostError):
89     r"""
90     HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
91
92     This exception is raised when trying to add a hostname that already exists.
93     """
94
95     def __init__(self, hostname):
96         r"Initialize the object. See class documentation for more info."
97         self.message = 'Host already exists: "%s"' % hostname
98
99 class HostNotFoundError(HostError):
100     r"""
101     HostNotFoundError(hostname) -> HostNotFoundError instance
102
103     This exception is raised when trying to operate on a hostname that doesn't
104     exists.
105     """
106
107     def __init__(self, hostname):
108         r"Initialize the object. See class documentation for more info."
109         self.message = 'Host not found: "%s"' % hostname
110
111
112 class MailExchangeError(Error, KeyError):
113     r"""
114     MailExchangeError(hostname) -> MailExchangeError instance
115
116     This is the base exception for all mail exchange related errors.
117     """
118
119     def __init__(self, mx):
120         r"Initialize the object. See class documentation for more info."
121         self.message = 'Mail Exchange error: "%s"' % mx
122
123
124 class MailExchangeAlreadyExistsError(MailExchangeError):
125     r"""
126     MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError instance
127
128     This exception is raised when trying to add a mail exchange that already exists.
129     """
130
131     def __init__(self, mx):
132         r"Initialize the object. See class documentation for more info."
133         self.message = 'Mail Exchange already exists: "%s"' % mx
134
135
136 class MailExchangeNotFoundError(MailExchangeError):
137     r"""
138     MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance
139
140     This exception is raised when trying to operate on a mail exchange that doesn't
141     exists.
142     """
143
144     def __init__(self, mx):
145         r"Initialize the object. See class documentation for more info."
146         self.message = 'Mail Exchange not found: "%s"' % mx
147
148
149
150 class NameServerError(Error, KeyError):
151     r"""
152     NameServerError(ns) -> NameServerError instance
153
154     This is the base exception for all name server related errors.
155     """
156
157     def __init__(self, ns):
158         r"Initialize the object. See class documentation for more info."
159         self.message = 'Name Server error: "%s"' % ns
160
161 class NameServerAlreadyExistsError(NameServerError):
162     r"""
163     NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError instance
164
165     This exception is raised when trying to add a name server that already exists.
166     """
167
168     def __init__(self, ns):
169         r"Initialize the object. See class documentation for more info."
170         self.message = 'Name server already exists: "%s"' % ns
171
172 class NameServerNotFoundError(NameServerError):
173     r"""
174     NameServerNotFoundError(hostname) -> NameServerNotFoundError instance
175
176     This exception is raised when trying to operate on a name server that doesn't
177     exists.
178     """
179
180     def __init__(self, ns):
181         r"Initialize the object. See class documentation for more info."
182         self.message = 'Mail Exchange not found: "%s"' % ns
183
184
185 class ParameterError(Error, KeyError):
186     r"""
187     ParameterError(paramname) -> ParameterError instance
188
189     This is the base exception for all DhcpHandler parameters related errors.
190     """
191
192     def __init__(self, paramname):
193         r"Initialize the object. See class documentation for more info."
194         self.message = 'Parameter error: "%s"' % paramname
195
196 class ParameterNotFoundError(ParameterError):
197     r"""
198     ParameterNotFoundError(hostname) -> ParameterNotFoundError instance
199
200     This exception is raised when trying to operate on a parameter that doesn't
201     exists.
202     """
203
204     def __init__(self, paramname):
205         r"Initialize the object. See class documentation for more info."
206         self.message = 'Parameter not found: "%s"' % paramname
207
208 class Host(Sequence):
209     def __init__(self, name, ip):
210         self.name = name
211         self.ip = ip
212
213     def as_tuple(self):
214         return (self.name, self.ip)
215
216 class HostHandler(Handler):
217     def __init__(self,zones):
218         self.zones = zones
219
220     @handler(u'Adds a host to a zone')
221     def add(self, name, hostname, ip):
222         if not name in self.zones:
223             raise ZoneNotFoundError(name)
224         if hostname in self.zones[name].hosts:
225             raise HostAlreadyExistsError(hostname)
226         self.zones[name].hosts[hostname] = Host(hostname, ip)
227         self.zones[name].mod = True
228
229     @handler(u'Updates a host ip in a zone')
230     def update(self, name, hostname, ip):
231         if not name in self.zones:
232             raise ZoneNotFoundError(name)
233         if not hostname in self.zones[name].hosts:
234              raise HostNotFoundError(name)
235         self.zones[name].hosts[hostname].ip = ip
236         self.zones[name].mod = True
237
238     @handler(u'Deletes a host from a zone')
239     def delete(self, name, hostname):
240         if not name in self.zones:
241             raise ZoneNotFoundError(name)
242         if not hostname in self.zones[name].hosts:
243              raise HostNotFoundError(name)
244         del self.zones[name].hosts[hostname]
245         self.zones[name].mod = True
246
247     @handler(u'Lists hosts')
248     def list(self):
249         return self.zones.keys()
250
251     @handler(u'Get insormation about all hosts')
252     def show(self):
253         return self.zones.values()
254
255
256 class MailExchange(Sequence):
257
258     def __init__(self, mx, prio):
259         self.mx = mx
260         self.prio = prio
261
262     def as_tuple(self):
263         return (self.mx, self.prio)
264
265 class MailExchangeHandler(Handler):
266
267     def __init__(self, zones):
268         self.zones = zones
269
270     @handler(u'Adds a mail exchange to a zone')
271     def add(self, zonename, mx, prio):
272         if not zonename in self.zones:
273             raise ZoneNotFoundError(zonename)
274         if mx in self.zones[zonename].mxs:
275             raise MailExchangeAlreadyExistsError(mx)
276         self.zones[zonename].mxs[mx] = MailExchange(mx, prio)
277         self.zones[zonename].mod = True
278
279     @handler(u'Updates a mail exchange priority')
280     def update(self, zonename, mx, prio):
281         if not zonename in self.zones:
282             raise ZoneNotFoundError(zonename)
283         if not mx in self.zones[zonename].mxs:
284             raise MailExchangeNotFoundError(mx)
285         self.zones[zonename].mxs[mx].prio = prio
286         self.zones[zonename].mod = True
287
288     @handler(u'Deletes a mail exchange from a zone')
289     def delete(self, zonename, mx):
290         if not zonename in self.zones:
291             raise ZoneNotFoundError(zonename)
292         if not mx in self.zones[zonename].mxs:
293             raise MailExchangeNotFoundError(mx)
294         del self.zones[zonename].mxs[mx]
295         self.zones[zonename].mod = True
296
297     @handler(u'Lists mail exchangers')
298     def list(self):
299         return self.zones.keys()
300
301     @handler(u'Get information about all mail exchangers')
302     def show(self):
303         return self.zones.values()
304
305
306 class NameServer(Sequence):
307
308     def __init__(self, name):
309         self.name = name
310
311     def as_tuple(self):
312         return (self.name)
313
314 class NameServerHandler(Handler):
315
316     def __init__(self, zones):
317         self.zones = zones
318
319     @handler(u'Adds a name server to a zone')
320     def add(self, zone, ns):
321         if not zone in self.zones:
322             raise ZoneNotFoundError(zone)
323         if ns in self.zones[zone].nss:
324             raise NameServerAlreadyExistsError(ns)
325         self.zones[zone].nss[ns] = NameServer(ns)
326         self.zones[zone].mod = True
327
328     @handler(u'Deletes a name server from a zone')
329     def delete(self, zone, ns):
330         if not zone in self.zones:
331             raise ZoneNotFoundError(zone)
332         if not ns in self.zones[zone].nss:
333             raise NameServerNotFoundError(ns)
334         del self.zones[zone].nss[ns]
335         self.zones[zone].mod = True
336
337     @handler(u'Lists name servers')
338     def list(self):
339         return self.zones.keys()
340
341     @handler(u'Get information about all name servers')
342     def show(self):
343         return self.zones.values()
344
345
346 class Zone(Sequence):
347     def __init__(self, name):
348         self.name = name
349         self.hosts = dict()
350         self.mxs = dict()
351         self.nss = dict()
352         self.new = False
353         self.mod = False
354         self.dele = False
355
356     def as_tuple(self):
357         return (self.name, self.hosts, self.mxs, self.nss)
358
359 class ZoneHandler(Handler):
360
361     r"""ZoneHandler(zones) -> ZoneHandler instance :: Handle a list of zones.
362
363     This class is a helper for DnsHandler to do all the work related to zone
364     administration.
365
366     zones - A dictionary with string keys (zone name) and Zone instances values.
367     """
368     def __init__(self, zones):
369         self.zones = zones
370
371     @handler(u'Adds a zone')
372     def add(self, name):
373         if name in self.zones:
374             if self.zones[name].dele == True:
375                 self.zones[name].dele = False
376             else:
377                 raise ZoneAlreadyExistsError(name)
378         self.zones[name] = Zone(name)
379         self.zones[name].mod = True
380         self.zones[name].new = True
381
382
383     @handler(u'Deletes a zone')
384     def delete(self, name):
385         r"delete(name) -> None :: Delete a zone from the zone list."
386         if not name in self.zones:
387             raise ZoneNotFoundError(name)
388         self.zones[name].dele = True
389
390     @handler(u'Lists zones')
391     def list(self):
392         return self.zones.keys()
393
394     @handler(u'Get information about all zones')
395     def show(self):
396         return self.zones.values()
397
398 class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
399                  ParametersHandler):
400     r"""DnsHandler([pickle_dir[, config_dir]]) -> DnsHandler instance.
401
402     Handles DNS service commands for the dns program.
403
404     pickle_dir - Directory where to write the persistent configuration data.
405
406     config_dir - Directory where to store de generated configuration files.
407
408     Both defaults to the current working directory.
409     """
410
411     _initd_name = 'bind'
412
413     _persistent_vars = ('params', 'zones')
414
415     _restorable_defaults = dict(
416             zones = dict(),
417             params  = dict(
418                 isp_dns1 = '',
419                 isp_dns2 = '',
420                 bind_addr1 = '',
421                 bind_addr2 = ''
422             ),
423     )
424
425     _config_writer_files = ('named.conf', 'zoneX.zone')
426     _config_writer_tpl_dir = path.join(path.dirname(__file__), 'templates')
427
428     def __init__(self, pickle_dir='.', config_dir='.'):
429         r"Initialize DnsHandler object, see class documentation for details."
430         self._persistent_dir = pickle_dir
431         self._config_writer_cfg_dir = config_dir
432         self.mod = False
433         self._config_build_templates()
434         self._restore()
435         self.host = HostHandler(self.zones)
436         self.zone = ZoneHandler(self.zones)
437         self.mx = MailExchangeHandler(self.zones)
438         self.ns = NameServerHandler(self.zones)
439
440     def _zone_filename(self, zone):
441         return zone.name + '.zone'
442
443     def _get_config_vars(self, config_file):
444         return dict(zones=self.zones.values(), **self.params)
445
446     def _write_config(self):
447         r"_write_config() -> None :: Generate all the configuration files."
448         delete_zones = list()
449         for a_zone in self.zones.values():
450             if a_zone.mod:
451                 if not a_zone.new:
452                     # TODO freeze de la zona
453                     call(('dns', 'freeze', a_zone.name))
454                 vars = dict(
455                     zone = a_zone,
456                     hosts = a_zone.hosts.values(),
457                     mxs = a_zone.mxs.values(),
458                     nss = a_zone.nss.values()
459                 )
460                 self._write_single_config('zoneX.zone',
461                                             self._zone_filename(a_zone), vars)
462                 a_zone.mod = False
463                 if not a_zone.new:
464                     # TODO unfreeze de la zona
465                     call(('dns', 'unfreeze', a_zone.name))
466                 else :
467                     self.mod = True
468                     a_zone.new = False
469             if a_zone.dele:
470                 #borro el archivo .zone
471                 try:
472                     self.mod = True
473                     unlink(self._zone_filename(a_zone))
474                 except OSError:
475                     #la excepcion pude darse en caso que haga un add de una zona y
476                     #luego el del, como no hice commit, no se crea el archivo
477                     pass
478                 delete_zones.append(a_zone.name)
479         #borro las zonas
480         for z in delete_zones:
481             del self.zones[z]
482         #archivo general
483         if self.mod:
484             self._write_single_config('named.conf')
485             self.mod = False
486             self.reload()
487
488 if __name__ == '__main__':
489
490     dns = DnsHandler();
491
492     dns.set('isp_dns1','la_garcha.com')
493     dns.set('bind_addr1','localhost')
494     dns.zone.add('zona_loca.com')
495     #dns.zone.update('zona_loca.com','ns1.dominio.com')
496
497     dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
498     dns.host.update('zona_loca.com','hostname_loco','192.168.0.66')
499
500     dns.host.add('zona_loca.com','hostname_kuak','192.168.0.23')
501     dns.host.delete('zona_loca.com','hostname_kuak')
502
503     dns.host.add('zona_loca.com','hostname_kuang','192.168.0.24')
504     dns.host.add('zona_loca.com','hostname_chan','192.168.0.25')
505     dns.host.add('zona_loca.com','hostname_kaine','192.168.0.26')
506
507     dns.mx.add('zona_loca.com','mx1.sarasa.com',10)
508     dns.mx.update('zona_loca.com','mx1.sarasa.com',20)
509     dns.mx.add('zona_loca.com','mx2.sarasa.com',30)
510     dns.mx.add('zona_loca.com','mx3.sarasa.com',40)
511     dns.mx.delete('zona_loca.com','mx3.sarasa.com')
512
513     dns.ns.add('zona_loca.com','ns1.jua.com')
514     dns.ns.add('zona_loca.com','ns2.jua.com')
515     dns.ns.add('zona_loca.com','ns3.jua.com')
516     dns.ns.delete('zona_loca.com','ns3.jua.com')
517
518     dns.zone.add('zona_oscura')
519
520     dns.host.add('zona_oscura','hostname_a','192.168.0.24')
521     dns.host.add('zona_oscura','hostname_b','192.168.0.25')
522     dns.host.add('zona_oscura','hostname_c','192.168.0.26')
523
524     dns.zone.delete('zona_oscura')
525
526     dns.commit()
527
528     print 'ZONAS :', dns.zone.show()
529     print 'HOSTS :', dns.host.show()
530
531     #test zone errors
532     #try:
533     #    dns.zone.update('zone-sarasa','lalal')
534     #except ZoneNotFoundError, inst:
535     #    print 'Error: ', inst
536
537     try:
538         dns.zone.delete('zone-sarasa')
539     except ZoneNotFoundError, inst:
540         print 'Error: ', inst
541
542     #try:
543     #    dns.zone.add('zona_loca.com','ns1.dom.com','ns2.dom.com')
544     #except ZoneAlreadyExistsError, inst:
545     #    print 'Error: ', inst
546
547     #test hosts errors
548     try:
549         dns.host.update('zone-sarasa','kuak','192.68')
550     except ZoneNotFoundError, inst:
551         print 'Error: ', inst
552
553     try:
554         dns.host.update('zona_loca.com','kuak','192.68')
555     except HostNotFoundError, inst:
556         print 'Error: ', inst
557
558     try:
559         dns.host.delete('zone-sarasa','lala')
560     except ZoneNotFoundError, inst:
561         print 'Error: ', inst
562
563     try:
564         dns.host.delete('zona_loca.com','lala')
565     except HostNotFoundError, inst:
566         print 'Error: ', inst
567
568     try:
569         dns.host.add('zona','hostname_loco','192.168.0.23')
570     except ZoneNotFoundError, inst:
571         print 'Error: ', inst
572
573     try:
574         dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
575     except HostAlreadyExistsError, inst:
576         print 'Error: ', inst