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