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