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