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