1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00
Roopa Prabhu e308cb8280 add new ifupdown2.conf option ifreload_down_changed to control ifreload
ifdown behaviour.

Ticket: CM-5819
Reviewed By: CCR-2846
Testing Done: tested ifreload evo test case

ifreload_down_changed is 0 by default which will make
sure ifreload will not execute down on changed interfaces
but only on deleted interfaces making it non-disruptive.

some notes from CCR:

ifreload was designed to be an optimization for 'service networking
restart' or 'ifdown -a + ifup -a'.
essentially it is a combination of 'ifdown + ifup' with some smarts in
which interfaces it will execute ifdown on.

By default it does the below:
ifdown all interfaces that were deleted from the interfaces file
ifdown all interfaces that were changed from the last time they were
ifup'ed
ifup -a (execute ifup on all interfaces marked auto)

Did not realize people will use ifreload as much as they do today. Also,
they may execute it on a production box when changes are made. ifdown on a production box can be
disruptive because if the ifdown which is part of the ifreload.

To have a non-disruptive option to ifreload, 2.5 added a new option -c
that only executed 'ifup' on all interfaces. Thus reloading all auto +
any other interfaces that were once brought up on the box (essentially
all interfaces present in the saved state file). This by default did not
do anything to the interfaces that got deleted from the file. But had an
ifupdown2.conf toggle to do so.

Looking at the evo use case, they do want to use a single command that
modifies, adds, deletes with
minimum disruption. we can achieve maybe what they want with multiple
commands (But there is also a case of a bug in the build evo is running
which makes it not so easy ).

This patch fixes the bug and also tries to change the default ifreload
behaviour controllable via a variable in ifupdown2.conf.
when ifreload_down_changed=0 in ifupdown2.conf, ifreload will only
ifdown interfaces that were deleted
from the file but not the ones that changed. subsequent ifup as part of
ifreload on the interfaces
that changed will apply the delta. And ifreload_down_changed default
value is '0'.

WIth the patch, ifreload by default will do the below (going back to the
previous default is just a toggle in the ifupdown.conf file):
ifdown all interfaces that were deleted from the interfaces file
ifup -a (execute ifup on all interfaces marked auto)

It sounds like a big change of behaviour for a hotfix release, but
essentially the patch just moves a few things around. And the change in
behaviour is so subtle that it is not very visible.
It just makes it non-disruptive.
(cherry picked from commit 2f7977834d4912a69159d27e54ba201f58a321d8)
2015-04-25 19:12:16 -07:00

532 lines
22 KiB
Python

#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# 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 *
from sets import Set
class ifaceSchedulerFlags():
""" Enumerates scheduler flags """
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.
"""
_STATE_CHECK = True
_SCHED_RETVAL = True
@classmethod
def run_iface_op(cls, ifupdownobj, ifaceobj, op, cenv=None):
""" Runs sub operation on an interface """
ifacename = ifaceobj.name
if ifupdownobj.type and ifupdownobj.type != ifaceobj.type:
return
if not ifupdownobj.ADDONS_ENABLE: return
if op == 'query-checkcurr':
query_ifaceobj=ifupdownobj.create_n_save_ifaceobjcurr(ifaceobj)
# If not type bridge vlan and the object does not exist,
# mark not found and return
if (not ifupdownobj.link_exists(ifaceobj.name) and
ifaceobj.type != ifaceType.BRIDGE_VLAN):
query_ifaceobj.set_state_n_status(ifaceState.from_str(op),
ifaceStatus.NOTFOUND)
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,
ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
else:
ifupdownobj.logger.debug(msg)
m.run(ifaceobj, op,
ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
except Exception, e:
if not ifupdownobj.ignore_error(str(e)):
err = 1
ifupdownobj.logger.warn(str(e))
# Continue with rest of the modules
pass
finally:
if err or ifaceobj.status == ifaceStatus.ERROR:
ifaceobj.set_state_n_status(ifaceState.from_str(op),
ifaceStatus.ERROR)
if 'up' in op or 'down' in op:
cls._SCHED_RETVAL = False
else:
# Mark success only if the interface was not already
# marked with error
status = (ifaceobj.status
if ifaceobj.status == ifaceStatus.ERROR
else ifaceStatus.SUCCESS)
ifaceobj.set_state_n_status(ifaceState.from_str(op),
status)
if ifupdownobj.config.get('addon_scripts_support', '0') == '1':
# 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_list_ops(cls, ifupdownobj, ifaceobjs, ops):
""" Runs all operations on a list of interface
configurations for the same interface
"""
# minor optimization. If operation is 'down', proceed only
# if interface exists in the system
ifacename = ifaceobjs[0].name
ifupdownobj.logger.info('%s: running ops ...' %ifacename)
if ('down' in ops[0] and
ifaceobjs[0].type != ifaceType.BRIDGE_VLAN and
not ifupdownobj.link_exists(ifacename)):
ifupdownobj.logger.debug('%s: does not exist' %ifacename)
# run posthook before you get out of here, so that
# appropriate cleanup is done
posthookfunc = ifupdownobj.sched_hooks.get('posthook')
if posthookfunc:
for ifaceobj in ifaceobjs:
ifaceobj.status = ifaceStatus.SUCCESS
posthookfunc(ifupdownobj, ifaceobj, 'down')
return
for op in ops:
# first run ifupdownobj handlers. This is good enough
# for the first object in the list
handler = ifupdownobj.ops_handlers.get(op)
if handler:
try:
handler(ifupdownobj, ifaceobjs[0])
except Exception, e:
if not ifupdownobj.link_master_slave_ignore_error(str(e)):
ifupdownobj.logger.warn('%s: %s'
%(ifaceobjs[0].name, str(e)))
pass
for ifaceobj in ifaceobjs:
cls.run_iface_op(ifupdownobj, ifaceobj, op,
cenv=ifupdownobj.generate_running_env(ifaceobj, op)
if ifupdownobj.config.get('addon_scripts_support',
'0') == '1' else None)
posthookfunc = ifupdownobj.sched_hooks.get('posthook')
if posthookfunc:
try:
[posthookfunc(ifupdownobj, ifaceobj, ops[0])
for ifaceobj in ifaceobjs]
except Exception, e:
ifupdownobj.logger.warn('%s' %str(e))
pass
@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.
"""
# proceed only for down operation
if 'down' not in ops[0]:
return True
if (ifupdownobj.FORCE or
not ifupdownobj.ADDONS_ENABLE or
(not ifupdownobj.is_ifaceobj_noconfig(ifaceobj) and
ifupdownobj.config.get('warn_on_ifdown', '0') == '0' and
not ifupdownobj.ALL)):
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:
if ifupdownobj.is_ifaceobj_noconfig(ifaceobj):
ifupdownobj.logger.info('%s: skipping interface down,'
%ifaceobj.name + ' upperiface %s still around ' %u)
else:
ifupdownobj.logger.warn('%s: skipping interface down,'
%ifaceobj.name + ' upperiface %s still around ' %u)
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)
# Check state of the dependent. If it is already brought up, return
if (cls._STATE_CHECK and
(ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
ifupdownobj.logger.debug('%s: already processed' %ifacename)
return
for ifaceobj in ifaceobjs:
if not cls._check_upperifaces(ifupdownobj, ifaceobj,
ops, parent, followdependents):
return
# If inorder, run the iface first and then its dependents
if order == ifaceSchedulerFlags.INORDER:
cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
for ifaceobj in ifaceobjs:
# 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_list_ops(ifupdownobj, ifaceobjs, 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)
if (cls._STATE_CHECK and
(ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
ifupdownobj.logger.debug('%s: already processed' %ifacename)
return
if not skip_root:
# run the iface first and then its upperifaces
cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
for ifaceobj in ifaceobjs:
# 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 ifupdownobj.logger.isEnabledFor(logging.DEBUG):
traceback.print_tb(sys.exc_info()[2])
ifupdownobj.logger.warn('%s : %s' %(ifacename, str(e)))
pass
@classmethod
def _get_valid_upperifaces(cls, ifupdownobj, ifacenames,
allupperifacenames):
""" Recursively find valid upperifaces
valid upperifaces are:
- An upperiface which had no user config (example builtin
interfaces. usually vlan interfaces.)
- or had config and previously up
- and interface currently does not exist
- or is a bridge (because if your upperiface was a bridge
- u will have to execute up on the bridge
to enslave the port and apply bridge attributes to the port) """
upperifacenames = []
for ifacename in ifacenames:
# get upperifaces
ifaceobj = ifupdownobj.get_ifaceobj_first(ifacename)
if not ifaceobj:
continue
ulist = Set(ifaceobj.upperifaces).difference(upperifacenames)
nulist = []
for u in ulist:
uifaceobj = ifupdownobj.get_ifaceobj_first(u)
if not uifaceobj:
continue
has_config = not bool(uifaceobj.priv_flags
& ifupdownobj.NOCONFIG)
if (((has_config and ifupdownobj.get_ifaceobjs_saved(u)) or
not has_config) and (not ifupdownobj.link_exists(u)
or uifaceobj.link_kind == ifaceLinkKind.BRIDGE)):
nulist.append(u)
upperifacenames.extend(nulist)
allupperifacenames.extend(upperifacenames)
if upperifacenames:
cls._get_valid_upperifaces(ifupdownobj, upperifacenames,
allupperifacenames)
return
@classmethod
def run_upperifaces(cls, ifupdownobj, ifacenames, ops,
continueonfailure=True):
""" Run through valid upperifaces """
upperifaces = []
cls._get_valid_upperifaces(ifupdownobj, ifacenames, upperifaces)
if not upperifaces:
return
# dump valid upperifaces
ifupdownobj.logger.debug(upperifaces)
for u in upperifaces:
try:
ifaceobjs = ifupdownobj.get_ifaceobjs(u)
if not ifaceobjs:
continue
cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
except Exception, e:
if continueonfailure:
self.logger.warn('%s' %str(e))
@classmethod
def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops,
dependency_graph, indegrees=None):
if len(ifacenames) == 1:
return ifacenames
# 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)
ifacenames_all_sorted = graph.topological_sort_graphs_all(
dependency_graph, indegrees)
# if ALL was set, return all interfaces
if ifupdownobj.ALL:
return ifacenames_all_sorted
# else return ifacenames passed as argument in sorted order
ifacenames_sorted = []
[ifacenames_sorted.append(ifacename)
for ifacename in ifacenames_all_sorted
if ifacename in ifacenames]
return ifacenames_sorted
@classmethod
def sched_ifaces(cls, ifupdownobj, ifacenames, ops,
dependency_graph=None, indegrees=None,
order=ifaceSchedulerFlags.POSTORDER,
followdependents=True, skipupperifaces=False, sort=False):
""" runs interface configuration modules on interfaces passed as
argument. Runs topological sort on interface dependency graph.
Args:
**ifupdownobj** (object): ifupdownMain object
**ifacenames** (list): list of interface names
**ops** : list of operations to perform eg ['pre-up', 'up', 'post-up']
**dependency_graph** (dict): dependency graph in adjacency list format
Kwargs:
**indegrees** (dict): indegree array of the dependency graph
**order** (int): ifaceSchedulerFlags (POSTORDER, INORDER)
**followdependents** (bool): follow dependent interfaces if true
**sort** (bool): sort ifacelist in the case where ALL is not set
"""
#
# Algo:
# if ALL/auto interfaces are specified,
# - walk the dependency tree in postorder or inorder depending
# on the operation.
# (This is to run interfaces correctly in order)
# else:
# - sort iface list if the ifaces belong to a "class"
# - else just run iface list in the order they were specified
#
# Run any upperifaces if available
#
followupperifaces = False
run_queue = []
skip_ifacesort = int(ifupdownobj.config.get('skip_ifacesort', '0'))
if not skip_ifacesort and not indegrees:
indegrees = OrderedDict()
for ifacename in dependency_graph.keys():
indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
if not ifupdownobj.ALL:
if 'up' in ops[0]:
# If there is any interface that does not exist, maybe it
# is a logical interface and we have to followupperifaces
# when it comes up, so lets get that list.
if any([True for i in ifacenames
if ifupdownobj.must_follow_upperifaces(i)]):
followupperifaces = (True if
[i for i in ifacenames
if not ifupdownobj.link_exists(i)]
else False)
# sort interfaces only if the caller asked to sort
# and skip_ifacesort is not on.
if not skip_ifacesort and sort:
run_queue = cls.get_sorted_iface_list(ifupdownobj, ifacenames,
ops, dependency_graph, indegrees)
if run_queue and 'up' in ops[0]:
run_queue.reverse()
else:
# if -a is set, we pick the interfaces
# that have no parents and use a sorted list of those
if not skip_ifacesort:
sorted_ifacenames = cls.get_sorted_iface_list(ifupdownobj,
ifacenames, ops, dependency_graph,
indegrees)
if sorted_ifacenames:
# 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))
else:
ifupdownobj.logger.warn('interface sort returned None')
# If queue not present, just run interfaces that were asked by the
# user
if not run_queue:
run_queue = list(ifacenames)
# if we are taking the order of interfaces as specified
# in the interfaces file, we should reverse the list if we
# want to down. This can happen if 'skip_ifacesort'
# is been specified.
if 'down' in ops[0]:
run_queue.reverse()
# run interface list
cls.run_iface_list(ifupdownobj, run_queue, ops,
parent=None, order=order,
followdependents=followdependents)
if not cls._SCHED_RETVAL:
raise Exception()
if (not skipupperifaces and
ifupdownobj.config.get('skip_upperifaces', '0') == '0' and
((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 (parent interfaces) ' +
'if available ..')
cls._STATE_CHECK = False
cls.run_upperifaces(ifupdownobj, ifacenames, ops)
cls._STATE_CHECK = True