]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdown/scheduler.py
Merge 'vlan filtering bridge + vxlan + mlag + vrr' support from internal
[mirror_ifupdown2.git] / ifupdown2 / ifupdown / scheduler.py
1 #!/usr/bin/python
2 #
3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
5 #
6 # ifaceScheduler --
7 # interface scheduler
8 #
9
10 from statemanager import *
11 from iface import *
12 from graph import *
13 from collections import deque
14 from collections import OrderedDict
15 import logging
16 import traceback
17 import sys
18 from graph import *
19 from collections import deque
20 from threading import *
21 from ifupdownbase import *
22 from sets import Set
23
24 class ifaceSchedulerFlags():
25 """ Enumerates scheduler flags """
26
27 INORDER = 0x1
28 POSTORDER = 0x2
29
30 class ifaceScheduler():
31 """ scheduler functions to schedule configuration of interfaces.
32
33 supports scheduling of interfaces serially in plain interface list
34 or dependency graph format.
35
36 """
37
38 _STATE_CHECK = True
39
40 _SCHED_RETVAL = True
41
42 @classmethod
43 def run_iface_op(cls, ifupdownobj, ifaceobj, op, cenv=None):
44 """ Runs sub operation on an interface """
45 ifacename = ifaceobj.name
46
47 if ifupdownobj.type and ifupdownobj.type != ifaceobj.type:
48 return
49
50 if not ifupdownobj.ADDONS_ENABLE: return
51 if op == 'query-checkcurr':
52 query_ifaceobj=ifupdownobj.create_n_save_ifaceobjcurr(ifaceobj)
53 # If not type bridge vlan and the object does not exist,
54 # mark not found and return
55 if (not ifupdownobj.link_exists(ifaceobj.name) and
56 ifaceobj.type != ifaceType.BRIDGE_VLAN):
57 query_ifaceobj.set_state_n_status(ifaceState.from_str(op),
58 ifaceStatus.NOTFOUND)
59 return
60 for mname in ifupdownobj.module_ops.get(op):
61 m = ifupdownobj.modules.get(mname)
62 err = 0
63 try:
64 if hasattr(m, 'run'):
65 msg = ('%s: %s : running module %s' %(ifacename, op, mname))
66 if op == 'query-checkcurr':
67 # Dont check curr if the interface object was
68 # auto generated
69 if (ifaceobj.priv_flags & ifupdownobj.NOCONFIG):
70 continue
71 ifupdownobj.logger.debug(msg)
72 m.run(ifaceobj, op, query_ifaceobj,
73 ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
74 else:
75 ifupdownobj.logger.debug(msg)
76 m.run(ifaceobj, op,
77 ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
78 except Exception, e:
79 if not ifupdownobj.ignore_error(str(e)):
80 err = 1
81 ifupdownobj.logger.warn(str(e))
82 # Continue with rest of the modules
83 pass
84 finally:
85 if err or ifaceobj.status == ifaceStatus.ERROR:
86 ifaceobj.set_state_n_status(ifaceState.from_str(op),
87 ifaceStatus.ERROR)
88 if 'up' in op or 'down' in op:
89 cls._SCHED_RETVAL = False
90 else:
91 # Mark success only if the interface was not already
92 # marked with error
93 status = (ifaceobj.status
94 if ifaceobj.status == ifaceStatus.ERROR
95 else ifaceStatus.SUCCESS)
96 ifaceobj.set_state_n_status(ifaceState.from_str(op),
97 status)
98
99 if ifupdownobj.config.get('addon_scripts_support', '0') == '1':
100 # execute /etc/network/ scripts
101 for mname in ifupdownobj.script_ops.get(op, []):
102 ifupdownobj.logger.debug('%s: %s : running script %s'
103 %(ifacename, op, mname))
104 try:
105 ifupdownobj.exec_command(mname, cmdenv=cenv)
106 except Exception, e:
107 ifupdownobj.log_error(str(e))
108
109 @classmethod
110 def run_iface_list_ops(cls, ifupdownobj, ifaceobjs, ops):
111 """ Runs all operations on a list of interface
112 configurations for the same interface
113 """
114
115 # minor optimization. If operation is 'down', proceed only
116 # if interface exists in the system
117 ifacename = ifaceobjs[0].name
118 ifupdownobj.logger.info('%s: running ops ...' %ifacename)
119 if ('down' in ops[0] and
120 ifaceobjs[0].type != ifaceType.BRIDGE_VLAN and
121 not ifupdownobj.link_exists(ifacename)):
122 ifupdownobj.logger.debug('%s: does not exist' %ifacename)
123 # run posthook before you get out of here, so that
124 # appropriate cleanup is done
125 posthookfunc = ifupdownobj.sched_hooks.get('posthook')
126 if posthookfunc:
127 for ifaceobj in ifaceobjs:
128 ifaceobj.status = ifaceStatus.SUCCESS
129 posthookfunc(ifupdownobj, ifaceobj, 'down')
130 return
131 for op in ops:
132 # first run ifupdownobj handlers. This is good enough
133 # for the first object in the list
134 handler = ifupdownobj.ops_handlers.get(op)
135 if handler:
136 try:
137 handler(ifupdownobj, ifaceobjs[0])
138 except Exception, e:
139 if not ifupdownobj.link_master_slave_ignore_error(str(e)):
140 ifupdownobj.logger.warn('%s: %s'
141 %(ifaceobjs[0].name, str(e)))
142 pass
143 for ifaceobj in ifaceobjs:
144 cls.run_iface_op(ifupdownobj, ifaceobj, op,
145 cenv=ifupdownobj.generate_running_env(ifaceobj, op)
146 if ifupdownobj.config.get('addon_scripts_support',
147 '0') == '1' else None)
148 posthookfunc = ifupdownobj.sched_hooks.get('posthook')
149 if posthookfunc:
150 try:
151 [posthookfunc(ifupdownobj, ifaceobj, ops[0])
152 for ifaceobj in ifaceobjs]
153 except Exception, e:
154 ifupdownobj.logger.warn('%s' %str(e))
155 pass
156
157 @classmethod
158 def _check_upperifaces(cls, ifupdownobj, ifaceobj, ops, parent,
159 followdependents=False):
160 """ Check if upperifaces are hanging off us and help caller decide
161 if he can proceed with the ops on this device
162
163 Returns True or False indicating the caller to proceed with the
164 operation.
165 """
166 # proceed only for down operation
167 if 'down' not in ops[0]:
168 return True
169
170 if (ifupdownobj.FORCE or
171 not ifupdownobj.ADDONS_ENABLE or
172 (not ifupdownobj.is_ifaceobj_noconfig(ifaceobj) and
173 ifupdownobj.config.get('warn_on_ifdown', '0') == '0' and
174 not ifupdownobj.ALL)):
175 return True
176
177 ulist = ifaceobj.upperifaces
178 if not ulist:
179 return True
180
181 # Get the list of upper ifaces other than the parent
182 tmpulist = ([u for u in ulist if u != parent] if parent
183 else ulist)
184 if not tmpulist:
185 return True
186 # XXX: This is expensive. Find a cheaper way to do this.
187 # if any of the upperdevs are present,
188 # return false to the caller to skip this interface
189 for u in tmpulist:
190 if ifupdownobj.link_exists(u):
191 if not ifupdownobj.ALL:
192 if ifupdownobj.is_ifaceobj_noconfig(ifaceobj):
193 ifupdownobj.logger.info('%s: skipping interface down,'
194 %ifaceobj.name + ' upperiface %s still around ' %u)
195 else:
196 ifupdownobj.logger.warn('%s: skipping interface down,'
197 %ifaceobj.name + ' upperiface %s still around ' %u)
198 return False
199 return True
200
201 @classmethod
202 def run_iface_graph(cls, ifupdownobj, ifacename, ops, parent=None,
203 order=ifaceSchedulerFlags.POSTORDER,
204 followdependents=True):
205 """ runs interface by traversing all nodes rooted at itself """
206
207 # Each ifacename can have a list of iface objects
208 ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
209 if not ifaceobjs:
210 raise Exception('%s: not found' %ifacename)
211
212 # Check state of the dependent. If it is already brought up, return
213 if (cls._STATE_CHECK and
214 (ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
215 ifupdownobj.logger.debug('%s: already processed' %ifacename)
216 return
217
218 for ifaceobj in ifaceobjs:
219 if not cls._check_upperifaces(ifupdownobj, ifaceobj,
220 ops, parent, followdependents):
221 return
222
223 # If inorder, run the iface first and then its dependents
224 if order == ifaceSchedulerFlags.INORDER:
225 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
226
227 for ifaceobj in ifaceobjs:
228 # Run lowerifaces or dependents
229 dlist = ifaceobj.lowerifaces
230 if dlist:
231 ifupdownobj.logger.debug('%s: found dependents %s'
232 %(ifacename, str(dlist)))
233 try:
234 if not followdependents:
235 # XXX: this is yet another extra step,
236 # but is needed for interfaces that are
237 # implicit dependents. even though we are asked to
238 # not follow dependents, we must follow the ones
239 # that dont have user given config. Because we own them
240 new_dlist = [d for d in dlist
241 if ifupdownobj.is_iface_noconfig(d)]
242 if new_dlist:
243 cls.run_iface_list(ifupdownobj, new_dlist, ops,
244 ifacename, order, followdependents,
245 continueonfailure=False)
246 else:
247 cls.run_iface_list(ifupdownobj, dlist, ops,
248 ifacename, order,
249 followdependents,
250 continueonfailure=False)
251 except Exception, e:
252 if (ifupdownobj.ignore_error(str(e))):
253 pass
254 else:
255 # Dont bring the iface up if children did not come up
256 ifaceobj.set_state_n_status(ifaceState.NEW,
257 ifaceStatus.ERROR)
258 raise
259 if order == ifaceSchedulerFlags.POSTORDER:
260 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
261
262 @classmethod
263 def run_iface_list(cls, ifupdownobj, ifacenames,
264 ops, parent=None, order=ifaceSchedulerFlags.POSTORDER,
265 followdependents=True, continueonfailure=True):
266 """ Runs interface list """
267
268 for ifacename in ifacenames:
269 try:
270 cls.run_iface_graph(ifupdownobj, ifacename, ops, parent,
271 order, followdependents)
272 except Exception, e:
273 if continueonfailure:
274 if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
275 traceback.print_tb(sys.exc_info()[2])
276 ifupdownobj.logger.error('%s : %s' %(ifacename, str(e)))
277 pass
278 else:
279 if (ifupdownobj.ignore_error(str(e))):
280 pass
281 else:
282 raise Exception('%s : (%s)' %(ifacename, str(e)))
283
284 @classmethod
285 def run_iface_graph_upper(cls, ifupdownobj, ifacename, ops, parent=None,
286 followdependents=True, skip_root=False):
287 """ runs interface by traversing all nodes rooted at itself """
288
289 # Each ifacename can have a list of iface objects
290 ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
291 if not ifaceobjs:
292 raise Exception('%s: not found' %ifacename)
293
294 if (cls._STATE_CHECK and
295 (ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
296 ifupdownobj.logger.debug('%s: already processed' %ifacename)
297 return
298
299 if not skip_root:
300 # run the iface first and then its upperifaces
301 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
302 for ifaceobj in ifaceobjs:
303 # Run upperifaces
304 ulist = ifaceobj.upperifaces
305 if ulist:
306 ifupdownobj.logger.debug('%s: found upperifaces %s'
307 %(ifacename, str(ulist)))
308 try:
309 cls.run_iface_list_upper(ifupdownobj, ulist, ops,
310 ifacename,
311 followdependents,
312 continueonfailure=True)
313 except Exception, e:
314 if (ifupdownobj.ignore_error(str(e))):
315 pass
316 else:
317 raise
318
319 @classmethod
320 def run_iface_list_upper(cls, ifupdownobj, ifacenames,
321 ops, parent=None, followdependents=True,
322 continueonfailure=True, skip_root=False):
323 """ Runs interface list """
324
325 for ifacename in ifacenames:
326 try:
327 cls.run_iface_graph_upper(ifupdownobj, ifacename, ops, parent,
328 followdependents, skip_root)
329 except Exception, e:
330 if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
331 traceback.print_tb(sys.exc_info()[2])
332 ifupdownobj.logger.warn('%s : %s' %(ifacename, str(e)))
333 pass
334
335 @classmethod
336 def _get_valid_upperifaces(cls, ifupdownobj, ifacenames,
337 allupperifacenames):
338 """ Recursively find valid upperifaces
339
340 valid upperifaces are:
341 - An upperiface which had no user config (example builtin
342 interfaces. usually vlan interfaces.)
343 - or had config and previously up
344 - and interface currently does not exist
345 - or is a bridge (because if your upperiface was a bridge
346 - u will have to execute up on the bridge
347 to enslave the port and apply bridge attributes to the port) """
348
349 upperifacenames = []
350 for ifacename in ifacenames:
351 # get upperifaces
352 ifaceobj = ifupdownobj.get_ifaceobj_first(ifacename)
353 if not ifaceobj:
354 continue
355 ulist = Set(ifaceobj.upperifaces).difference(upperifacenames)
356 nulist = []
357 for u in ulist:
358 uifaceobj = ifupdownobj.get_ifaceobj_first(u)
359 if not uifaceobj:
360 continue
361 has_config = not bool(uifaceobj.priv_flags
362 & ifupdownobj.NOCONFIG)
363 if (((has_config and ifupdownobj.get_ifaceobjs_saved(u)) or
364 not has_config) and (not ifupdownobj.link_exists(u)
365 or uifaceobj.link_kind == ifaceLinkKind.BRIDGE)):
366 nulist.append(u)
367 upperifacenames.extend(nulist)
368 allupperifacenames.extend(upperifacenames)
369 if upperifacenames:
370 cls._get_valid_upperifaces(ifupdownobj, upperifacenames,
371 allupperifacenames)
372 return
373
374 @classmethod
375 def run_upperifaces(cls, ifupdownobj, ifacenames, ops,
376 continueonfailure=True):
377 """ Run through valid upperifaces """
378 upperifaces = []
379
380 cls._get_valid_upperifaces(ifupdownobj, ifacenames, upperifaces)
381 if not upperifaces:
382 return
383 # dump valid upperifaces
384 ifupdownobj.logger.debug(upperifaces)
385 for u in upperifaces:
386 try:
387 ifaceobjs = ifupdownobj.get_ifaceobjs(u)
388 if not ifaceobjs:
389 continue
390 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
391 except Exception, e:
392 if continueonfailure:
393 self.logger.warn('%s' %str(e))
394
395 @classmethod
396 def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops,
397 dependency_graph, indegrees=None):
398 if len(ifacenames) == 1:
399 return ifacenames
400 # Get a sorted list of all interfaces
401 if not indegrees:
402 indegrees = OrderedDict()
403 for ifacename in dependency_graph.keys():
404 indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
405 ifacenames_all_sorted = graph.topological_sort_graphs_all(
406 dependency_graph, indegrees)
407 # if ALL was set, return all interfaces
408 if ifupdownobj.ALL:
409 return ifacenames_all_sorted
410
411 # else return ifacenames passed as argument in sorted order
412 ifacenames_sorted = []
413 [ifacenames_sorted.append(ifacename)
414 for ifacename in ifacenames_all_sorted
415 if ifacename in ifacenames]
416 return ifacenames_sorted
417
418 @classmethod
419 def sched_ifaces(cls, ifupdownobj, ifacenames, ops,
420 dependency_graph=None, indegrees=None,
421 order=ifaceSchedulerFlags.POSTORDER,
422 followdependents=True, skipupperifaces=False):
423 """ runs interface configuration modules on interfaces passed as
424 argument. Runs topological sort on interface dependency graph.
425
426 Args:
427 **ifupdownobj** (object): ifupdownMain object
428
429 **ifacenames** (list): list of interface names
430
431 **ops** : list of operations to perform eg ['pre-up', 'up', 'post-up']
432
433 **dependency_graph** (dict): dependency graph in adjacency list format
434
435 Kwargs:
436 **indegrees** (dict): indegree array of the dependency graph
437
438 **order** (int): ifaceSchedulerFlags (POSTORDER, INORDER)
439
440 **followdependents** (bool): follow dependent interfaces if true
441
442 """
443 #
444 # Algo:
445 # if ALL/auto interfaces are specified,
446 # - walk the dependency tree in postorder or inorder depending
447 # on the operation.
448 # (This is to run interfaces correctly in order)
449 # else:
450 # - sort iface list if the ifaces belong to a "class"
451 # - else just run iface list in the order they were specified
452 #
453 # Run any upperifaces if available
454 #
455 followupperifaces = False
456 run_queue = []
457 skip_ifacesort = int(ifupdownobj.config.get('skip_ifacesort', '0'))
458 if not skip_ifacesort and not indegrees:
459 indegrees = OrderedDict()
460 for ifacename in dependency_graph.keys():
461 indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
462
463 if not ifupdownobj.ALL:
464 # If there is any interface that does exist, maybe it is a
465 # logical interface and we have to followupperifaces when it
466 # comes up, so get that list.
467 if any([True for i in ifacenames
468 if ifupdownobj.must_follow_upperifaces(i)]):
469 followupperifaces = (True if
470 [i for i in ifacenames
471 if not ifupdownobj.link_exists(i)]
472 else False)
473 if not skip_ifacesort and ifupdownobj.IFACE_CLASS:
474 # sort interfaces only if allow class was specified and
475 # not skip_ifacesort
476 run_queue = cls.get_sorted_iface_list(ifupdownobj, ifacenames,
477 ops, dependency_graph, indegrees)
478 if run_queue and 'up' in ops[0]:
479 run_queue.reverse()
480 else:
481 # if -a is set, we dont really have to sort. We pick the interfaces
482 # that have no parents and
483 if not skip_ifacesort:
484 sorted_ifacenames = cls.get_sorted_iface_list(ifupdownobj,
485 ifacenames, ops, dependency_graph,
486 indegrees)
487 if sorted_ifacenames:
488 # pick interfaces that user asked
489 # and those that dont have any dependents first
490 [run_queue.append(ifacename)
491 for ifacename in sorted_ifacenames
492 if ifacename in ifacenames and
493 not indegrees.get(ifacename)]
494 ifupdownobj.logger.debug('graph roots (interfaces that ' +
495 'dont have dependents):' + ' %s' %str(run_queue))
496 else:
497 ifupdownobj.logger.warn('interface sort returned None')
498
499 # If queue not present, just run interfaces that were asked by the user
500 if not run_queue:
501 run_queue = list(ifacenames)
502 if 'down' in ops[0]:
503 run_queue.reverse()
504
505 # run interface list
506 cls.run_iface_list(ifupdownobj, run_queue, ops,
507 parent=None, order=order,
508 followdependents=followdependents)
509 if not cls._SCHED_RETVAL:
510 raise Exception()
511
512 if (not skipupperifaces and
513 ifupdownobj.config.get('skip_upperifaces', '0') == '0' and
514 ((not ifupdownobj.ALL and followdependents) or
515 followupperifaces) and
516 'up' in ops[0]):
517 # If user had given a set of interfaces to bring up
518 # try and execute 'up' on the upperifaces
519 ifupdownobj.logger.info('running upperifaces (parent interfaces) ' +
520 'if available ..')
521 cls._STATE_CHECK = False
522 cls.run_upperifaces(ifupdownobj, ifacenames, ops)
523 cls._STATE_CHECK = True