]>
Commit | Line | Data |
---|---|---|
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 | 10 | from statemanager import * |
11 | from iface import * | |
12 | from graph import * | |
13 | from collections import deque | |
14 | from collections import OrderedDict | |
a6f80f0e | 15 | import logging |
d08d5f54 | 16 | import traceback |
69f58278 | 17 | import sys |
a6f80f0e | 18 | from graph import * |
19 | from collections import deque | |
20 | from threading import * | |
21 | from ifupdownbase import * | |
22 | ||
d08d5f54 | 23 | class ifaceSchedulerFlags(): |
904908bc RP |
24 | """ Enumerates scheduler flags """ |
25 | ||
c798b0f4 | 26 | INORDER = 0x1 |
27 | POSTORDER = 0x2 | |
d08d5f54 | 28 | |
be0b20f2 | 29 | class 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 |