1 # vim: set encoding=utf-8 et sw=4 sts=4 :
3 from mako.template import Template
4 from mako.runtime import Context
5 from subprocess import Popen, PIPE
8 import cPickle as pickle
13 from seqtools import Sequence
18 from dispatcher import handler, HandlerError, Handler
21 class HandlerError(RuntimeError): pass
28 __ALL__ = ('IpHandler','Error','DeviceError','DeviceNotFoundError','RouteError','RouteNotFoundError',
29 'RouteAlreadyExistsError','AddressError','AddressNotFoundError','AddressAlreadyExistsError')
32 pickle_devices = 'devs'
34 template_dir = path.join(path.dirname(__file__), 'templates')
35 command_filename = 'command'
38 device_com = 'device.command'
39 ip_add_com = 'ip_add.command'
40 ip_del_com = 'ip_del.command'
41 ip_flush_com = 'ip_flush.command'
42 route_add_com = 'route_add.command'
43 route_del_com = 'route_del.command'
44 route_flush_com = 'route_flush.command'
46 class Error(HandlerError):
48 Error(command) -> Error instance :: Base IpHandler exception class.
50 All exceptions raised by the IpHandler inherits from this one, so you can
51 easily catch any IpHandler exception.
53 message - A descriptive error message.
56 def __init__(self, message):
57 r"Initialize the Error object. See class documentation for more info."
58 self.message = message
63 class DeviceError(Error):
65 def __init__(self, device):
66 self.message = 'Device error : "%s"' % device
68 class DeviceNotFoundError(DeviceError):
70 def __init__(self, device):
71 self.message = 'Device not found : "%s"' % device
73 class AddressError(Error):
75 def __init__(self, addr):
76 self.message = 'Address error : "%s"' % addr
78 class AddressNotFoundError(AddressError):
80 def __init__(self, address):
81 self.message = 'Address not found : "%s"' % address
83 class AddressAlreadyExistsError(AddressError):
85 def __init__(self, address):
86 self.message = 'Address already exists : "%s"' % address
88 class RouteError(Error):
90 def __init__(self, route):
91 self.message = 'Route error : "%s"' % route
93 class RouteNotFoundError(RouteError):
95 def __init__(self, route):
96 self.message = 'Route not found : "%s"' % route
98 class RouteAlreadyExistsError(RouteError):
100 def __init__(self, route):
101 self.message = 'Route already exists : "%s"' % route
103 class Route(Sequence):
105 def __init__(self, net_addr, prefix, gateway):
106 self.net_addr = net_addr
108 self.gateway = gateway
111 return(self.addr, self.prefix, self.gateway)
113 def __cmp__(self, other):
114 if self.net_addr == other.net_addr \
115 and self.prefix == other.prefix \
116 and self.gateway == other.gateway:
118 return cmp(id(self), id(other))
120 class RouteHandler(Handler):
122 def __init__(self, devices):
123 self.devices = devices
125 @handler(u'Adds a route to a device')
126 def add(self, device, net_addr, prefix, gateway):
127 if not device in self.devices:
128 raise DeviceNotFoundError(device)
129 r = Route(net_addr, prefix, gateway)
131 self.devices[device].routes.index(r)
132 raise RouteAlreadyExistsError(net_addr + '/' + prefix + '->' + gateway)
134 self.devices[device].routes.append(r)
136 @handler(u'Deletes a route from a device')
137 def delete(self, device, net_addr, prefix, gateway):
138 if not device in self.devices:
139 raise DeviceNotFoundError(device)
140 r = Route(net_addr, prefix, gateway)
142 self.devices[device].routes.remove(r)
144 raise RouteNotFoundError(net_addr + '/' + prefix + '->' + gateway)
146 @handler(u'Flushes routes from a device')
147 def flush(self, device):
148 if not device in self.devices:
149 raise DeviceNotFoundError(device)
150 self.devices[device].routes = list()
153 @handler(u'List routes')
154 def list(self, device):
156 k = self.devices[device].routes.keys()
161 @handler(u'Get information about all routes')
164 k = self.devices[device].routes.values()
169 class Address(Sequence):
171 def __init__(self, ip, prefix, broadcast):
174 self.broadcast = broadcast
177 return (self.ip, self.prefix, self.broadcast)
179 class AddressHandler(Handler):
181 def __init__(self, devices):
182 self.devices = devices
184 @handler(u'Adds an address to a device')
185 def add(self, device, ip, prefix, broadcast='+'):
186 if not device in self.devices:
187 raise DeviceNotFoundError(device)
188 if ip in self.devices[device].addrs:
189 raise AddressAlreadyExistsError(ip)
190 self.devices[device].addrs[ip] = Address(ip, prefix, broadcast)
192 @handler(u'Deletes an address from a device')
193 def delete(self, device, ip):
194 if not device in self.devices:
195 raise DeviceNotFoundError(device)
196 if not ip in self.devices[device].addrs:
197 raise AddressNotFoundError(ip)
198 del self.devices[device].addrs[ip]
200 @handler(u'Flushes addresses from a device')
201 def flush(self, device):
202 if not device in self.devices:
203 raise DeviceNotFoundError(device)
204 self.devices[device].addrs = dict()
206 @handler(u'List all addresses from a device')
207 def list(self, device):
209 k = self.devices[device].addrs.keys()
214 @handler(u'Get information about addresses from a device')
215 def show(self, device):
217 k = self.devices[device].addrs.values()
222 class Device(Sequence):
224 def __init__(self, name, mac):
231 return (self.name, self.mac)
233 class DeviceHandler(Handler):
235 def __init__(self, devices):
236 self.devices = devices
237 dev_fn = path.join(template_dir, device_com)
238 self.device_template = Template(filename=dev_fn)
240 @handler(u'Bring the device up')
242 if name in self.devices:
243 print self.device_template.render(dev=name, action='up')
245 raise DeviceNotFoundError(name)
247 @handler(u'Bring the device down')
248 def down(self, name):
249 if name in self.devices:
250 print self.device_template.render(dev=name, action='down')
252 raise DeviceNotFoundError(name)
254 @handler(u'List all devices')
256 return self.devices.keys()
258 @handler(u'Get information about a device')
260 return self.devices.items()
262 class IpHandler(Handler):
264 def __init__(self, pickle_dir='.', config_dir='.'):
265 r"Initialize DhcpHandler object, see class documentation for details."
267 self.pickle_dir = pickle_dir
268 self.config_dir = config_dir
270 ip_add_fn = path.join(template_dir, ip_add_com)
271 ip_del_fn = path.join(template_dir, ip_del_com)
272 ip_flush_fn = path.join(template_dir, ip_flush_com)
273 self.ip_add_template = Template(filename=ip_add_fn)
274 self.ip_del_template = Template(filename=ip_del_fn)
275 self.ip_flush_template = Template(filename=ip_flush_fn)
277 route_add_fn = path.join(template_dir, route_add_com)
278 route_del_fn = path.join(template_dir, route_del_com)
279 route_flush_fn = path.join(template_dir, route_flush_com)
280 self.route_add_template = Template(filename=route_add_fn)
281 self.route_del_template = Template(filename=route_del_fn)
282 self.route_flush_template = Template(filename=route_flush_fn)
287 p = Popen('ip link list', shell=True, stdout=PIPE, close_fds=True)
288 devs = _get_devices(p.stdout.read())
289 self.devices = dict()
290 for eth, mac in devs:
291 self.devices[eth] = Device(eth, mac)
293 self.addr = AddressHandler(self.devices)
294 self.route = RouteHandler(self.devices)
295 self.dev = DeviceHandler(self.devices)
298 @handler(u'Commit the changes (reloading the service, if necessary).')
300 r"commit() -> None :: Commit the changes and reload the DHCP service."
301 #esto seria para poner en una interfaz
302 #y seria que hace el pickle deberia llamarse
303 #al hacerse un commit
307 @handler(u'Discard all the uncommited changes.')
309 r"rollback() -> None :: Discard the changes not yet commited."
313 r"_dump() -> None :: Dump all persistent data to pickle files."
314 # XXX podría ir en una clase base
315 self._dump_var(self.devices, pickle_devices)
319 r"_load() -> None :: Load all persistent data from pickle files."
320 # XXX podría ir en una clase base
321 self.devices = self._load_var(pickle_devices)
323 def _pickle_filename(self, name):
324 r"_pickle_filename() -> string :: Construct a pickle filename."
325 # XXX podría ir en una clase base
326 return path.join(self.pickle_dir, name) + pickle_ext
328 def _dump_var(self, var, name):
329 r"_dump_var() -> None :: Dump a especific variable to a pickle file."
330 # XXX podría ir en una clase base
331 pkl_file = file(self._pickle_filename(name), 'wb')
332 pickle.dump(var, pkl_file, 2)
335 def _load_var(self, name):
336 r"_load_var()7 -> object :: Load a especific pickle file."
337 # XXX podría ir en una clase base
338 return pickle.load(file(self._pickle_filename(name)))
340 def _write_config(self):
341 r"_write_config() -> None :: Execute all commands."
342 for device in self.devices.values():
343 print self.route_flush_template.render(dev=device.name)
344 print self.ip_flush_template.render(dev=device.name)
345 for address in device.addrs.values():
346 print self.ip_add_template.render(
349 prefix=address.prefix,
350 broadcast=address.broadcast
352 for route in device.routes:
353 print self.route_add_template.render(
355 net_addr=route.net_addr,
357 gateway=route.gateway
360 def _get_devices(string):
362 i = string.find('eth')
365 m = string.find('link/ether', i+4)
366 mac = string[ m+11 : m+11+17]
368 i = string.find('eth', m+11+17)
371 if __name__ == '__main__':
374 print '----------------------'
376 ip.addr.add('eth0','192.168.0.23','24','192.168.255.255')
377 ip.addr.add('eth0','192.168.0.26','24')
379 ip.route.add('eth0','192.168.0.0','24','192.168.0.1')
380 ip.route.add('eth0','192.168.0.5','24','192.168.0.1')
382 ip.route.flush('eth0')
384 ip.addr.delete('eth0','192.168.0.23')