]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/ifupdown/scheduler.py
Merge 'vlan filtering bridge + vxlan + mlag + vrr' support from internal
[mirror_ifupdown2.git] / ifupdown2 / ifupdown / scheduler.py
CommitLineData
2c8c4ce7
RP
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
10from statemanager import *
11from iface import *
12from graph import *
13from collections import deque
14from collections import OrderedDict
15import logging
16import traceback
17import sys
18from graph import *
19from collections import deque
20from threading import *
21from ifupdownbase import *
f82758bf 22from sets import Set
2c8c4ce7
RP
23
24class ifaceSchedulerFlags():
25 """ Enumerates scheduler flags """
26
27 INORDER = 0x1
28 POSTORDER = 0x2
29
30class 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
f82758bf 47 if ifupdownobj.type and ifupdownobj.type != ifaceobj.type:
2c8c4ce7 48 return
f82758bf 49
2c8c4ce7
RP
50 if not ifupdownobj.ADDONS_ENABLE: return
51 if op == 'query-checkcurr':
52 query_ifaceobj=ifupdownobj.create_n_save_ifaceobjcurr(ifaceobj)
f82758bf
RP
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
2c8c4ce7
RP
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)
f82758bf
RP
72 m.run(ifaceobj, op, query_ifaceobj,
73 ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
2c8c4ce7
RP
74 else:
75 ifupdownobj.logger.debug(msg)
f82758bf
RP
76 m.run(ifaceobj, op,
77 ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
2c8c4ce7 78 except Exception, e:
f82758bf
RP
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
2c8c4ce7
RP
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:
f82758bf
RP
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)
2c8c4ce7 96 ifaceobj.set_state_n_status(ifaceState.from_str(op),
f82758bf 97 status)
2c8c4ce7 98
f82758bf 99 if ifupdownobj.config.get('addon_scripts_support', '0') == '1':
2c8c4ce7
RP
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 """
f82758bf 114
2c8c4ce7
RP
115 # minor optimization. If operation is 'down', proceed only
116 # if interface exists in the system
117 ifacename = ifaceobjs[0].name
f82758bf 118 ifupdownobj.logger.info('%s: running ops ...' %ifacename)
2c8c4ce7 119 if ('down' in ops[0] and
f82758bf 120 ifaceobjs[0].type != ifaceType.BRIDGE_VLAN and
2c8c4ce7
RP
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:
f82758bf 136 try:
2c8c4ce7 137 handler(ifupdownobj, ifaceobjs[0])
f82758bf
RP
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
2c8c4ce7
RP
143 for ifaceobj in ifaceobjs:
144 cls.run_iface_op(ifupdownobj, ifaceobj, op,
145 cenv=ifupdownobj.generate_running_env(ifaceobj, op)
f82758bf
RP
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
2c8c4ce7
RP
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
f82758bf
RP
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
2c8c4ce7
RP
218 for ifaceobj in ifaceobjs:
219 if not cls._check_upperifaces(ifupdownobj, ifaceobj,
220 ops, parent, followdependents):
f82758bf 221 return
2c8c4ce7
RP
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
f82758bf
RP
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
2c8c4ce7
RP
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
f82758bf
RP
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
2c8c4ce7
RP
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,
f82758bf 422 followdependents=True, skipupperifaces=False):
2c8c4ce7
RP
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 #
f82758bf 455 followupperifaces = False
2c8c4ce7
RP
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.
f82758bf
RP
467 if any([True for i in ifacenames
468 if ifupdownobj.must_follow_upperifaces(i)]):
469 followupperifaces = (True if
2c8c4ce7
RP
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
2c8c4ce7
RP
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
f82758bf
RP
512 if (not skipupperifaces and
513 ifupdownobj.config.get('skip_upperifaces', '0') == '0' and
2c8c4ce7
RP
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
f82758bf 522 cls.run_upperifaces(ifupdownobj, ifacenames, ops)
2c8c4ce7 523 cls._STATE_CHECK = True