]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdown/scheduler.py
ifupdown2 2.0.0 release
[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 if ('down' in ops[0] and
148 ifaceobjs[0].type != ifaceType.BRIDGE_VLAN and
149 not ifupdownobj.link_exists(ifacename)):
150 ifupdownobj.logger.debug('%s: does not exist' %ifacename)
151 # run posthook before you get out of here, so that
152 # appropriate cleanup is done
153 posthookfunc = ifupdownobj.sched_hooks.get('posthook')
154 if posthookfunc:
155 for ifaceobj in ifaceobjs:
156 ifaceobj.status = ifaceStatus.SUCCESS
157 posthookfunc(ifupdownobj, ifaceobj, 'down')
158 return
159 for op in ops:
160 # first run ifupdownobj handlers. This is good enough
161 # for the first object in the list
162 handler = ifupdownobj.ops_handlers.get(op)
163 if handler:
164 try:
165 handler(ifupdownobj, ifaceobjs[0])
166 except Exception, e:
167 if not ifupdownobj.link_master_slave_ignore_error(str(e)):
168 ifupdownobj.logger.warn('%s: %s'
169 %(ifaceobjs[0].name, str(e)))
170 pass
171 for ifaceobj in ifaceobjs:
172 cls.run_iface_op(ifupdownobj, ifaceobj, op,
173 cenv=ifupdownobj.generate_running_env(ifaceobj, op)
174 if ifupdownobj.config.get('addon_scripts_support',
175 '0') == '1' else None)
176 posthookfunc = ifupdownobj.sched_hooks.get('posthook')
177 if posthookfunc:
178 try:
179 [posthookfunc(ifupdownobj, ifaceobj, ops[0])
180 for ifaceobj in ifaceobjs]
181 except Exception, e:
182 ifupdownobj.logger.warn('%s' %str(e))
183 pass
184
185 @classmethod
186 def _check_upperifaces(cls, ifupdownobj, ifaceobj, ops, parent,
187 followdependents=False):
188 """ Check if upperifaces are hanging off us and help caller decide
189 if he can proceed with the ops on this device
190
191 Returns True or False indicating the caller to proceed with the
192 operation.
193 """
194 # proceed only for down operation
195 if 'down' not in ops[0]:
196 return True
197
198 if (ifupdownobj.flags.SCHED_SKIP_CHECK_UPPERIFACES):
199 return True
200
201 if (ifupdownflags.flags.FORCE or
202 not ifupdownobj.flags.ADDONS_ENABLE or
203 (not ifupdownobj.is_ifaceobj_noconfig(ifaceobj) and
204 ifupdownobj.config.get('warn_on_ifdown', '0') == '0' and
205 not ifupdownflags.flags.ALL)):
206 return True
207
208 ulist = ifaceobj.upperifaces
209 if not ulist:
210 return True
211
212 # Get the list of upper ifaces other than the parent
213 tmpulist = ([u for u in ulist if u != parent] if parent
214 else ulist)
215 if not tmpulist:
216 return True
217 # XXX: This is expensive. Find a cheaper way to do this.
218 # if any of the upperdevs are present,
219 # return false to the caller to skip this interface
220 for u in tmpulist:
221 if ifupdownobj.link_exists(u):
222 if not ifupdownflags.flags.ALL:
223 if ifupdownobj.is_ifaceobj_noconfig(ifaceobj):
224 ifupdownobj.logger.info('%s: skipping interface down,'
225 %ifaceobj.name + ' upperiface %s still around ' %u)
226 else:
227 ifupdownobj.logger.warn('%s: skipping interface down,'
228 %ifaceobj.name + ' upperiface %s still around ' %u)
229 return False
230 return True
231
232 @classmethod
233 def run_iface_graph(cls, ifupdownobj, ifacename, ops, parent=None,
234 order=ifaceSchedulerFlags.POSTORDER,
235 followdependents=True):
236 """ runs interface by traversing all nodes rooted at itself """
237
238 # Each ifacename can have a list of iface objects
239 ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
240 if not ifaceobjs:
241 raise Exception('%s: not found' %ifacename)
242
243 # Check state of the dependent. If it is already brought up, return
244 if (cls._STATE_CHECK and
245 (ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
246 ifupdownobj.logger.debug('%s: already processed' %ifacename)
247 return
248
249 for ifaceobj in ifaceobjs:
250 if not cls._check_upperifaces(ifupdownobj, ifaceobj,
251 ops, parent, followdependents):
252 return
253
254 # If inorder, run the iface first and then its dependents
255 if order == ifaceSchedulerFlags.INORDER:
256 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
257
258 for ifaceobj in ifaceobjs:
259 # Run lowerifaces or dependents
260 dlist = ifaceobj.lowerifaces
261 if dlist:
262 ifupdownobj.logger.debug('%s: found dependents %s'
263 %(ifacename, str(dlist)))
264 try:
265 if not followdependents:
266 # XXX: this is yet another extra step,
267 # but is needed for interfaces that are
268 # implicit dependents. even though we are asked to
269 # not follow dependents, we must follow the ones
270 # that dont have user given config. Because we own them
271 new_dlist = [d for d in dlist
272 if ifupdownobj.is_iface_noconfig(d)]
273 if new_dlist:
274 cls.run_iface_list(ifupdownobj, new_dlist, ops,
275 ifacename, order, followdependents,
276 continueonfailure=False)
277 else:
278 cls.run_iface_list(ifupdownobj, dlist, ops,
279 ifacename, order,
280 followdependents,
281 continueonfailure=False)
282 except Exception, e:
283 if (ifupdownobj.ignore_error(str(e))):
284 pass
285 else:
286 # Dont bring the iface up if children did not come up
287 ifaceobj.set_state_n_status(ifaceState.NEW,
288 ifaceStatus.ERROR)
289 raise
290 if order == ifaceSchedulerFlags.POSTORDER:
291 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
292
293 @classmethod
294 def run_iface_list(cls, ifupdownobj, ifacenames,
295 ops, parent=None, order=ifaceSchedulerFlags.POSTORDER,
296 followdependents=True, continueonfailure=True):
297 """ Runs interface list """
298
299 for ifacename in ifacenames:
300 try:
301 cls.run_iface_graph(ifupdownobj, ifacename, ops, parent,
302 order, followdependents)
303 except Exception, e:
304 if continueonfailure:
305 if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
306 traceback.print_tb(sys.exc_info()[2])
307 ifupdownobj.logger.error('%s : %s' %(ifacename, str(e)))
308 pass
309 else:
310 if (ifupdownobj.ignore_error(str(e))):
311 pass
312 else:
313 raise Exception('%s : (%s)' %(ifacename, str(e)))
314
315 @classmethod
316 def run_iface_graph_upper(cls, ifupdownobj, ifacename, ops, parent=None,
317 followdependents=True, skip_root=False):
318 """ runs interface by traversing all nodes rooted at itself """
319
320 # Each ifacename can have a list of iface objects
321 ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
322 if not ifaceobjs:
323 raise Exception('%s: not found' %ifacename)
324
325 if (cls._STATE_CHECK and
326 (ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
327 ifupdownobj.logger.debug('%s: already processed' %ifacename)
328 return
329
330 if not skip_root:
331 # run the iface first and then its upperifaces
332 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
333 for ifaceobj in ifaceobjs:
334 # Run upperifaces
335 ulist = ifaceobj.upperifaces
336 if ulist:
337 ifupdownobj.logger.debug('%s: found upperifaces %s'
338 %(ifacename, str(ulist)))
339 try:
340 cls.run_iface_list_upper(ifupdownobj, ulist, ops,
341 ifacename,
342 followdependents,
343 continueonfailure=True)
344 except Exception, e:
345 if (ifupdownobj.ignore_error(str(e))):
346 pass
347 else:
348 raise
349
350 @classmethod
351 def run_iface_list_upper(cls, ifupdownobj, ifacenames,
352 ops, parent=None, followdependents=True,
353 continueonfailure=True, skip_root=False):
354 """ Runs interface list """
355
356 for ifacename in ifacenames:
357 try:
358 cls.run_iface_graph_upper(ifupdownobj, ifacename, ops, parent,
359 followdependents, skip_root)
360 except Exception, e:
361 if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
362 traceback.print_tb(sys.exc_info()[2])
363 ifupdownobj.logger.warn('%s : %s' %(ifacename, str(e)))
364 pass
365
366 @classmethod
367 def _get_valid_upperifaces(cls, ifupdownobj, ifacenames,
368 allupperifacenames):
369 """ Recursively find valid upperifaces
370
371 valid upperifaces are:
372 - An upperiface which had no user config (example builtin
373 interfaces. usually vlan interfaces.)
374 - or had config and previously up
375 - and interface currently does not exist
376 - or is a bridge (because if your upperiface was a bridge
377 - u will have to execute up on the bridge
378 to enslave the port and apply bridge attributes to the port) """
379
380 upperifacenames = []
381 for ifacename in ifacenames:
382 # get upperifaces
383 ifaceobj = ifupdownobj.get_ifaceobj_first(ifacename)
384 if not ifaceobj:
385 continue
386 ulist = Set(ifaceobj.upperifaces).difference(upperifacenames)
387 nulist = []
388 for u in ulist:
389 uifaceobj = ifupdownobj.get_ifaceobj_first(u)
390 if not uifaceobj:
391 continue
392 has_config = not (uifaceobj.priv_flags and
393 uifaceobj.priv_flags.NOCONFIG)
394 if (((has_config and ifupdownobj.get_ifaceobjs_saved(u)) or
395 not has_config) and (not ifupdownobj.link_exists(u)
396 # Do this always for a bridge. Note that this is
397 # not done for a vlan aware bridge because,
398 # in the vlan aware bridge case, the bridge module
399 # applies the bridge port configuration on the port
400 # when up is scheduled on the port.
401 or (uifaceobj.link_kind == ifaceLinkKind.BRIDGE))):
402 nulist.append(u)
403 upperifacenames.extend(nulist)
404 allupperifacenames.extend(upperifacenames)
405 if upperifacenames:
406 cls._get_valid_upperifaces(ifupdownobj, upperifacenames,
407 allupperifacenames)
408 return
409
410 @classmethod
411 def run_upperifaces(cls, ifupdownobj, ifacenames, ops,
412 continueonfailure=True):
413 """ Run through valid upperifaces """
414 upperifaces = []
415
416 cls._get_valid_upperifaces(ifupdownobj, ifacenames, upperifaces)
417 if not upperifaces:
418 return
419 # dump valid upperifaces
420 ifupdownobj.logger.debug(upperifaces)
421 for u in upperifaces:
422 try:
423 ifaceobjs = ifupdownobj.get_ifaceobjs(u)
424 if not ifaceobjs:
425 continue
426 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
427 except Exception, e:
428 if continueonfailure:
429 ifupdownobj.logger.warn('%s' %str(e))
430
431 @classmethod
432 def _dump_dependency_info(cls, ifupdownobj, ifacenames,
433 dependency_graph=None, indegrees=None):
434 ifupdownobj.logger.info('{\n')
435 ifupdownobj.logger.info('\nifaceobjs:')
436 for iname in ifacenames:
437 iobjs = ifupdownobj.get_ifaceobjs(iname)
438 for iobj in iobjs:
439 iobj.dump(ifupdownobj.logger)
440 if (dependency_graph):
441 ifupdownobj.logger.info('\nDependency Graph:')
442 ifupdownobj.logger.info(dependency_graph)
443 if (indegrees):
444 ifupdownobj.logger.info('\nIndegrees:')
445 ifupdownobj.logger.info(indegrees)
446 ifupdownobj.logger.info('}\n')
447
448 @classmethod
449 def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops,
450 dependency_graph, indegrees=None):
451 if len(ifacenames) == 1:
452 return ifacenames
453 # Get a sorted list of all interfaces
454 if not indegrees:
455 indegrees = OrderedDict()
456 for ifacename in dependency_graph.keys():
457 indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
458
459 #cls._dump_dependency_info(ifupdownobj, ifacenames,
460 # dependency_graph, indegrees)
461
462 ifacenames_all_sorted = graph.topological_sort_graphs_all(
463 dependency_graph, indegrees)
464 # if ALL was set, return all interfaces
465 if ifupdownflags.flags.ALL:
466 return ifacenames_all_sorted
467
468 # else return ifacenames passed as argument in sorted order
469 ifacenames_sorted = []
470 [ifacenames_sorted.append(ifacename)
471 for ifacename in ifacenames_all_sorted
472 if ifacename in ifacenames]
473 return ifacenames_sorted
474
475 @classmethod
476 def sched_ifaces(cls, ifupdownobj, ifacenames, ops,
477 dependency_graph=None, indegrees=None,
478 order=ifaceSchedulerFlags.POSTORDER,
479 followdependents=True, skipupperifaces=False, sort=False):
480 """ runs interface configuration modules on interfaces passed as
481 argument. Runs topological sort on interface dependency graph.
482
483 Args:
484 **ifupdownobj** (object): ifupdownMain object
485
486 **ifacenames** (list): list of interface names
487
488 **ops** : list of operations to perform eg ['pre-up', 'up', 'post-up']
489
490 **dependency_graph** (dict): dependency graph in adjacency list format
491
492 Kwargs:
493 **indegrees** (dict): indegree array of the dependency graph
494
495 **order** (int): ifaceSchedulerFlags (POSTORDER, INORDER)
496
497 **followdependents** (bool): follow dependent interfaces if true
498
499 **sort** (bool): sort ifacelist in the case where ALL is not set
500
501 """
502 #
503 # Algo:
504 # if ALL/auto interfaces are specified,
505 # - walk the dependency tree in postorder or inorder depending
506 # on the operation.
507 # (This is to run interfaces correctly in order)
508 # else:
509 # - sort iface list if the ifaces belong to a "class"
510 # - else just run iface list in the order they were specified
511 #
512 # Run any upperifaces if available
513 #
514 followupperifaces = False
515 run_queue = []
516 skip_ifacesort = int(ifupdownobj.config.get('skip_ifacesort', '0'))
517 if not skip_ifacesort and not indegrees:
518 indegrees = OrderedDict()
519 for ifacename in dependency_graph.keys():
520 indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
521
522 if not ifupdownflags.flags.ALL:
523 if 'up' in ops[0]:
524 # If there is any interface that does not exist, maybe it
525 # is a logical interface and we have to followupperifaces
526 # when it comes up, so lets get that list.
527 if any([True for i in ifacenames
528 if ifupdownobj.must_follow_upperifaces(i)]):
529 followupperifaces = (True if
530 [i for i in ifacenames
531 if not ifupdownobj.link_exists(i)]
532 else False)
533 # sort interfaces only if the caller asked to sort
534 # and skip_ifacesort is not on.
535 if not skip_ifacesort and sort:
536 run_queue = cls.get_sorted_iface_list(ifupdownobj, ifacenames,
537 ops, dependency_graph, indegrees)
538 if run_queue and 'up' in ops[0]:
539 run_queue.reverse()
540 else:
541 # if -a is set, we pick the interfaces
542 # that have no parents and use a sorted list of those
543 if not skip_ifacesort:
544 sorted_ifacenames = cls.get_sorted_iface_list(ifupdownobj,
545 ifacenames, ops, dependency_graph,
546 indegrees)
547 if sorted_ifacenames:
548 # pick interfaces that user asked
549 # and those that dont have any dependents first
550 [run_queue.append(ifacename)
551 for ifacename in sorted_ifacenames
552 if ifacename in ifacenames and
553 not indegrees.get(ifacename)]
554 ifupdownobj.logger.debug('graph roots (interfaces that ' +
555 'dont have dependents):' + ' %s' %str(run_queue))
556 else:
557 ifupdownobj.logger.warn('interface sort returned None')
558
559 # If queue not present, just run interfaces that were asked by the
560 # user
561 if not run_queue:
562 run_queue = list(ifacenames)
563 # if we are taking the order of interfaces as specified
564 # in the interfaces file, we should reverse the list if we
565 # want to down. This can happen if 'skip_ifacesort'
566 # is been specified.
567 if 'down' in ops[0]:
568 run_queue.reverse()
569
570 # run interface list
571 cls.run_iface_list(ifupdownobj, run_queue, ops,
572 parent=None, order=order,
573 followdependents=followdependents)
574 if not cls.get_sched_status():
575 return
576
577 if (not skipupperifaces and
578 ifupdownobj.config.get('skip_upperifaces', '0') == '0' and
579 ((not ifupdownflags.flags.ALL and followdependents) or
580 followupperifaces) and
581 'up' in ops[0]):
582 # If user had given a set of interfaces to bring up
583 # try and execute 'up' on the upperifaces
584 ifupdownobj.logger.info('running upperifaces (parent interfaces) ' +
585 'if available ..')
586 try:
587 # upperiface bring up is best effort.
588 # eg case: if we are bringing up a bridge port
589 # this section does an 'ifup on the bridge'
590 # so that the recently up'ed bridge port gets enslaved
591 # to the bridge. But the up on the bridge may
592 # throw out more errors if the bridge is not
593 # in the correct state. Lets not surprise
594 # the user with such errors when he has
595 # only requested to bring up the bridge port.
596 cls._STATE_CHECK = False
597 ifupdownflags.flags.IGNORE_ERRORS = True
598 cls.run_upperifaces(ifupdownobj, ifacenames, ops)
599 finally:
600 ifupdownflags.flags.IGNORE_ERRORS = False
601 cls._STATE_CHECK = True
602 # upperiface bringup is best effort, so dont propagate errors
603 # reset scheduler status to True
604 cls.set_sched_status(True)