]> git.llucax.com Git - software/pymin.git/blob - services/dhcp/__init__.py
Update TODO (add logging).
[software/pymin.git] / services / dhcp / __init__.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 from mako.template import Template
4 from mako.runtime import Context
5 from os import path
6 try:
7     import cPickle as pickle
8 except ImportError:
9     import pickle
10
11 try:
12     from seqtools import Sequence
13 except ImportError:
14     # NOP for testing
15     class Sequence: pass
16 try:
17     from dispatcher import handler, HandlerError
18 except ImportError:
19     # NOP for testing
20     class HandlerError(RuntimeError): pass
21     def handler(f): return f
22
23 __ALL__ = ('DhcpHandler',)
24
25 pickle_ext = '.pkl'
26 pickle_vars = 'vars'
27 pickle_hosts = 'hosts'
28
29 config_filename = 'dhcpd.conf'
30
31 template_dir = path.join(path.dirname(__file__), 'templates')
32
33 class Error(HandlerError):
34     r"""
35     Error(command) -> Error instance :: Base DhcpHandler exception class.
36
37     All exceptions raised by the DhcpHandler inherits from this one, so you can
38     easily catch any DhcpHandler exception.
39
40     message - A descriptive error message.
41     """
42
43     def __init__(self, message):
44         r"Initialize the Error object. See class documentation for more info."
45         self.message = message
46
47     def __str__(self):
48         return self.message
49
50 class HostError(Error, KeyError):
51     r"""
52     HostError(hostname) -> HostError instance
53
54     This is the base exception for all host related errors.
55     """
56
57     def __init__(self, hostname):
58         r"Initialize the object. See class documentation for more info."
59         self.message = 'Host error: "%s"' % hostname
60
61 class HostAlreadyExistsError(HostError):
62     r"""
63     HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
64
65     This exception is raised when trying to add a hostname that already exists.
66     """
67
68     def __init__(self, hostname):
69         r"Initialize the object. See class documentation for more info."
70         self.message = 'Host already exists: "%s"' % hostname
71
72 class HostNotFoundError(HostError):
73     r"""
74     HostNotFoundError(hostname) -> HostNotFoundError instance
75
76     This exception is raised when trying to operate on a hostname that doesn't
77     exists.
78     """
79
80     def __init__(self, hostname):
81         r"Initialize the object. See class documentation for more info."
82         self.message = 'Host not found: "%s"' % hostname
83
84 class ParameterError(Error, KeyError):
85     r"""
86     ParameterError(paramname) -> ParameterError instance
87
88     This is the base exception for all DhcpHandler parameters related errors.
89     """
90
91     def __init__(self, paramname):
92         r"Initialize the object. See class documentation for more info."
93         self.message = 'Parameter error: "%s"' % paramname
94
95 class ParameterNotFoundError(ParameterError):
96     r"""
97     ParameterNotFoundError(hostname) -> ParameterNotFoundError instance
98
99     This exception is raised when trying to operate on a parameter that doesn't
100     exists.
101     """
102
103     def __init__(self, paramname):
104         r"Initialize the object. See class documentation for more info."
105         self.message = 'Parameter not found: "%s"' % paramname
106
107
108 class Host(Sequence):
109     r"""Host(name, ip, mac) -> Host instance :: Class representing a host.
110
111     name - Host name, should be a fully qualified name, but no checks are done.
112     ip - IP assigned to the hostname.
113     mac - MAC address to associate to the hostname.
114     """
115
116     def __init__(self, name, ip, mac):
117         r"Initialize Host object, see class documentation for details."
118         self.name = name
119         self.ip = ip
120         self.mac = mac
121
122     def as_tuple(self):
123         r"Return a tuple representing the host."
124         return (self.name, self.ip, self.mac)
125
126 class HostHandler:
127     r"""HostHandler(hosts) -> HostHandler instance :: Handle a list of hosts.
128
129     This class is a helper for DhcpHandler to do all the work related to hosts
130     administration.
131
132     hosts - A dictionary with string keys (hostnames) and Host instances values.
133     """
134
135     def __init__(self, hosts):
136         r"Initialize HostHandler object, see class documentation for details."
137         self.hosts = hosts
138
139     @handler
140     def add(self, name, ip, mac):
141         r"add(name, ip, mac) -> None :: Add a host to the hosts list."
142         if name in self.hosts:
143             raise HostAlreadyExistsError(name)
144         self.hosts[name] = Host(name, ip, mac)
145
146     @handler
147     def update(self, name, ip=None, mac=None):
148         r"update(name[, ip[, mac]]) -> None :: Update a host of the hosts list."
149         if not name in self.hosts:
150             raise HostNotFoundError(name)
151         if ip is not None:
152             self.hosts[name].ip = ip
153         if mac is not None:
154             self.hosts[name].mac = mac
155
156     @handler
157     def delete(self, name):
158         r"delete(name) -> None :: Delete a host of the hosts list."
159         if not name in self.hosts:
160             raise HostNotFoundError(name)
161         del self.hosts[name]
162
163     @handler
164     def get(self, name):
165         r"""get(name) -> CSV string :: List all the information of a host.
166
167         The host is returned as a CSV list of: hostname,ip,mac
168         """
169         if not name in self.hosts:
170             raise HostNotFoundError(name)
171         return self.hosts[name]
172
173     @handler
174     def list(self):
175         r"""list() -> CSV string :: List all the hostnames.
176
177         The list is returned as a single CSV line with all the hostnames.
178         """
179         return self.hosts.keys()
180
181     @handler
182     def show(self):
183         r"""show() -> CSV string :: List all the complete hosts information.
184
185         The hosts are returned as a CSV list with each host in a line, like:
186         hostname,ip,mac
187         """
188         return self.hosts.values()
189
190 class DhcpHandler:
191     r"""DhcpHandler([pickle_dir[, config_dir]]) -> DhcpHandler instance.
192
193     Handles DHCP service commands for the dhcpd program.
194
195     pickle_dir - Directory where to write the persistent configuration data.
196
197     config_dir - Directory where to store de generated configuration files.
198
199     Both defaults to the current working directory.
200     """
201
202     def __init__(self, pickle_dir='.', config_dir='.'):
203         r"Initialize DhcpHandler object, see class documentation for details."
204         self.pickle_dir = pickle_dir
205         self.config_dir = config_dir
206         filename = path.join(template_dir, config_filename)
207         self.template = Template(filename=filename)
208         try:
209             self._load()
210         except IOError:
211             # This is the first time the handler is used, create a basic
212             # setup using some nice defaults
213             self.hosts = dict()
214             self.vars = dict(
215                 domain_name = 'example.com',
216                 dns_1       = 'ns1.example.com',
217                 dns_2       = 'ns2.example.com',
218                 net_address = '192.168.0.0',
219                 net_mask    = '255.255.255.0',
220                 net_start   = '192.168.0.100',
221                 net_end     = '192.168.0.200',
222                 net_gateway = '192.168.0.1',
223             )
224             self._dump()
225             self._write_config()
226         self.host = HostHandler(self.hosts)
227
228     @handler
229     def set(self, param, value):
230         r"set(param, value) -> None :: Set a DHCP parameter."
231         if not param in self.vars:
232             raise ParameterNotFoundError(param)
233         self.vars[param] = value
234
235     @handler
236     def get(self, param):
237         r"get(param) -> None :: Get a DHCP parameter."
238         if not param in self.vars:
239             raise ParameterNotFoundError(param)
240         return self.vars[param]
241
242     @handler
243     def list(self):
244         r"""list() -> CSV string :: List all the parameter names.
245
246         The list is returned as a single CSV line with all the names.
247         """
248         return self.vars.keys()
249
250     @handler
251     def show(self):
252         r"""show() -> CSV string :: List all the parameters (with their values).
253
254         The parameters are returned as a CSV list with each parameter in a
255         line, like:
256         name,value
257         """
258         return self.vars.items()
259
260     @handler
261     def start(self):
262         r"start() -> None :: Start the DHCP service."
263         #esto seria para poner en una interfaz
264         #y seria el hook para arrancar el servicio
265         pass
266
267     @handler
268     def stop(self):
269         r"stop() -> None :: Stop the DHCP service."
270         #esto seria para poner en una interfaz
271         #y seria el hook para arrancar el servicio
272         pass
273
274     @handler
275     def restart(self):
276         r"restart() -> None :: Restart the DHCP service."
277         #esto seria para poner en una interfaz
278         #y seria el hook para arrancar el servicio
279         pass
280
281     @handler
282     def reload(self):
283         r"reload() -> None :: Reload the configuration of the DHCP service."
284         #esto seria para poner en una interfaz
285         #y seria el hook para arrancar el servicio
286         pass
287
288     @handler
289     def commit(self):
290         r"commit() -> None :: Commit the changes and reload the DHCP service."
291         #esto seria para poner en una interfaz
292         #y seria que hace el pickle deberia llamarse
293         #al hacerse un commit
294         self._dump()
295         self._write_config()
296         self.reload()
297
298     @handler
299     def rollback(self):
300         r"rollback() -> None :: Discard the changes not yet commited."
301         self._load()
302
303     def _dump(self):
304         r"_dump() -> None :: Dump all persistent data to pickle files."
305         # XXX podría ir en una clase base
306         self._dump_var(self.vars, pickle_vars)
307         self._dump_var(self.hosts, pickle_hosts)
308
309     def _load(self):
310         r"_load() -> None :: Load all persistent data from pickle files."
311         # XXX podría ir en una clase base
312         self.vars = self._load_var(pickle_vars)
313         self.hosts = self._load_var(pickle_hosts)
314
315     def _pickle_filename(self, name):
316         r"_pickle_filename() -> string :: Construct a pickle filename."
317         # XXX podría ir en una clase base
318         return path.join(self.pickle_dir, name) + pickle_ext
319
320     def _dump_var(self, var, name):
321         r"_dump_var() -> None :: Dump a especific variable to a pickle file."
322         # XXX podría ir en una clase base
323         pkl_file = file(self._pickle_filename(name), 'wb')
324         pickle.dump(var, pkl_file, 2)
325         pkl_file.close()
326
327     def _load_var(self, name):
328         r"_load_var() -> object :: Load a especific pickle file."
329         # XXX podría ir en una clase base
330         return pickle.load(file(self._pickle_filename(name)))
331
332     def _write_config(self):
333         r"_write_config() -> None :: Generate all the configuration files."
334         # XXX podría ir en una clase base, ver como generalizar variables a
335         # reemplazar en la template
336         out_file = file(path.join(self.config_dir, config_filename), 'w')
337         ctx = Context(out_file, hosts=self.hosts.values(), **self.vars)
338         self.template.render_context(ctx)
339         out_file.close()
340
341 if __name__ == '__main__':
342
343     import os
344
345     dhcp_handler = DhcpHandler()
346
347     def dump():
348         print '-' * 80
349         print 'Variables:', dhcp_handler.list()
350         print dhcp_handler.show()
351         print
352         print 'Hosts:', dhcp_handler.host.list()
353         print dhcp_handler.host.show()
354         print '-' * 80
355
356     dump()
357
358     dhcp_handler.host.add('my_name','192.168.0.102','00:12:ff:56')
359
360     dhcp_handler.host.update('my_name','192.168.0.192','00:12:ff:56')
361
362     dhcp_handler.host.add('nico','192.168.0.188','00:00:00:00')
363
364     dhcp_handler.set('domain_name','baryon.com.ar')
365
366     try:
367         dhcp_handler.set('sarasa','baryon.com.ar')
368     except KeyError, e:
369         print 'Error:', e
370
371     dhcp_handler.commit()
372
373     dump()
374
375     for f in (pickle_vars + pickle_ext, pickle_hosts + pickle_ext,
376                                                             config_filename):
377         os.unlink(f)
378