3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
15 from ifupdown2
.ifupdown
.graph
import *
16 from ifupdown2
.ifupdown
.ifupdownbase
import *
18 from ifupdown2
.ifupdown
.iface
import *
19 from ifupdown2
.ifupdown
.utils
import utils
20 from ifupdown2
.ifupdown
.statemanager
import *
22 import ifupdown2
.ifupdown
.ifupdownflags
as ifupdownflags
24 from ifupdown
.graph
import *
25 from ifupdown
.ifupdownbase
import *
27 from ifupdown
.iface
import *
28 from ifupdown
.utils
import utils
29 from ifupdown
.statemanager
import *
31 import ifupdown
.ifupdownflags
as ifupdownflags
34 class ifaceSchedulerFlags():
35 """ Enumerates scheduler flags """
40 class ifaceScheduler():
41 """ scheduler functions to schedule configuration of interfaces.
43 supports scheduling of interfaces serially in plain interface list
44 or dependency graph format.
54 cls
._STATE
_CHECK
= True
55 cls
._SCHED
_STATUS
= True
58 def get_sched_status(cls
):
59 return cls
._SCHED
_STATUS
62 def set_sched_status(cls
, state
):
63 cls
._SCHED
_STATUS
= state
66 def run_iface_op(cls
, ifupdownobj
, ifaceobj
, op
, cenv
=None):
67 """ Runs sub operation on an interface """
68 ifacename
= ifaceobj
.name
70 if ifupdownobj
.type and ifupdownobj
.type != ifaceobj
.type:
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
),
83 for mname
in ifupdownobj
.module_ops
.get(op
):
84 m
= ifupdownobj
.modules
.get(mname
)
88 msg
= ('%s: %s : running module %s' %(ifacename
, op
, mname
))
89 if op
== 'query-checkcurr':
90 # Dont check curr if the interface object was
92 if (ifaceobj
.priv_flags
and
93 ifaceobj
.priv_flags
.NOCONFIG
):
95 ifupdownobj
.logger
.debug(msg
)
96 m
.run(ifaceobj
, op
, query_ifaceobj
,
97 ifaceobj_getfunc
=ifupdownobj
.get_ifaceobjs
)
99 ifupdownobj
.logger
.debug(msg
)
101 ifaceobj_getfunc
=ifupdownobj
.get_ifaceobjs
)
103 if not ifupdownobj
.ignore_error(str(e
)):
105 ifupdownobj
.logger
.error(str(e
))
106 # Continue with rest of the modules
109 if err
or ifaceobj
.status
== ifaceStatus
.ERROR
:
110 ifaceobj
.set_state_n_status(ifaceState
.from_str(op
),
112 if 'up' in op
or 'down' in op
or 'query-checkcurr' in op
:
113 cls
.set_sched_status(False)
115 # Mark success only if the interface was not already
117 status
= (ifaceobj
.status
118 if ifaceobj
.status
== ifaceStatus
.ERROR
119 else ifaceStatus
.SUCCESS
)
120 ifaceobj
.set_state_n_status(ifaceState
.from_str(op
),
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
))
133 utils
.exec_command(mname
, env
=cenv
)
135 ifupdownobj
.log_error('%s: %s %s' % (ifacename
, op
, str(e
)))
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
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
)
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')
157 for ifaceobj
in ifaceobjs
:
158 ifaceobj
.status
= ifaceStatus
.SUCCESS
159 posthookfunc(ifupdownobj
, ifaceobj
, 'down')
162 # first run ifupdownobj handlers. This is good enough
163 # for the first object in the list
164 handler
= ifupdownobj
.ops_handlers
.get(op
)
167 handler(ifupdownobj
, ifaceobjs
[0])
169 if not ifupdownobj
.link_master_slave_ignore_error(str(e
)):
170 ifupdownobj
.logger
.warn('%s: %s'
171 %(ifaceobjs
[0].name
, str(e
)))
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')
181 [posthookfunc(ifupdownobj
, ifaceobj
, ops
[0])
182 for ifaceobj
in ifaceobjs
]
184 ifupdownobj
.logger
.warn('%s' %str
(e
))
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
193 Returns True or False indicating the caller to proceed with the
196 # proceed only for down operation
197 if 'down' not in ops
[0]:
200 if (ifupdownobj
.flags
.SCHED_SKIP_CHECK_UPPERIFACES
):
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
)):
210 ulist
= ifaceobj
.upperifaces
214 # Get the list of upper ifaces other than the parent
215 tmpulist
= ([u
for u
in ulist
if u
!= parent
] if parent
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
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)
229 ifupdownobj
.logger
.warn('%s: skipping interface down,'
230 %ifaceobj
.name
+ ' upperiface %s still around ' %u)
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 """
240 # Each ifacename can have a list of iface objects
241 ifaceobjs
= ifupdownobj
.get_ifaceobjs(ifacename
)
243 raise Exception('%s: not found' %ifacename
)
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
)
251 for ifaceobj
in ifaceobjs
:
252 if not cls
._check
_upperifaces
(ifupdownobj
, ifaceobj
,
253 ops
, parent
, followdependents
):
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
)
260 for ifaceobj
in ifaceobjs
:
261 # Run lowerifaces or dependents
262 dlist
= ifaceobj
.lowerifaces
264 ifupdownobj
.logger
.debug('%s: found dependents %s'
265 %(ifacename
, str(dlist
)))
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
)]
276 cls
.run_iface_list(ifupdownobj
, new_dlist
, ops
,
277 ifacename
, order
, followdependents
,
278 continueonfailure
=False)
280 cls
.run_iface_list(ifupdownobj
, dlist
, ops
,
283 continueonfailure
=False)
285 if (ifupdownobj
.ignore_error(str(e
))):
288 # Dont bring the iface up if children did not come up
289 ifaceobj
.set_state_n_status(ifaceState
.NEW
,
292 if order
== ifaceSchedulerFlags
.POSTORDER
:
293 cls
.run_iface_list_ops(ifupdownobj
, ifaceobjs
, ops
)
296 def run_iface_list(cls
, ifupdownobj
, ifacenames
,
297 ops
, parent
=None, order
=ifaceSchedulerFlags
.POSTORDER
,
298 followdependents
=True, continueonfailure
=True):
299 """ Runs interface list """
301 for ifacename
in ifacenames
:
303 cls
.run_iface_graph(ifupdownobj
, ifacename
, ops
, parent
,
304 order
, followdependents
)
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
)))
312 if (ifupdownobj
.ignore_error(str(e
))):
315 raise Exception('%s : (%s)' %(ifacename
, str(e
)))
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 """
322 # Each ifacename can have a list of iface objects
323 ifaceobjs
= ifupdownobj
.get_ifaceobjs(ifacename
)
325 raise Exception('%s: not found' %ifacename
)
327 if (cls
._STATE
_CHECK
and
328 (ifaceobjs
[0].state
== ifaceState
.from_str(ops
[-1]))):
329 ifupdownobj
.logger
.debug('%s: already processed' %ifacename
)
333 # run the iface first and then its upperifaces
334 cls
.run_iface_list_ops(ifupdownobj
, ifaceobjs
, ops
)
335 for ifaceobj
in ifaceobjs
:
337 ulist
= ifaceobj
.upperifaces
339 ifupdownobj
.logger
.debug('%s: found upperifaces %s'
340 %(ifacename
, str(ulist
)))
342 cls
.run_iface_list_upper(ifupdownobj
, ulist
, ops
,
345 continueonfailure
=True)
347 if (ifupdownobj
.ignore_error(str(e
))):
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 """
358 for ifacename
in ifacenames
:
360 cls
.run_iface_graph_upper(ifupdownobj
, ifacename
, ops
, parent
,
361 followdependents
, skip_root
)
363 if ifupdownobj
.logger
.isEnabledFor(logging
.DEBUG
):
364 traceback
.print_tb(sys
.exc_info()[2])
365 ifupdownobj
.logger
.warn('%s : %s' %(ifacename
, str(e
)))
369 def _get_valid_upperifaces(cls
, ifupdownobj
, ifacenames
,
371 """ Recursively find valid upperifaces
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) """
383 for ifacename
in ifacenames
:
385 ifaceobj
= ifupdownobj
.get_ifaceobj_first(ifacename
)
388 ulist
= Set(ifaceobj
.upperifaces
).difference(upperifacenames
)
391 uifaceobj
= ifupdownobj
.get_ifaceobj_first(u
)
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
))):
405 upperifacenames
.extend(nulist
)
406 allupperifacenames
.extend(upperifacenames
)
408 cls
._get
_valid
_upperifaces
(ifupdownobj
, upperifacenames
,
413 def run_upperifaces(cls
, ifupdownobj
, ifacenames
, ops
,
414 continueonfailure
=True):
415 """ Run through valid upperifaces """
418 cls
._get
_valid
_upperifaces
(ifupdownobj
, ifacenames
, upperifaces
)
421 # dump valid upperifaces
422 ifupdownobj
.logger
.debug(upperifaces
)
423 for u
in upperifaces
:
425 ifaceobjs
= ifupdownobj
.get_ifaceobjs(u
)
428 cls
.run_iface_list_ops(ifupdownobj
, ifaceobjs
, ops
)
430 if continueonfailure
:
431 ifupdownobj
.logger
.warn('%s' %str
(e
))
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
)
441 iobj
.dump(ifupdownobj
.logger
)
442 if (dependency_graph
):
443 ifupdownobj
.logger
.info('\nDependency Graph:')
444 ifupdownobj
.logger
.info(dependency_graph
)
446 ifupdownobj
.logger
.info('\nIndegrees:')
447 ifupdownobj
.logger
.info(indegrees
)
448 ifupdownobj
.logger
.info('}\n')
451 def get_sorted_iface_list(cls
, ifupdownobj
, ifacenames
, ops
,
452 dependency_graph
, indegrees
=None):
453 if len(ifacenames
) == 1:
455 # Get a sorted list of all interfaces
457 indegrees
= OrderedDict()
458 for ifacename
in dependency_graph
.keys():
459 indegrees
[ifacename
] = ifupdownobj
.get_iface_refcnt(ifacename
)
461 #cls._dump_dependency_info(ifupdownobj, ifacenames,
462 # dependency_graph, indegrees)
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
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
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.
486 **ifupdownobj** (object): ifupdownMain object
488 **ifacenames** (list): list of interface names
490 **ops** : list of operations to perform eg ['pre-up', 'up', 'post-up']
492 **dependency_graph** (dict): dependency graph in adjacency list format
495 **indegrees** (dict): indegree array of the dependency graph
497 **order** (int): ifaceSchedulerFlags (POSTORDER, INORDER)
499 **followdependents** (bool): follow dependent interfaces if true
501 **sort** (bool): sort ifacelist in the case where ALL is not set
506 # if ALL/auto interfaces are specified,
507 # - walk the dependency tree in postorder or inorder depending
509 # (This is to run interfaces correctly in order)
511 # - sort iface list if the ifaces belong to a "class"
512 # - else just run iface list in the order they were specified
514 # Run any upperifaces if available
516 followupperifaces
= False
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
)
524 if not ifupdownflags
.flags
.ALL
:
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
)]
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]:
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
,
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
))
559 ifupdownobj
.logger
.warn('interface sort returned None')
561 # If queue not present, just run interfaces that were asked by the
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'
573 cls
.run_iface_list(ifupdownobj
, run_queue
, ops
,
574 parent
=None, order
=order
,
575 followdependents
=followdependents
)
576 if not cls
.get_sched_status():
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
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) ' +
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
)
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)