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