3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
10 from statemanager
import *
11 import ifupdown
.ifupdownflags
as ifupdownflags
14 from collections
import deque
15 from collections
import OrderedDict
20 from collections
import deque
21 from threading
import *
22 from ifupdownbase
import *
23 from ifupdown
.utils
import utils
26 class ifaceSchedulerFlags():
27 """ Enumerates scheduler flags """
32 class ifaceScheduler():
33 """ scheduler functions to schedule configuration of interfaces.
35 supports scheduling of interfaces serially in plain interface list
36 or dependency graph format.
45 def get_sched_status(cls
):
46 return cls
._SCHED
_STATUS
49 def set_sched_status(cls
, state
):
50 cls
._SCHED
_STATUS
= state
53 def run_iface_op(cls
, ifupdownobj
, ifaceobj
, op
, cenv
=None):
54 """ Runs sub operation on an interface """
55 ifacename
= ifaceobj
.name
57 if ifupdownobj
.type and ifupdownobj
.type != ifaceobj
.type:
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
),
70 for mname
in ifupdownobj
.module_ops
.get(op
):
71 m
= ifupdownobj
.modules
.get(mname
)
75 msg
= ('%s: %s : running module %s' %(ifacename
, op
, mname
))
76 if op
== 'query-checkcurr':
77 # Dont check curr if the interface object was
79 if (ifaceobj
.priv_flags
and
80 ifaceobj
.priv_flags
.NOCONFIG
):
82 ifupdownobj
.logger
.debug(msg
)
83 m
.run(ifaceobj
, op
, query_ifaceobj
,
84 ifaceobj_getfunc
=ifupdownobj
.get_ifaceobjs
)
86 ifupdownobj
.logger
.debug(msg
)
88 ifaceobj_getfunc
=ifupdownobj
.get_ifaceobjs
)
90 if not ifupdownobj
.ignore_error(str(e
)):
92 ifupdownobj
.logger
.error(str(e
))
93 # Continue with rest of the modules
96 if err
or ifaceobj
.status
== ifaceStatus
.ERROR
:
97 ifaceobj
.set_state_n_status(ifaceState
.from_str(op
),
99 if 'up' in op
or 'down' in op
or 'query-checkcurr' in op
:
100 cls
.set_sched_status(False)
102 # Mark success only if the interface was not already
104 status
= (ifaceobj
.status
105 if ifaceobj
.status
== ifaceStatus
.ERROR
106 else ifaceStatus
.SUCCESS
)
107 ifaceobj
.set_state_n_status(ifaceState
.from_str(op
),
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
))
120 utils
.exec_command(mname
, env
=cenv
)
122 ifupdownobj
.log_error('%s: %s %s' % (ifacename
, op
, str(e
)))
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
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')
142 for ifaceobj
in ifaceobjs
:
143 ifaceobj
.status
= ifaceStatus
.SUCCESS
144 posthookfunc(ifupdownobj
, ifaceobj
, 'down')
147 # first run ifupdownobj handlers. This is good enough
148 # for the first object in the list
149 handler
= ifupdownobj
.ops_handlers
.get(op
)
152 handler(ifupdownobj
, ifaceobjs
[0])
154 if not ifupdownobj
.link_master_slave_ignore_error(str(e
)):
155 ifupdownobj
.logger
.warn('%s: %s'
156 %(ifaceobjs
[0].name
, str(e
)))
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')
166 [posthookfunc(ifupdownobj
, ifaceobj
, ops
[0])
167 for ifaceobj
in ifaceobjs
]
169 ifupdownobj
.logger
.warn('%s' %str
(e
))
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
178 Returns True or False indicating the caller to proceed with the
181 # proceed only for down operation
182 if 'down' not in ops
[0]:
185 if (ifupdownobj
.flags
.SCHED_SKIP_CHECK_UPPERIFACES
):
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
)):
195 ulist
= ifaceobj
.upperifaces
199 # Get the list of upper ifaces other than the parent
200 tmpulist
= ([u
for u
in ulist
if u
!= parent
] if parent
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
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)
214 ifupdownobj
.logger
.warn('%s: skipping interface down,'
215 %ifaceobj
.name
+ ' upperiface %s still around ' %u)
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 """
225 # Each ifacename can have a list of iface objects
226 ifaceobjs
= ifupdownobj
.get_ifaceobjs(ifacename
)
228 raise Exception('%s: not found' %ifacename
)
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
)
236 for ifaceobj
in ifaceobjs
:
237 if not cls
._check
_upperifaces
(ifupdownobj
, ifaceobj
,
238 ops
, parent
, followdependents
):
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
)
245 for ifaceobj
in ifaceobjs
:
246 # Run lowerifaces or dependents
247 dlist
= ifaceobj
.lowerifaces
249 ifupdownobj
.logger
.debug('%s: found dependents %s'
250 %(ifacename
, str(dlist
)))
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
)]
261 cls
.run_iface_list(ifupdownobj
, new_dlist
, ops
,
262 ifacename
, order
, followdependents
,
263 continueonfailure
=False)
265 cls
.run_iface_list(ifupdownobj
, dlist
, ops
,
268 continueonfailure
=False)
270 if (ifupdownobj
.ignore_error(str(e
))):
273 # Dont bring the iface up if children did not come up
274 ifaceobj
.set_state_n_status(ifaceState
.NEW
,
277 if order
== ifaceSchedulerFlags
.POSTORDER
:
278 cls
.run_iface_list_ops(ifupdownobj
, ifaceobjs
, ops
)
281 def run_iface_list(cls
, ifupdownobj
, ifacenames
,
282 ops
, parent
=None, order
=ifaceSchedulerFlags
.POSTORDER
,
283 followdependents
=True, continueonfailure
=True):
284 """ Runs interface list """
286 for ifacename
in ifacenames
:
288 cls
.run_iface_graph(ifupdownobj
, ifacename
, ops
, parent
,
289 order
, followdependents
)
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
)))
297 if (ifupdownobj
.ignore_error(str(e
))):
300 raise Exception('%s : (%s)' %(ifacename
, str(e
)))
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 """
307 # Each ifacename can have a list of iface objects
308 ifaceobjs
= ifupdownobj
.get_ifaceobjs(ifacename
)
310 raise Exception('%s: not found' %ifacename
)
312 if (cls
._STATE
_CHECK
and
313 (ifaceobjs
[0].state
== ifaceState
.from_str(ops
[-1]))):
314 ifupdownobj
.logger
.debug('%s: already processed' %ifacename
)
318 # run the iface first and then its upperifaces
319 cls
.run_iface_list_ops(ifupdownobj
, ifaceobjs
, ops
)
320 for ifaceobj
in ifaceobjs
:
322 ulist
= ifaceobj
.upperifaces
324 ifupdownobj
.logger
.debug('%s: found upperifaces %s'
325 %(ifacename
, str(ulist
)))
327 cls
.run_iface_list_upper(ifupdownobj
, ulist
, ops
,
330 continueonfailure
=True)
332 if (ifupdownobj
.ignore_error(str(e
))):
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 """
343 for ifacename
in ifacenames
:
345 cls
.run_iface_graph_upper(ifupdownobj
, ifacename
, ops
, parent
,
346 followdependents
, skip_root
)
348 if ifupdownobj
.logger
.isEnabledFor(logging
.DEBUG
):
349 traceback
.print_tb(sys
.exc_info()[2])
350 ifupdownobj
.logger
.warn('%s : %s' %(ifacename
, str(e
)))
354 def _get_valid_upperifaces(cls
, ifupdownobj
, ifacenames
,
356 """ Recursively find valid upperifaces
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) """
368 for ifacename
in ifacenames
:
370 ifaceobj
= ifupdownobj
.get_ifaceobj_first(ifacename
)
373 ulist
= Set(ifaceobj
.upperifaces
).difference(upperifacenames
)
376 uifaceobj
= ifupdownobj
.get_ifaceobj_first(u
)
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
))):
390 upperifacenames
.extend(nulist
)
391 allupperifacenames
.extend(upperifacenames
)
393 cls
._get
_valid
_upperifaces
(ifupdownobj
, upperifacenames
,
398 def run_upperifaces(cls
, ifupdownobj
, ifacenames
, ops
,
399 continueonfailure
=True):
400 """ Run through valid upperifaces """
403 cls
._get
_valid
_upperifaces
(ifupdownobj
, ifacenames
, upperifaces
)
406 # dump valid upperifaces
407 ifupdownobj
.logger
.debug(upperifaces
)
408 for u
in upperifaces
:
410 ifaceobjs
= ifupdownobj
.get_ifaceobjs(u
)
413 cls
.run_iface_list_ops(ifupdownobj
, ifaceobjs
, ops
)
415 if continueonfailure
:
416 ifupdownobj
.logger
.warn('%s' %str
(e
))
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
)
426 iobj
.dump(ifupdownobj
.logger
)
427 if (dependency_graph
):
428 ifupdownobj
.logger
.info('\nDependency Graph:')
429 ifupdownobj
.logger
.info(dependency_graph
)
431 ifupdownobj
.logger
.info('\nIndegrees:')
432 ifupdownobj
.logger
.info(indegrees
)
433 ifupdownobj
.logger
.info('}\n')
436 def get_sorted_iface_list(cls
, ifupdownobj
, ifacenames
, ops
,
437 dependency_graph
, indegrees
=None):
438 if len(ifacenames
) == 1:
440 # Get a sorted list of all interfaces
442 indegrees
= OrderedDict()
443 for ifacename
in dependency_graph
.keys():
444 indegrees
[ifacename
] = ifupdownobj
.get_iface_refcnt(ifacename
)
446 #cls._dump_dependency_info(ifupdownobj, ifacenames,
447 # dependency_graph, indegrees)
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
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
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.
471 **ifupdownobj** (object): ifupdownMain object
473 **ifacenames** (list): list of interface names
475 **ops** : list of operations to perform eg ['pre-up', 'up', 'post-up']
477 **dependency_graph** (dict): dependency graph in adjacency list format
480 **indegrees** (dict): indegree array of the dependency graph
482 **order** (int): ifaceSchedulerFlags (POSTORDER, INORDER)
484 **followdependents** (bool): follow dependent interfaces if true
486 **sort** (bool): sort ifacelist in the case where ALL is not set
491 # if ALL/auto interfaces are specified,
492 # - walk the dependency tree in postorder or inorder depending
494 # (This is to run interfaces correctly in order)
496 # - sort iface list if the ifaces belong to a "class"
497 # - else just run iface list in the order they were specified
499 # Run any upperifaces if available
501 followupperifaces
= False
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
)
509 if not ifupdownflags
.flags
.ALL
:
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
)]
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]:
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
,
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
))
544 ifupdownobj
.logger
.warn('interface sort returned None')
546 # If queue not present, just run interfaces that were asked by the
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'
558 cls
.run_iface_list(ifupdownobj
, run_queue
, ops
,
559 parent
=None, order
=order
,
560 followdependents
=followdependents
)
561 if not cls
.get_sched_status():
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
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) ' +
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
)
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)