#!/usr/bin/python # # Copyright 2013. Cumulus Networks, Inc. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # ifaceScheduler -- # interface scheduler # from statemanager import * from iface import * from graph import * from collections import deque from collections import OrderedDict import logging import traceback import sys from graph import * from collections import deque from threading import * from ifupdownbase import * class ifaceSchedulerFlags(): INORDER = 0x1 POSTORDER = 0x2 class ifaceScheduler(): """ scheduler functions to schedule configuration of interfaces. supports scheduling of interfaces serially in plain interface list or dependency graph format. Algo: - run topological sort on the iface objects - In the sorted iface object list, pick up interfaces with no parents and run ops on them and their children. - If operation is up and user gave interface list (ie not all) option, also see if there were upper-devices and run ops on them. - if operation is down, dont down the interface if it still has upperifaces present. The down operation is executed when the last upperiface goes away. If force option is set, this rule does not apply. - run ops calls addon modules run operation passing the iface object and op to each module. - ops are [pre-up, up, post-up, pre-down, down, post-down, query-running, query-check] """ _STATE_CHECK = True @classmethod def run_iface_op(cls, ifupdownobj, ifaceobj, op, cenv): """ Runs sub operation on an interface """ ifacename = ifaceobj.name if (cls._STATE_CHECK and (ifaceobj.state >= ifaceState.from_str(op)) and (ifaceobj.status == ifaceStatus.SUCCESS)): ifupdownobj.logger.debug('%s: already in state %s' %(ifacename, op)) return # first run ifupdownobj handlers handler = ifupdownobj.ops_handlers.get(op) if handler: if not ifaceobj.addr_method or (ifaceobj.addr_method and ifaceobj.addr_method != 'manual'): handler(ifupdownobj, ifaceobj) if not ifupdownobj.ADDONS_ENABLE: return for mname in ifupdownobj.module_ops.get(op): m = ifupdownobj.modules.get(mname) err = 0 try: if hasattr(m, 'run'): msg = ('%s: %s : running module %s' %(ifacename, op, mname)) if op == 'query-checkcurr': # Dont check curr if the interface object was # auto generated if (ifaceobj.priv_flags & ifupdownobj.NOCONFIG): continue ifupdownobj.logger.debug(msg) m.run(ifaceobj, op, query_ifaceobj=ifupdownobj.create_n_save_ifaceobjcurr(ifaceobj)) else: ifupdownobj.logger.debug(msg) m.run(ifaceobj, op) except Exception, e: err = 1 ifupdownobj.log_error(str(e)) finally: if err: ifaceobj.set_state_n_status(ifaceState.from_str(op), ifaceStatus.ERROR) else: ifaceobj.set_state_n_status(ifaceState.from_str(op), ifaceStatus.SUCCESS) if ifupdownobj.COMPAT_EXEC_SCRIPTS: # execute /etc/network/ scripts for mname in ifupdownobj.script_ops.get(op, []): ifupdownobj.logger.debug('%s: %s : running script %s' %(ifacename, op, mname)) try: ifupdownobj.exec_command(mname, cmdenv=cenv) except Exception, e: ifupdownobj.log_error(str(e)) @classmethod def run_iface_ops(cls, ifupdownobj, ifaceobj, ops): """ Runs all operations on an interface """ ifacename = ifaceobj.name # minor optimization. If operation is 'down', proceed only # if interface exists in the system if 'down' in ops[0] and not ifupdownobj.link_exists(ifacename): ifupdownobj.logger.debug('%s: does not exist' %ifacename) return cenv=None if ifupdownobj.COMPAT_EXEC_SCRIPTS: # For backward compatibility generate env variables # for attributes cenv = ifupdownobj.generate_running_env(ifaceobj, ops[0]) map(lambda op: cls.run_iface_op(ifupdownobj, ifaceobj, op, cenv), ops) posthookfunc = ifupdownobj.sched_hooks.get('posthook') if posthookfunc: posthookfunc(ifupdownobj, ifaceobj, ops[0]) @classmethod def _check_upperifaces(cls, ifupdownobj, ifaceobj, ops, parent, followdependents=False): """ Check if upperifaces are hanging off us and help caller decide if he can proceed with the ops on this device Returns True or False indicating the caller to proceed with the operation. """ if ifupdownobj.FORCE: return True # proceed only for down operation if 'down' not in ops[0]: return True ulist = ifaceobj.upperifaces if not ulist: return True # Get the list of upper ifaces other than the parent tmpulist = ([u for u in ulist if u != parent] if parent else ulist) if not tmpulist: return True # XXX: This is expensive. Find a cheaper way to do this. # if any of the upperdevs are present, # return false to the caller to skip this interface for u in tmpulist: if ifupdownobj.link_exists(u): if not ifupdownobj.ALL: ifupdownobj.logger.info('%s: skip interface down,' %ifaceobj.name + ' upperiface %s still around ' %u + '(use --force to override)') return False return True @classmethod def run_iface_graph(cls, ifupdownobj, ifacename, ops, parent=None, order=ifaceSchedulerFlags.POSTORDER, followdependents=True): """ runs interface by traversing all nodes rooted at itself """ # Each ifacename can have a list of iface objects ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename) if not ifaceobjs: raise Exception('%s: not found' %ifacename) for ifaceobj in ifaceobjs: if not cls._check_upperifaces(ifupdownobj, ifaceobj, ops, parent, followdependents): return if order == ifaceSchedulerFlags.INORDER: # If inorder, run the iface first and then its dependents cls.run_iface_ops(ifupdownobj, ifaceobj, ops) # Run lowerifaces or dependents dlist = ifaceobj.lowerifaces if dlist: ifupdownobj.logger.debug('%s: found dependents %s' %(ifacename, str(dlist))) try: if not followdependents: # XXX: this is yet another extra step, # but is needed for interfaces that are # implicit dependents. even though we are asked to # not follow dependents, we must follow the ones # that dont have user given config. Because we own them new_dlist = [d for d in dlist if ifupdownobj.is_iface_noconfig(d)] if new_dlist: cls.run_iface_list(ifupdownobj, new_dlist, ops, ifacename, order, followdependents, continueonfailure=False) else: cls.run_iface_list(ifupdownobj, dlist, ops, ifacename, order, followdependents, continueonfailure=False) except Exception, e: if (ifupdownobj.ignore_error(str(e))): pass else: # Dont bring the iface up if children did not come up ifaceobj.set_state_n_status(ifaceState.NEW, ifaceStatus.ERROR) raise if order == ifaceSchedulerFlags.POSTORDER: cls.run_iface_ops(ifupdownobj, ifaceobj, ops) @classmethod def run_iface_list(cls, ifupdownobj, ifacenames, ops, parent=None, order=ifaceSchedulerFlags.POSTORDER, followdependents=True, continueonfailure=True): """ Runs interface list """ for ifacename in ifacenames: try: cls.run_iface_graph(ifupdownobj, ifacename, ops, parent, order, followdependents) except Exception, e: if continueonfailure: if ifupdownobj.logger.isEnabledFor(logging.DEBUG): traceback.print_tb(sys.exc_info()[2]) ifupdownobj.logger.error('%s : %s' %(ifacename, str(e))) pass else: if (ifupdownobj.ignore_error(str(e))): pass else: raise Exception('%s : (%s)' %(ifacename, str(e))) @classmethod def run_iface_graph_upper(cls, ifupdownobj, ifacename, ops, parent=None, followdependents=True, skip_root=False): """ runs interface by traversing all nodes rooted at itself """ # Each ifacename can have a list of iface objects ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename) if not ifaceobjs: raise Exception('%s: not found' %ifacename) for ifaceobj in ifaceobjs: if not skip_root: # run the iface first and then its upperifaces cls.run_iface_ops(ifupdownobj, ifaceobj, ops) # Run upperifaces ulist = ifaceobj.upperifaces if ulist: ifupdownobj.logger.debug('%s: found upperifaces %s' %(ifacename, str(ulist))) try: cls.run_iface_list_upper(ifupdownobj, ulist, ops, ifacename, followdependents, continueonfailure=True) except Exception, e: if (ifupdownobj.ignore_error(str(e))): pass else: raise @classmethod def run_iface_list_upper(cls, ifupdownobj, ifacenames, ops, parent=None, followdependents=True, continueonfailure=True, skip_root=False): """ Runs interface list """ for ifacename in ifacenames: try: cls.run_iface_graph_upper(ifupdownobj, ifacename, ops, parent, followdependents, skip_root) except Exception, e: if continueonfailure: if ifupdownobj.logger.isEnabledFor(logging.DEBUG): traceback.print_tb(sys.exc_info()[2]) ifupdownobj.logger.error('%s : %s' %(ifacename, str(e))) pass else: if (ifupdownobj.ignore_error(str(e))): pass else: raise Exception('%s : (%s)' %(ifacename, str(e))) @classmethod def sched_ifaces(cls, ifupdownobj, ifacenames, ops, dependency_graph=None, indegrees=None, order=ifaceSchedulerFlags.POSTORDER, followdependents=True): """ Runs iface dependeny graph by visiting all the nodes Parameters: ----------- ifupdownobj : ifupdown object (used for getting and updating iface object state) dependency_graph : dependency graph in adjacency list format (contains more than one dependency graph) ops : list of operations to perform eg ['pre-up', 'up', 'post-up'] indegrees : indegree array if present is used to determine roots of the graphs in the dependency_graph """ if not ifupdownobj.ALL or not followdependents or len(ifacenames) == 1: # If there is any interface that does exist, maybe it is a # logical interface and we have to followupperifaces followupperifaces = (True if [i for i in ifacenames if not ifupdownobj.link_exists(i)] else False) cls.run_iface_list(ifupdownobj, ifacenames, ops, parent=None,order=order, followdependents=followdependents) if (not ifupdownobj.ALL and (followdependents or followupperifaces) and 'up' in ops[0]): # If user had given a set of interfaces to bring up # try and execute 'up' on the upperifaces ifupdownobj.logger.info('running upperifaces if available') cls._STATE_CHECK = False cls.run_iface_list_upper(ifupdownobj, ifacenames, ops, skip_root=True) cls._STATE_CHECK = True return run_queue = [] # Get a sorted list of all interfaces if not indegrees: indegrees = OrderedDict() for ifacename in dependency_graph.keys(): indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename) sorted_ifacenames = graph.topological_sort_graphs_all(dependency_graph, dict(indegrees)) ifupdownobj.logger.debug('sorted ifacenames %s : ' %str(sorted_ifacenames)) # From the sorted list, pick interfaces that user asked # and those that dont have any dependents first [run_queue.append(ifacename) for ifacename in sorted_ifacenames if ifacename in ifacenames and not indegrees.get(ifacename)] ifupdownobj.logger.debug('graph roots (interfaces that dont have ' 'dependents):' + ' %s' %str(run_queue)) cls.run_iface_list(ifupdownobj, run_queue, ops, parent=None,order=order, followdependents=followdependents)