1 # vim: set encoding=utf-8 et sw=4 sts=4 :
3 from mako.template import Template
4 from mako.runtime import Context
7 import cPickle as pickle
12 from seqtools import Sequence
17 from dispatcher import Handler, handler, HandlerError
20 class HandlerError(RuntimeError): pass
27 __ALL__ = ('DhcpHandler', 'Error', 'HostError', 'HostAlreadyExistsError',
28 'HostNotFoundError', 'ParameterError', 'ParameterNotFoundError')
32 pickle_hosts = 'hosts'
34 config_filename = 'dhcpd.conf'
36 template_dir = path.join(path.dirname(__file__), 'templates')
38 class Error(HandlerError):
40 Error(message) -> Error instance :: Base DhcpHandler exception class.
42 All exceptions raised by the DhcpHandler inherits from this one, so you can
43 easily catch any DhcpHandler exception.
45 message - A descriptive error message.
48 def __init__(self, message):
49 r"Initialize the Error object. See class documentation for more info."
50 self.message = message
55 class HostError(Error, KeyError):
57 HostError(hostname) -> HostError instance
59 This is the base exception for all host related errors.
62 def __init__(self, hostname):
63 r"Initialize the object. See class documentation for more info."
64 self.message = 'Host error: "%s"' % hostname
66 class HostAlreadyExistsError(HostError):
68 HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
70 This exception is raised when trying to add a hostname that already exists.
73 def __init__(self, hostname):
74 r"Initialize the object. See class documentation for more info."
75 self.message = 'Host already exists: "%s"' % hostname
77 class HostNotFoundError(HostError):
79 HostNotFoundError(hostname) -> HostNotFoundError instance
81 This exception is raised when trying to operate on a hostname that doesn't
85 def __init__(self, hostname):
86 r"Initialize the object. See class documentation for more info."
87 self.message = 'Host not found: "%s"' % hostname
89 class ParameterError(Error, KeyError):
91 ParameterError(paramname) -> ParameterError instance
93 This is the base exception for all DhcpHandler parameters related errors.
96 def __init__(self, paramname):
97 r"Initialize the object. See class documentation for more info."
98 self.message = 'Parameter error: "%s"' % paramname
100 class ParameterNotFoundError(ParameterError):
102 ParameterNotFoundError(hostname) -> ParameterNotFoundError instance
104 This exception is raised when trying to operate on a parameter that doesn't
108 def __init__(self, paramname):
109 r"Initialize the object. See class documentation for more info."
110 self.message = 'Parameter not found: "%s"' % paramname
113 class Host(Sequence):
114 r"""Host(name, ip, mac) -> Host instance :: Class representing a host.
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.
121 def __init__(self, name, ip, mac):
122 r"Initialize Host object, see class documentation for details."
128 r"Return a tuple representing the host."
129 return (self.name, self.ip, self.mac)
131 class HostHandler(Handler):
132 r"""HostHandler(hosts) -> HostHandler instance :: Handle a list of hosts.
134 This class is a helper for DhcpHandler to do all the work related to hosts
137 hosts - A dictionary with string keys (hostnames) and Host instances values.
140 def __init__(self, hosts):
141 r"Initialize HostHandler object, see class documentation for details."
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)
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)
157 self.hosts[name].ip = ip
159 self.hosts[name].mac = mac
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)
168 @handler(u'Get information about a host.')
170 r"""get(name) -> CSV string :: List all the information of a host.
172 The host is returned as a CSV list of: hostname,ip,mac
174 if not name in self.hosts:
175 raise HostNotFoundError(name)
176 return self.hosts[name]
178 @handler(u'List hosts.')
180 r"""list() -> CSV string :: List all the hostnames.
182 The list is returned as a single CSV line with all the hostnames.
184 return self.hosts.keys()
186 @handler(u'Get information about all hosts.')
188 r"""show() -> CSV string :: List all the complete hosts information.
190 The hosts are returned as a CSV list with each host in a line, like:
193 return self.hosts.values()
195 class DhcpHandler(Handler):
196 r"""DhcpHandler([pickle_dir[, config_dir]]) -> DhcpHandler instance.
198 Handles DHCP service commands for the dhcpd program.
200 pickle_dir - Directory where to write the persistent configuration data.
202 config_dir - Directory where to store de generated configuration files.
204 Both defaults to the current working directory.
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)
216 # This is the first time the handler is used, create a basic
217 # setup using some nice defaults
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',
231 self.host = HostHandler(self.hosts)
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
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]
247 @handler(u'List all available DHCP parameters.')
249 r"""list() -> CSV string :: List all the parameter names.
251 The list is returned as a single CSV line with all the names.
253 return self.vars.keys()
255 @handler(u'Get all DHCP parameters, with their values.')
257 r"""show() -> CSV string :: List all the parameters (with their values).
259 The parameters are returned as a CSV list with each parameter in a
263 return self.vars.items()
265 @handler(u'Start the service.')
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
272 @handler(u'Stop the service.')
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
279 @handler(u'Restart the service.')
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
286 @handler(u'Reload the service config (without restarting, if possible).')
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
293 @handler(u'Commit the changes (reloading the service, if necessary).')
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
303 @handler(u'Discard all the uncommited changes.')
305 r"rollback() -> None :: Discard the changes not yet commited."
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)
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)
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
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)
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)))
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)
346 if __name__ == '__main__':
350 dhcp_handler = DhcpHandler()
354 print 'Variables:', dhcp_handler.list()
355 print dhcp_handler.show()
357 print 'Hosts:', dhcp_handler.host.list()
358 print dhcp_handler.host.show()
363 dhcp_handler.host.add('my_name','192.168.0.102','00:12:ff:56')
365 dhcp_handler.host.update('my_name','192.168.0.192','00:12:ff:56')
367 dhcp_handler.host.add('nico','192.168.0.188','00:00:00:00')
369 dhcp_handler.set('domain_name','baryon.com.ar')
372 dhcp_handler.set('sarasa','baryon.com.ar')
376 dhcp_handler.commit()
380 for f in (pickle_vars + pickle_ext, pickle_hosts + pickle_ext,