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