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