]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown/scheduler.py
First cut ifupdown2 module for vrrpd
[mirror_ifupdown2.git] / ifupdown / scheduler.py
CommitLineData
a6f80f0e 1#!/usr/bin/python
3e8ee54f 2#
904908bc 3# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
3e8ee54f 4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6# ifaceScheduler --
7# interface scheduler
8#
a6f80f0e 9
a6f80f0e 10from statemanager import *
11from iface import *
12from graph import *
13from collections import deque
14from collections import OrderedDict
a6f80f0e 15import logging
d08d5f54 16import traceback
69f58278 17import sys
a6f80f0e 18from graph import *
19from collections import deque
20from threading import *
21from ifupdownbase import *
22
d08d5f54 23class ifaceSchedulerFlags():
904908bc
RP
24 """ Enumerates scheduler flags """
25
c798b0f4 26 INORDER = 0x1
27 POSTORDER = 0x2
d08d5f54 28
be0b20f2 29class ifaceScheduler():
30 """ scheduler functions to schedule configuration of interfaces.
a6f80f0e 31
a6f80f0e 32 supports scheduling of interfaces serially in plain interface list
33 or dependency graph format.
99b212b0 34
a6f80f0e 35 """
36
c798b0f4 37 _STATE_CHECK = True
38
5973036b
RP
39 _SCHED_RETVAL = True
40
be0b20f2 41 @classmethod
44a6ca06 42 def run_iface_op(cls, ifupdownobj, ifaceobj, op, cenv=None):
a6f80f0e 43 """ Runs sub operation on an interface """
62ddec8b 44 ifacename = ifaceobj.name
a6f80f0e 45
c798b0f4 46 if (cls._STATE_CHECK and
5973036b 47 (ifaceobj.state >= ifaceState.from_str(op))):
be0b20f2 48 ifupdownobj.logger.debug('%s: already in state %s' %(ifacename, op))
d08d5f54 49 return
20dd6242 50 if not ifupdownobj.ADDONS_ENABLE: return
44a6ca06
RP
51 if op == 'query-checkcurr':
52 query_ifaceobj=ifupdownobj.create_n_save_ifaceobjcurr(ifaceobj)
be0b20f2 53 for mname in ifupdownobj.module_ops.get(op):
37c0543d 54 m = ifupdownobj.modules.get(mname)
a6f80f0e 55 err = 0
56 try:
d08d5f54 57 if hasattr(m, 'run'):
a690dfae 58 msg = ('%s: %s : running module %s' %(ifacename, op, mname))
739f665b 59 if op == 'query-checkcurr':
d08d5f54 60 # Dont check curr if the interface object was
37c0543d 61 # auto generated
d08d5f54 62 if (ifaceobj.priv_flags & ifupdownobj.NOCONFIG):
37c0543d 63 continue
a690dfae 64 ifupdownobj.logger.debug(msg)
e1601369
RP
65 m.run(ifaceobj, op, query_ifaceobj,
66 ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
a6f80f0e 67 else:
a690dfae 68 ifupdownobj.logger.debug(msg)
84ca006f 69 m.run(ifaceobj, op, ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
a6f80f0e 70 except Exception, e:
71 err = 1
be0b20f2 72 ifupdownobj.log_error(str(e))
699c1cff
RP
73 err = 0 # error can be ignored by log_error, in which case
74 # reset err flag
a6f80f0e 75 finally:
ca105861 76 if err or ifaceobj.status == ifaceStatus.ERROR:
31a5f4c3 77 ifaceobj.set_state_n_status(ifaceState.from_str(op),
78 ifaceStatus.ERROR)
5973036b
RP
79 if 'up' in op or 'down' in op:
80 cls._SCHED_RETVAL = False
d08d5f54 81 else:
31a5f4c3 82 ifaceobj.set_state_n_status(ifaceState.from_str(op),
83 ifaceStatus.SUCCESS)
6bd7fc74 84
ee3fcf44 85 if ifupdownobj.config.get('addon_scripts_support', '0') == '1':
6bd7fc74 86 # execute /etc/network/ scripts
87 for mname in ifupdownobj.script_ops.get(op, []):
88 ifupdownobj.logger.debug('%s: %s : running script %s'
d08d5f54 89 %(ifacename, op, mname))
6bd7fc74 90 try:
91 ifupdownobj.exec_command(mname, cmdenv=cenv)
92 except Exception, e:
93 ifupdownobj.log_error(str(e))
37c0543d 94
be0b20f2 95 @classmethod
923290bd 96 def run_iface_list_ops(cls, ifupdownobj, ifaceobjs, ops):
97 """ Runs all operations on a list of interface
98 configurations for the same interface
99 """
a690dfae 100 # minor optimization. If operation is 'down', proceed only
101 # if interface exists in the system
923290bd 102 ifacename = ifaceobjs[0].name
8c13865c 103 ifupdownobj.logger.info('%s: running ops ...' %ifacename)
41febf89 104 if ('down' in ops[0] and ifaceobjs[0].type != ifaceType.BRIDGE_VLAN and
923290bd 105 not ifupdownobj.link_exists(ifacename)):
525f0a30 106 ifupdownobj.logger.debug('%s: does not exist' %ifacename)
923290bd 107 # run posthook before you get out of here, so that
108 # appropriate cleanup is done
109 posthookfunc = ifupdownobj.sched_hooks.get('posthook')
110 if posthookfunc:
111 for ifaceobj in ifaceobjs:
112 ifaceobj.status = ifaceStatus.SUCCESS
113 posthookfunc(ifupdownobj, ifaceobj, 'down')
a690dfae 114 return
923290bd 115 for op in ops:
116 # first run ifupdownobj handlers. This is good enough
117 # for the first object in the list
118 handler = ifupdownobj.ops_handlers.get(op)
119 if handler:
120 if (not ifaceobjs[0].addr_method or
121 (ifaceobjs[0].addr_method and
122 ifaceobjs[0].addr_method != 'manual')):
123 handler(ifupdownobj, ifaceobjs[0])
124 for ifaceobj in ifaceobjs:
125 cls.run_iface_op(ifupdownobj, ifaceobj, op,
923290bd 126 cenv=ifupdownobj.generate_running_env(ifaceobj, op)
ee3fcf44
RP
127 if ifupdownobj.config.get('addon_scripts_support',
128 '0') == '1' else None)
129 posthookfunc = ifupdownobj.sched_hooks.get('posthook')
130 if posthookfunc:
131 [posthookfunc(ifupdownobj, ifaceobj, ops[0])
132 for ifaceobj in ifaceobjs]
21c7daa7 133
134 @classmethod
fa3da4be 135 def _check_upperifaces(cls, ifupdownobj, ifaceobj, ops, parent,
136 followdependents=False):
99b212b0 137 """ Check if upperifaces are hanging off us and help caller decide
138 if he can proceed with the ops on this device
21c7daa7 139
99b212b0 140 Returns True or False indicating the caller to proceed with the
141 operation.
142 """
86fc62e2 143 # proceed only for down operation
144 if 'down' not in ops[0]:
145 return True
146
65c48517 147 if (ifupdownobj.FORCE or
148 not ifupdownobj.ADDONS_ENABLE or
86fc62e2 149 (not ifupdownobj.is_ifaceobj_noconfig(ifaceobj) and
33e106da
RP
150 ifupdownobj.config.get('warn_on_ifdown', '0') == '0' and
151 not ifupdownobj.ALL)):
99b212b0 152 return True
65c48517 153
c2d55ff5
RP
154 if ifaceobj.type == ifaceType.BRIDGE:
155 #
156 # XXX: This function's job is to return True for
157 # logical devices when any of the upperifaces are still around.
158 # In the new model, where bridge is represented as a dependency
159 # for a bridge port, bridge becomes a lowerdevice of a bridge port,
160 # which is not really true. To handle this case, add a special
161 # check for bridge device. Will figure out a better way to handle
162 # this.
163 # Long term this function should be replaced by
164 # walking the sysfs links to upper and lower device available in
165 # latest kernels.
166 try:
167 if os.listdir('/sys/class/net/%s/brif' %ifaceobj.name):
168 return False
169 else:
170 return True
171 except Exception:
172 pass
173 return True
174
62ddec8b 175 ulist = ifaceobj.upperifaces
99b212b0 176 if not ulist:
177 return True
33e106da 178
99b212b0 179 # Get the list of upper ifaces other than the parent
180 tmpulist = ([u for u in ulist if u != parent] if parent
181 else ulist)
182 if not tmpulist:
183 return True
184 # XXX: This is expensive. Find a cheaper way to do this.
185 # if any of the upperdevs are present,
186 # return false to the caller to skip this interface
187 for u in tmpulist:
188 if ifupdownobj.link_exists(u):
189 if not ifupdownobj.ALL:
86fc62e2 190 if ifupdownobj.is_ifaceobj_noconfig(ifaceobj):
191 ifupdownobj.logger.info('%s: skipping interface down,'
192 %ifaceobj.name + ' upperiface %s still around ' %u)
193 else:
194 ifupdownobj.logger.warn('%s: skipping interface down,'
195 %ifaceobj.name + ' upperiface %s still around ' %u)
99b212b0 196 return False
21c7daa7 197 return True
198
be0b20f2 199 @classmethod
200 def run_iface_graph(cls, ifupdownobj, ifacename, ops, parent=None,
d08d5f54 201 order=ifaceSchedulerFlags.POSTORDER,
202 followdependents=True):
6ef5bfa2 203 """ runs interface by traversing all nodes rooted at itself """
204
d08d5f54 205 # Each ifacename can have a list of iface objects
31a5f4c3 206 ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
207 if not ifaceobjs:
d08d5f54 208 raise Exception('%s: not found' %ifacename)
a6f80f0e 209
d08d5f54 210 for ifaceobj in ifaceobjs:
ca3f4fc7 211 if not cls._check_upperifaces(ifupdownobj, ifaceobj,
212 ops, parent, followdependents):
21c7daa7 213 return
f3215127 214
923290bd 215 # If inorder, run the iface first and then its dependents
216 if order == ifaceSchedulerFlags.INORDER:
217 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
218
219 for ifaceobj in ifaceobjs:
f3215127 220 # Run lowerifaces or dependents
62ddec8b 221 dlist = ifaceobj.lowerifaces
f3215127 222 if dlist:
fa3da4be 223 ifupdownobj.logger.debug('%s: found dependents %s'
224 %(ifacename, str(dlist)))
d08d5f54 225 try:
226 if not followdependents:
227 # XXX: this is yet another extra step,
228 # but is needed for interfaces that are
f3215127 229 # implicit dependents. even though we are asked to
230 # not follow dependents, we must follow the ones
231 # that dont have user given config. Because we own them
d08d5f54 232 new_dlist = [d for d in dlist
923290bd 233 if ifupdownobj.is_iface_noconfig(d)]
6ef5bfa2 234 if new_dlist:
be0b20f2 235 cls.run_iface_list(ifupdownobj, new_dlist, ops,
923290bd 236 ifacename, order, followdependents,
237 continueonfailure=False)
d08d5f54 238 else:
be0b20f2 239 cls.run_iface_list(ifupdownobj, dlist, ops,
f3215127 240 ifacename, order,
241 followdependents,
6ef5bfa2 242 continueonfailure=False)
d08d5f54 243 except Exception, e:
be0b20f2 244 if (ifupdownobj.ignore_error(str(e))):
d08d5f54 245 pass
246 else:
247 # Dont bring the iface up if children did not come up
a690dfae 248 ifaceobj.set_state_n_status(ifaceState.NEW,
923290bd 249 ifaceStatus.ERROR)
d08d5f54 250 raise
923290bd 251 if order == ifaceSchedulerFlags.POSTORDER:
252 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
a6f80f0e 253
be0b20f2 254 @classmethod
255 def run_iface_list(cls, ifupdownobj, ifacenames,
f3215127 256 ops, parent=None, order=ifaceSchedulerFlags.POSTORDER,
6ef5bfa2 257 followdependents=True, continueonfailure=True):
d08d5f54 258 """ Runs interface list """
a6f80f0e 259
d08d5f54 260 for ifacename in ifacenames:
a6f80f0e 261 try:
be0b20f2 262 cls.run_iface_graph(ifupdownobj, ifacename, ops, parent,
d08d5f54 263 order, followdependents)
a6f80f0e 264 except Exception, e:
6ef5bfa2 265 if continueonfailure:
69f58278 266 if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
267 traceback.print_tb(sys.exc_info()[2])
be0b20f2 268 ifupdownobj.logger.error('%s : %s' %(ifacename, str(e)))
d08d5f54 269 pass
270 else:
be0b20f2 271 if (ifupdownobj.ignore_error(str(e))):
6ef5bfa2 272 pass
273 else:
9dce3561 274 raise Exception('%s : (%s)' %(ifacename, str(e)))
d08d5f54 275
be0b20f2 276 @classmethod
c798b0f4 277 def run_iface_graph_upper(cls, ifupdownobj, ifacename, ops, parent=None,
278 followdependents=True, skip_root=False):
279 """ runs interface by traversing all nodes rooted at itself """
280
281 # Each ifacename can have a list of iface objects
282 ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
283 if not ifaceobjs:
284 raise Exception('%s: not found' %ifacename)
285
923290bd 286 if not skip_root:
287 # run the iface first and then its upperifaces
288 cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
c798b0f4 289 for ifaceobj in ifaceobjs:
c798b0f4 290 # Run upperifaces
62ddec8b 291 ulist = ifaceobj.upperifaces
c798b0f4 292 if ulist:
fa3da4be 293 ifupdownobj.logger.debug('%s: found upperifaces %s'
294 %(ifacename, str(ulist)))
c798b0f4 295 try:
296 cls.run_iface_list_upper(ifupdownobj, ulist, ops,
297 ifacename,
298 followdependents,
299 continueonfailure=True)
300 except Exception, e:
301 if (ifupdownobj.ignore_error(str(e))):
302 pass
303 else:
304 raise
305
306 @classmethod
307 def run_iface_list_upper(cls, ifupdownobj, ifacenames,
308 ops, parent=None, followdependents=True,
309 continueonfailure=True, skip_root=False):
310 """ Runs interface list """
311
312 for ifacename in ifacenames:
313 try:
314 cls.run_iface_graph_upper(ifupdownobj, ifacename, ops, parent,
315 followdependents, skip_root)
316 except Exception, e:
09a304ca
RP
317 if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
318 traceback.print_tb(sys.exc_info()[2])
319 ifupdownobj.logger.warn('%s : %s' %(ifacename, str(e)))
320 pass
c798b0f4 321
5ee3e1a8
RP
322 @classmethod
323 def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops,
324 dependency_graph, indegrees=None):
325 if len(ifacenames) == 1:
326 return ifacenames
327 # Get a sorted list of all interfaces
328 if not indegrees:
329 indegrees = OrderedDict()
330 for ifacename in dependency_graph.keys():
331 indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
332 ifacenames_all_sorted = graph.topological_sort_graphs_all(
333 dependency_graph, indegrees)
334 # if ALL was set, return all interfaces
335 if ifupdownobj.ALL:
336 return ifacenames_all_sorted
337
338 # else return ifacenames passed as argument in sorted order
339 ifacenames_sorted = []
340 [ifacenames_sorted.append(ifacename)
341 for ifacename in ifacenames_all_sorted
342 if ifacename in ifacenames]
343 return ifacenames_sorted
344
c798b0f4 345 @classmethod
346 def sched_ifaces(cls, ifupdownobj, ifacenames, ops,
347 dependency_graph=None, indegrees=None,
d08d5f54 348 order=ifaceSchedulerFlags.POSTORDER,
349 followdependents=True):
2c0ad8b3
RP
350 """ runs interface configuration modules on interfaces passed as
351 argument. Runs topological sort on interface dependency graph.
352
353 Args:
904908bc
RP
354 **ifupdownobj** (object): ifupdownMain object
355
356 **ifacenames** (list): list of interface names
357
358 **ops** : list of operations to perform eg ['pre-up', 'up', 'post-up']
359
360 **dependency_graph** (dict): dependency graph in adjacency list format
2c0ad8b3
RP
361
362 Kwargs:
904908bc
RP
363 **indegrees** (dict): indegree array of the dependency graph
364
365 **order** (int): ifaceSchedulerFlags (POSTORDER, INORDER)
366
367 **followdependents** (bool): follow dependent interfaces if true
2c0ad8b3 368
d08d5f54 369 """
5ee3e1a8
RP
370 #
371 # Algo:
372 # if ALL/auto interfaces are specified,
373 # - walk the dependency tree in postorder or inorder depending
374 # on the operation.
375 # (This is to run interfaces correctly in order)
376 # else:
377 # - sort iface list if the ifaces belong to a "class"
378 # - else just run iface list in the order they were specified
379 #
380 # Run any upperifaces if available
381 #
8c13865c 382 followupperifaces = False
5ee3e1a8
RP
383 run_queue = []
384 skip_ifacesort = int(ifupdownobj.config.get('skip_ifacesort', '0'))
385 if not skip_ifacesort and not indegrees:
386 indegrees = OrderedDict()
387 for ifacename in dependency_graph.keys():
388 indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
c798b0f4 389
5ee3e1a8 390 if not ifupdownobj.ALL:
ca3f4fc7 391 # If there is any interface that does exist, maybe it is a
5ee3e1a8
RP
392 # logical interface and we have to followupperifaces when it
393 # comes up, so get that list.
8c13865c
RP
394 if any([True for i in ifacenames
395 if ifupdownobj.must_follow_upperifaces(i)]):
396 followupperifaces = (True if
ca3f4fc7 397 [i for i in ifacenames
923290bd 398 if not ifupdownobj.link_exists(i)]
399 else False)
5ee3e1a8
RP
400 if not skip_ifacesort and ifupdownobj.IFACE_CLASS:
401 # sort interfaces only if allow class was specified and
402 # not skip_ifacesort
403 run_queue = cls.get_sorted_iface_list(ifupdownobj, ifacenames,
404 ops, dependency_graph, indegrees)
405 if run_queue and 'up' in ops[0]:
406 run_queue.reverse()
ba7b1d60 407 else:
5ee3e1a8
RP
408 # if -a is set, we dont really have to sort. We pick the interfaces
409 # that have no parents and
410 if not skip_ifacesort:
411 sorted_ifacenames = cls.get_sorted_iface_list(ifupdownobj,
412 ifacenames, ops, dependency_graph,
413 indegrees)
414 if sorted_ifacenames:
415 # pick interfaces that user asked
416 # and those that dont have any dependents first
417 [run_queue.append(ifacename)
418 for ifacename in sorted_ifacenames
419 if ifacename in ifacenames and
420 not indegrees.get(ifacename)]
421 ifupdownobj.logger.debug('graph roots (interfaces that ' +
422 'dont have dependents):' + ' %s' %str(run_queue))
423 else:
424 ifupdownobj.logger.warn('interface sort returned None')
425
426 # If queue not present, just run interfaces that were asked by the user
427 if not run_queue:
428 run_queue = list(ifacenames)
429 if 'down' in ops[0]:
430 run_queue.reverse()
431
432 # run interface list
5ee3e1a8
RP
433 cls.run_iface_list(ifupdownobj, run_queue, ops,
434 parent=None, order=order,
435 followdependents=followdependents)
09a304ca
RP
436 if not cls._SCHED_RETVAL:
437 raise Exception()
438
439 if (ifupdownobj.config.get('skip_upperifaces', '0') == '0' and
440 ((not ifupdownobj.ALL and followdependents) or
5ee3e1a8
RP
441 followupperifaces) and
442 'up' in ops[0]):
443 # If user had given a set of interfaces to bring up
444 # try and execute 'up' on the upperifaces
09a304ca
RP
445 ifupdownobj.logger.info('running upperifaces (parent interfaces) ' +
446 'if available ..')
5ee3e1a8
RP
447 cls._STATE_CHECK = False
448 cls.run_iface_list_upper(ifupdownobj, ifacenames, ops,
449 skip_root=True)
450 cls._STATE_CHECK = True