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