]> git.proxmox.com Git - mirror_ifupdown2.git/blob - addons/vrf.py
addons: vrf: use full path to cgcreate and cgset
[mirror_ifupdown2.git] / addons / vrf.py
1 #!/usr/bin/python
2 #
3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
5 #
6
7 import os
8 import atexit
9 from ifupdown.iface import *
10 import ifupdown.policymanager as policymanager
11 import ifupdownaddons
12 import ifupdown.rtnetlink_api as rtnetlink_api
13 from ifupdownaddons.modulebase import moduleBase
14 from ifupdownaddons.bondutil import bondutil
15 from ifupdownaddons.iproute2 import iproute2
16
17 class vrf(moduleBase):
18 """ ifupdown2 addon module to configure vrfs """
19 _modinfo = { 'mhelp' : 'vrf configuration module',
20 'attrs' : {
21 'vrf-table':
22 {'help' : 'vrf device table id. key to ' +
23 'creating a vrf device',
24 'example': ['vrf-table-id 1']},
25 'vrf-default-route':
26 {'help' : 'vrf device default route ' +
27 'to avoid communication outside the vrf device',
28 'example': ['vrf-default-route yes/no']},
29 'vrf':
30 {'help' : 'vrf the interface is part of.',
31 'example': ['vrf blue']}}}
32
33 iproute2_vrf_filename = '/etc/iproute2/rt_tables.d/ifupdown2_vrf_map.conf'
34 iproute2_vrf_filehdr = '# This file is autogenerated by ifupdown2.\n' + \
35 '# It contains the vrf name to table mapping.\n' + \
36 '# Reserved table range %s %s\n'
37 VRF_TABLE_START = 1001
38 VRF_TABLE_END = 5000
39
40 def __init__(self, *args, **kargs):
41 ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs)
42 self.ipcmd = None
43 self.bondcmd = None
44 try:
45 ip_rules = self.exec_command('/sbin/ip rule show').splitlines()
46 self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules]
47 except Exception, e:
48 self.ip_rule_cache = []
49 self.logger.warn('%s' %str(e))
50
51 try:
52 ip_rules = self.exec_command('/sbin/ip -6 rule show').splitlines()
53 self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules]
54 except Exception, e:
55 self.ip6_rule_cache = []
56 self.logger.warn('%s' %str(e))
57
58 #self.logger.debug("vrf: ip rule cache")
59 #self.logger.info(self.ip_rule_cache)
60
61 #self.logger.info("vrf: ip -6 rule cache")
62 #self.logger.info(self.ip6_rule_cache)
63
64 # XXX: check for vrf reserved overlap in /etc/iproute2/rt_tables
65 self.iproute2_vrf_map = {}
66 # read or create /etc/iproute2/rt_tables.d/ifupdown2.vrf_map
67 if os.path.exists(self.iproute2_vrf_filename):
68 self.vrf_map_fd = open(self.iproute2_vrf_filename, 'a+')
69 lines = self.vrf_map_fd.readlines()
70 for l in lines:
71 l = l.strip()
72 if l[0] == '#':
73 continue
74 try:
75 (table, vrf_name) = l.strip().split()
76 self.iproute2_vrf_map[table] = vrf_name
77 except Exception, e:
78 self.logger.info('vrf: iproute2_vrf_map: unable to parse %s'
79 %l)
80 pass
81 #self.logger.info("vrf: dumping iproute2_vrf_map")
82 #self.logger.info(self.iproute2_vrf_map)
83
84 # purge vrf table entries that are not around
85 iproute2_vrf_map_pruned = {}
86 for t, v in self.iproute2_vrf_map.iteritems():
87 if os.path.exists('/sys/class/net/%s' %v):
88 iproute2_vrf_map_pruned[t] = v
89 else:
90 try:
91 # cleanup rules
92 self._del_vrf_rules(v, t)
93 except Exception:
94 pass
95 self.iproute2_vrf_map = iproute2_vrf_map_pruned
96
97 self.vrf_table_id_start = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-start')
98 if not self.vrf_table_id_start:
99 self.vrf_table_id_start = self.VRF_TABLE_START
100 self.vrf_table_id_end = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-end')
101 if not self.vrf_table_id_end:
102 self.vrf_table_id_end = self.VRF_TABLE_END
103 self.vrf_max_count = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-max-count')
104
105 last_used_vrf_table = None
106 for t in range(self.vrf_table_id_start,
107 self.vrf_table_id_end):
108 if not self.iproute2_vrf_map.get(t):
109 break
110 last_used_vrf_table = t
111 self.last_used_vrf_table = last_used_vrf_table
112 self.iproute2_write_vrf_map = False
113 atexit.register(self.iproute2_vrf_map_write)
114 self.vrf_fix_local_table = True
115 self.vrf_count = 0
116 self.vrf_cgroup_create = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-cgroup-create')
117 if not self.vrf_cgroup_create:
118 self.vrf_cgroup_create = False
119 elif self.vrf_cgroup_create == 'yes':
120 self.vrf_cgroup_create = True
121 else:
122 self.vrf_cgroup_create = False
123
124 def iproute2_vrf_map_write(self):
125 if not self.iproute2_write_vrf_map:
126 return
127 self.logger.info('vrf: writing table map to %s'
128 %self.iproute2_vrf_filename)
129 with open(self.iproute2_vrf_filename, 'w') as f:
130 f.write(self.iproute2_vrf_filehdr %(self.vrf_table_id_start,
131 self.vrf_table_id_end))
132 for t, v in self.iproute2_vrf_map.iteritems():
133 f.write('%s %s\n' %(t, v))
134
135 def _is_vrf(self, ifaceobj):
136 if ifaceobj.get_attr_value_first('vrf-table'):
137 return True
138 return False
139
140 def get_upper_ifacenames(self, ifaceobj, ifacenames_all=None):
141 """ Returns list of interfaces dependent on ifaceobj """
142
143 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
144 if vrf_table:
145 ifaceobj.link_type = ifaceLinkType.LINK_MASTER
146 ifaceobj.link_kind |= ifaceLinkKind.VRF
147 vrf_iface_name = ifaceobj.get_attr_value_first('vrf')
148 if not vrf_iface_name:
149 return None
150 ifaceobj.link_type = ifaceLinkType.LINK_SLAVE
151 return [vrf_iface_name]
152
153 def get_upper_ifacenames_running(self, ifaceobj):
154 return None
155
156 def _get_iproute2_vrf_table(self, vrf_dev_name):
157 for t, v in self.iproute2_vrf_map.iteritems():
158 if v == vrf_dev_name:
159 return t
160 return None
161
162 def _get_avail_vrf_table_id(self):
163 if self.last_used_vrf_table == None:
164 table_id_start = self.vrf_table_id_start
165 else:
166 table_id_start = self.last_used_vrf_table + 1
167 for t in range(table_id_start,
168 self.vrf_table_id_end):
169 if not self.iproute2_vrf_map.get(t):
170 self.last_used_vrf_table = t
171 return str(t)
172 return None
173
174 def _iproute2_vrf_table_entry_add(self, vrf_dev_name, table_id):
175 self.iproute2_vrf_map[table_id] = vrf_dev_name
176 self.iproute2_write_vrf_map = True
177
178 def _iproute2_vrf_table_entry_del(self, table_id):
179 try:
180 del self.iproute2_vrf_map[table_id]
181 self.iproute2_write_vrf_map = True
182 except Exception, e:
183 self.logger.info('vrf: iproute2 vrf map del failed for %d (%s)'
184 %(table_id, str(e)))
185 pass
186
187 def _up_vrf_slave(self, ifacename, vrfname):
188 try:
189 if self.ipcmd.link_exists(vrfname):
190 self.ipcmd.link_set(ifacename, 'master', vrfname)
191 except Exception, e:
192 self.logger.warn('%s: %s' %(ifacename, str(e)))
193
194 def _del_vrf_rules(self, vrf_dev_name, vrf_table):
195 pref = 200
196 ip_rule_out_format = '%s: from all %s %s lookup %s'
197 ip_rule_cmd = 'ip %s rule del pref %s %s %s table %s'
198
199 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
200 if rule in self.ip_rule_cache:
201 rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, vrf_table)
202 self.exec_command(rule_cmd)
203
204 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
205 if rule in self.ip_rule_cache:
206 rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, vrf_table)
207 self.exec_command(rule_cmd)
208
209 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
210 if rule in self.ip_rule_cache:
211 rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name,
212 vrf_table)
213 self.exec_command(rule_cmd)
214
215 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
216 if rule in self.ip_rule_cache:
217 rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name,
218 vrf_table)
219 self.exec_command(rule_cmd)
220
221 def _add_vrf_rules(self, vrf_dev_name, vrf_table):
222 pref = 200
223 ip_rule_out_format = '%s: from all %s %s lookup %s'
224 ip_rule_cmd = 'ip %s rule add pref %s %s %s table %s'
225 if self.vrf_fix_local_table:
226 self.vrf_fix_local_table = False
227 rule = '0: from all lookup local'
228 if rule in self.ip_rule_cache:
229 try:
230 self.exec_command('ip rule del pref 0')
231 self.exec_command('ip rule add pref 32765 table local')
232 except Exception, e:
233 self.logger.info('%s' %str(e))
234 pass
235
236 #Example ip rule
237 #200: from all oif blue lookup blue
238 #200: from all iif blue lookup blue
239
240 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
241 if rule not in self.ip_rule_cache:
242 rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, vrf_table)
243 self.exec_command(rule_cmd)
244
245 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
246 if rule not in self.ip_rule_cache:
247 rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, vrf_table)
248 self.exec_command(rule_cmd)
249
250 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
251 if rule not in self.ip_rule_cache:
252 rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, vrf_table)
253 self.exec_command(rule_cmd)
254
255 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
256 if rule not in self.ip_rule_cache:
257 rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name,
258 vrf_table)
259 self.exec_command(rule_cmd)
260
261 def _add_vrf_slaves(self, ifaceobj):
262 running_slaves = self.ipcmd.link_get_lowers(ifaceobj.name)
263 config_slaves = ifaceobj.lowerifaces
264 if not config_slaves and not running_slaves:
265 return
266 add_slaves = set(config_slaves).difference(set(running_slaves))
267 del_slaves = set(running_slaves).difference(set(config_slaves))
268 if add_slaves:
269 for s in add_slaves:
270 try:
271 self._up_vrf_slave(s, ifaceobj.name)
272 except Exception, e:
273 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
274
275 if del_slaves:
276 for s in del_slaves:
277 try:
278 self._down_vrf_slave(s, ifaceobj.name)
279 except Exception, e:
280 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
281
282 if ifaceobj.link_type == ifaceLinkType.LINK_MASTER:
283 for s in config_slaves:
284 try:
285 rtnetlink_api.rtnl_api.link_set(s, "up")
286 except Exception, e:
287 self.logger.debug('%s: %s: link set up (%s)'
288 %(ifaceobj.name, s, str(e)))
289 pass
290
291 def _create_cgroup(self, ifaceobj):
292 if not self.vrf_cgroup_create:
293 return
294 try:
295 if not os.path.exists('/sys/fs/cgroup/l3mdev/%s' %ifaceobj.name):
296 self.exec_command('/usr/bin/cgcreate -g l3mdev:%s' %ifaceobj.name)
297 self.exec_command('/usr/bin/cgset -r l3mdev.master-device=%s %s'
298 %(ifaceobj.name, ifaceobj.name))
299 except Exception, e:
300 self.log_warn('%s: cgroup create failed (%s)\n'
301 %(ifaceobj.name, str(e)), ifaceobj)
302
303 def _up_vrf_dev(self, ifaceobj, vrf_table):
304
305 if not self.ipcmd.link_exists(ifaceobj.name):
306 if vrf_table == 'auto':
307 vrf_table = self._get_avail_vrf_table_id()
308 if not vrf_table:
309 self.log_error('%s: unable to get an auto table id'
310 %ifaceobj.name)
311 self.logger.info('%s: table id auto: selected table id %s\n'
312 %(ifaceobj.name, vrf_table))
313 # XXX: If we decide to not allow vrf id usages out of
314 # the reserved ifupdown range, then uncomment this code.
315 #else:
316 # if (int(vrf_table) < self.vrf_table_id_start or
317 # int(vrf_table) > self.vrf_table_id_end):
318 # self.log_error('%s: vrf table id %s out of reserved range [%d,%d]'
319 # %(ifaceobj.name, vrf_table,
320 # self.vrf_table_id_start,
321 # self.vrf_table_id_end))
322 try:
323 self.ipcmd.link_create(ifaceobj.name, 'vrf',
324 {'table' : '%s' %vrf_table})
325 except Exception, e:
326 self.log_error('%s: create failed (%s)\n'
327 %(ifaceobj.name, str(e)))
328 else:
329 vrf_table = self._get_iproute2_vrf_table(ifaceobj.name)
330 if not vrf_table:
331 self.log_error('%s: unable to get vrf table id'
332 %ifaceobj.name)
333
334 # if the device exists, check if table id is same
335 vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name)
336 if vrfdev_attrs:
337 running_table = vrfdev_attrs.get('table', None)
338 if vrf_table != running_table:
339 self.log_error('%s: cannot change vrf table id,running table id %s is different from config id %s' %(ifaceobj.name,
340 running_table, vrf_table))
341
342 try:
343 self._iproute2_vrf_table_entry_add(ifaceobj.name, vrf_table)
344 self._add_vrf_rules(ifaceobj.name, vrf_table)
345 self._add_vrf_slaves(ifaceobj)
346 self._create_cgroup(ifaceobj)
347 except Exception, e:
348 self.log_error('%s: %s' %(ifaceobj.name, str(e)))
349
350 def _up_vrf_default_route(self, ifaceobj, vrf_table):
351 vrf_default_route = ifaceobj.get_attr_value_first('vrf-default-route')
352 if not vrf_default_route:
353 vrf_default_route = policymanager.policymanager_api.get_attr_default(
354 module_name=self.__class__.__name__, attr='vrf-default-route')
355 if not vrf_default_route:
356 return
357 if str(vrf_default_route).lower() == "yes":
358 try:
359 self.exec_command('ip route add table %s unreachable default' %vrf_table)
360 except OSError, e:
361 if e.errno != 17:
362 raise
363 pass
364
365 try:
366 self.exec_command('ip -6 route add table %s unreachable default' %vrf_table)
367 except OSError, e:
368 if e.errno != 17:
369 raise
370 pass
371
372 def _up(self, ifaceobj):
373 try:
374 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
375 if vrf_table:
376 if self.vrf_count == self.vrf_max_count:
377 self.log_error('%s: max vrf count %d hit...not '
378 'creating vrf' %(ifaceobj.name,
379 self.vrf_count))
380 self._up_vrf_dev(ifaceobj, vrf_table)
381 self._up_vrf_default_route(ifaceobj, vrf_table)
382 else:
383 vrf = ifaceobj.get_attr_value_first('vrf')
384 if vrf:
385 self._up_vrf_slave(ifaceobj.name, vrf)
386 except Exception, e:
387 self.log_error(str(e))
388
389 def _delete_cgroup(self, ifaceobj):
390 try:
391 if os.path.exists('/sys/fs/cgroup/l3mdev/%s' %ifaceobj.name):
392 self.exec_command('cgdelete -g l3mdev:%s' %ifaceobj.name)
393 except Exception, e:
394 self.log_warn('%s: cgroup delete failed (%s)\n'
395 %(ifaceobj.name, str(e)), ifaceobj)
396
397 def _down_vrf_dev(self, ifaceobj, vrf_table):
398 if vrf_table == 'auto':
399 vrf_table = self._get_iproute2_vrf_table(ifaceobj.name)
400 try:
401 self.ipcmd.link_delete(ifaceobj.name)
402 except Exception, e:
403 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
404 pass
405
406 try:
407 self._iproute2_vrf_table_entry_del(vrf_table)
408 self._delete_cgroup(ifaceobj)
409 except Exception, e:
410 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
411 pass
412
413 try:
414 self._del_vrf_rules(ifaceobj.name, vrf_table)
415 except Exception, e:
416 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
417 pass
418
419 def _down_vrf_slave(self, ifacename, vrf):
420 try:
421 self.ipcmd.link_set(ifacename, 'nomaster')
422 except Exception, e:
423 self.logger.warn('%s: %s' %(ifacename, str(e)))
424
425 def _down(self, ifaceobj):
426 try:
427 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
428 if vrf_table:
429 self._down_vrf_dev(ifaceobj, vrf_table)
430 else:
431 vrf = ifaceobj.get_attr_value_first('vrf')
432 if vrf:
433 self._down_vrf_slave(ifaceobj.name, vrf)
434 except Exception, e:
435 self.log_warn(str(e))
436
437 def _query_check_vrf_slave(self, ifaceobj, ifaceobjcurr, vrf):
438 try:
439 master = self.ipcmd.link_get_master(ifaceobj.name)
440 if not master or master != vrf:
441 ifaceobjcurr.update_config_with_status('vrf', master, 1)
442 else:
443 ifaceobjcurr.update_config_with_status('vrf', master, 0)
444 except Exception, e:
445 self.log_warn(str(e))
446
447 def _query_check_vrf_dev(self, ifaceobj, ifaceobjcurr, vrf_table):
448 try:
449 if not self.ipcmd.link_exists(ifaceobj.name):
450 self.logger.info('%s: vrf: does not exist' %(ifaceobj.name))
451 return
452 if vrf_table == 'auto':
453 config_table = self._get_iproute2_vrf_table(ifaceobj.name)
454 else:
455 config_table = vrf_table
456 vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name)
457 if not vrfdev_attrs:
458 ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1)
459 return
460 running_table = vrfdev_attrs.get('table')
461 if not running_table:
462 ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1)
463 return
464 if config_table != running_table:
465 ifaceobjcurr.update_config_with_status('vrf-table',
466 running_table, 1)
467 else:
468 ifaceobjcurr.update_config_with_status('vrf-table',
469 running_table, 0)
470 except Exception, e:
471 self.log_warn(str(e))
472
473 def _query_check(self, ifaceobj, ifaceobjcurr):
474 try:
475 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
476 if vrf_table:
477 self._query_check_vrf_dev(ifaceobj, ifaceobjcurr, vrf_table)
478 else:
479 vrf = ifaceobj.get_attr_value_first('vrf')
480 if vrf:
481 self._query_check_vrf_slave(ifaceobj, ifaceobjcurr, vrf)
482 except Exception, e:
483 self.log_warn(str(e))
484
485 def _query_running(self, ifaceobjrunning):
486 try:
487 kind = self.ipcmd.link_get_kind(ifaceobjrunning.name)
488 if kind == 'vrf':
489 vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobjrunning.name)
490 if vrfdev_attrs:
491 running_table = vrfdev_attrs.get('table')
492 if running_table:
493 ifaceobjrunning.update_config('vrf-table',
494 running_table)
495 elif kind == 'vrf_slave':
496 vrf = self.ipcmd.link_get_master(ifaceobjrunning.name)
497 if vrf:
498 ifaceobjrunning.update_config('vrf', vrf)
499 except Exception, e:
500 self.log_warn(str(e))
501
502 _run_ops = {'pre-up' : _up,
503 'post-down' : _down,
504 'query-running' : _query_running,
505 'query-checkcurr' : _query_check}
506
507 def get_ops(self):
508 """ returns list of ops supported by this module """
509 return self._run_ops.keys()
510
511 def _init_command_handlers(self):
512 flags = self.get_flags()
513 if not self.ipcmd:
514 self.ipcmd = iproute2(**flags)
515 if not self.bondcmd:
516 self.bondcmd = bondutil(**flags)
517
518 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
519 """ run bond configuration on the interface object passed as argument
520
521 Args:
522 **ifaceobj** (object): iface object
523
524 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
525 'query-running'
526
527 Kwargs:
528 **query_ifaceobj** (object): query check ifaceobject. This is only
529 valid when op is 'query-checkcurr'. It is an object same as
530 ifaceobj, but contains running attribute values and its config
531 status. The modules can use it to return queried running state
532 of interfaces. status is success if the running state is same
533 as user required state in ifaceobj. error otherwise.
534 """
535 op_handler = self._run_ops.get(operation)
536 if not op_handler:
537 return
538 self._init_command_handlers()
539 if operation == 'query-checkcurr':
540 op_handler(self, ifaceobj, query_ifaceobj)
541 else:
542 op_handler(self, ifaceobj)