diff --git a/TODO b/TODO index 00032f6..af2c033 100644 --- a/TODO +++ b/TODO @@ -40,3 +40,11 @@ TODO: PATH the command search path: /usr/local/sbin:/usr/local/bin:‐ /usr/sbin:/usr/bin:/sbin:/bin +- ifquery -r shows bridge-pathcosts + auto bridge100 + iface bridge100 + bridge-ports swp13.100 swp14.100 + bridge-pathcosts swp13.100=2 swp14.100=2 + +- Does not purge old addresses + diff --git a/config/ifupdown2.conf b/config/ifupdown2.conf new file mode 100644 index 0000000..b20bd42 --- /dev/null +++ b/config/ifupdown2.conf @@ -0,0 +1,7 @@ +# +# +# +TEMPLATE_ENGINE=mako + +TEMPLATE_LOOKUPPATH=/etc/network/ifupdown2/templates + diff --git a/pkg/iface.py b/pkg/iface.py index 17efe23..2aa379e 100644 --- a/pkg/iface.py +++ b/pkg/iface.py @@ -383,6 +383,7 @@ class iface(): for cname, cvaluelist in config.items(): idx = 0 for cv in cvaluelist: + if not cv: continue if with_status: outbuf += indent + '%s %s %s\n' %(cname, cv, self.get_config_attr_status_str(cname, idx)) diff --git a/pkg/ifupdownmain.py b/pkg/ifupdownmain.py index b6de174..36a764d 100644 --- a/pkg/ifupdownmain.py +++ b/pkg/ifupdownmain.py @@ -40,7 +40,7 @@ class ifupdownMain(ifupdownBase): scripts_dir='/etc/network' addon_modules_dir='/usr/share/ifupdownaddons' - addon_modules_configfile='/etc/network/.addons.conf' + addon_modules_configfile='/var/lib/ifupdownaddons/addons.conf' # iface dictionary in the below format: # { '' : [, ..] } @@ -106,9 +106,11 @@ class ifupdownMain(ifupdownBase): # ifupdown object interface scheduler pre and posthooks sched_hooks = {'posthook' : run_sched_ifaceobj_posthook} - def __init__(self, force=False, dryrun=False, nowait=False, + def __init__(self, config={}, + force=False, dryrun=False, nowait=False, perfmode=False, withdepends=False, njobs=1, - cache=False, addons_enable=True, statemanager_enable=True): + cache=False, addons_enable=True, statemanager_enable=True, + interfacesfile='/etc/network/interfaces'): self.logger = logging.getLogger('ifupdown') self.FORCE = force self.DRYRUN = dryrun @@ -117,6 +119,9 @@ class ifupdownMain(ifupdownBase): self.WITH_DEPENDS = withdepends self.STATEMANAGER_ENABLE = statemanager_enable self.CACHE = cache + self.interfacesfile = interfacesfile + self.config = config + self.logger.debug(self.config) # Can be used to provide hints for caching self.CACHE_FLAGS = 0x0 @@ -295,8 +300,6 @@ class ifupdownMain(ifupdownBase): dlist = None pass if dlist: - self.logger.debug('%s: ' %ifaceobj.name + - 'lowerifaces/dependents: %s' %str(dlist)) break return dlist @@ -323,7 +326,7 @@ class ifupdownMain(ifupdownBase): if dlist: self.preprocess_dependency_list(ifaceobj.name, dlist, ops) - self.logger.debug('%s: lowerifaces/dependents after processing: %s' + self.logger.debug('%s: lowerifaces/dependents: %s' %(i, str(dlist))) ifaceobj.lowerifaces = dlist [iqueue.append(d) for d in dlist] @@ -352,18 +355,18 @@ class ifupdownMain(ifupdownBase): pass return False - def read_default_iface_config(self): + def read_iface_config(self): """ Reads default network interface config /etc/network/interfaces. """ - nifaces = networkInterfaces() + nifaces = networkInterfaces(self.interfacesfile, + template_engine=self.config.get('template_engine'), + template_lookuppath=self.config.get('template_lookuppath')) nifaces.subscribe('iface_found', self._save_iface) nifaces.subscribe('validate', self._module_syntax_checker) nifaces.load() - def read_iface_config(self): - return self.read_default_iface_config() - def read_old_iface_config(self): - """ Reads the saved iface config instead of default iface config. """ + """ Reads the saved iface config instead of default iface config. + And saved iface config is already read by the statemanager """ self.ifaceobjdict = copy.deepcopy(self.statemanager.ifaceobjdict) def _load_addon_modules_config(self): @@ -420,6 +423,7 @@ class ifupdownMain(ifupdownBase): self.module_ops['query'] = self.modules.keys() self.module_ops['query-raw'] = self.modules.keys() + def _modules_help(self): """ Prints addon modules supported syntax """ @@ -668,12 +672,11 @@ class ifupdownMain(ifupdownBase): excludepats, i)] if not filtered_ifacenames: raise Exception('no ifaces found matching given allow lists') + + self.populate_dependency_info(ops, filtered_ifacenames) if printdependency: - self.populate_dependency_info(ops, filtered_ifacenames) self.print_dependency(filtered_ifacenames, printdependency) return - else: - self.populate_dependency_info(ops) try: self._sched_ifaces(filtered_ifacenames, ops) @@ -727,12 +730,10 @@ class ifupdownMain(ifupdownBase): raise Exception('no ifaces found matching ' + 'given allow lists') + self.populate_dependency_info(ops, filtered_ifacenames) if ops[0] == 'query-dependency' and printdependency: - self.populate_dependency_info(ops, filtered_ifacenames) self.print_dependency(filtered_ifacenames, printdependency) return - else: - self.populate_dependency_info(ops) if ops[0] == 'query': return self.print_ifaceobjs_pretty(filtered_ifacenames, format) @@ -751,7 +752,7 @@ class ifupdownMain(ifupdownBase): return def reload(self, upops, downops, auto=False, allow=None, - ifacenames=None, excludepats=None): + ifacenames=None, excludepats=None, usecurrentconfig=False): """ reload interface config """ allow_classes = [] @@ -762,23 +763,21 @@ class ifupdownMain(ifupdownBase): self.WITH_DEPENDS = True try: - # Read the current interface config self.read_iface_config() except: raise # generate dependency graph of interfaces self.populate_dependency_info(upops) + if (not usecurrentconfig and self.STATEMANAGER_ENABLE + and self.statemanager.ifaceobjdict): + # Save a copy of new iface objects and dependency_graph + new_ifaceobjdict = dict(self.ifaceobjdict) + new_dependency_graph = dict(self.dependency_graph) - # Save a copy of new iface objects and dependency_graph - new_ifaceobjdict = dict(self.ifaceobjdict) - new_dependency_graph = dict(self.dependency_graph) - - if self.STATEMANAGER_ENABLE and self.statemanager.ifaceobjdict: # if old state is present, read old state and mark op for 'down' # followed by 'up' aka: reload # old interface config is read into self.ifaceobjdict - # self.read_old_iface_config() op = 'reload' else: @@ -825,7 +824,7 @@ class ifupdownMain(ifupdownBase): # reinitialize dependency graph self.dependency_graph = OrderedDict({}) # Generate dependency info for old config - self.populate_dependency_info(downops) + self.populate_dependency_info(downops, ifacedownlist) self._sched_ifaces(ifacedownlist, downops) else: self.logger.debug('no interfaces to down ..') diff --git a/pkg/networkinterfaces.py b/pkg/networkinterfaces.py index fd3de2e..aa094c3 100644 --- a/pkg/networkinterfaces.py +++ b/pkg/networkinterfaces.py @@ -11,7 +11,9 @@ import collections import logging import glob import re +import os from iface import * +from template import templateEngine class networkInterfaces(): @@ -19,22 +21,24 @@ class networkInterfaces(): auto_ifaces = [] callbacks = {} - ifaces_file = "/etc/network/interfaces" - - def __init__(self): + def __init__(self, interfacesfile='/etc/network/interfaces', + template_engine=None, template_lookuppath=None): self.logger = logging.getLogger('ifupdown.' + self.__class__.__name__) self.callbacks = {'iface_found' : None, 'validate' : None} self.allow_classes = {} - self._filestack = [self.ifaces_file] + self.interfacesfile = interfacesfile + self._filestack = [self.interfacesfile] + self._template_engine = templateEngine(template_engine, + template_lookuppath) @property def _currentfile(self): try: return self._filestack[-1] except: - return self.ifaces_file + return self.interfacesfile def _parse_error(self, filename, lineno, msg): if lineno == -1: @@ -95,12 +99,14 @@ class networkInterfaces(): [self.auto_ifaces.append(a) for a in auto_ifaces] return 0 - def _add_to_iface_config(self, iface_config, attrname, attrval, lineno): + def _add_to_iface_config(self, ifacename, iface_config, attrname, + attrval, lineno): newattrname = attrname.replace("_", "-") try: if not self.callbacks.get('validate')(newattrname, attrval): self._parse_error(self._currentfile, lineno, - 'unsupported keyword (%s)' %attrname) + 'iface %s: unsupported keyword (%s)' + %(ifacename, attrname)) return except: pass @@ -152,12 +158,12 @@ class networkInterfaces(): attrs = l.split(' ', 1) if len(attrs) < 2: self._parse_error(self._currentfile, line_idx, - 'invalid syntax \'%s\'' %ifacename) + 'iface %s: invalid syntax \'%s\'' %(ifacename, l)) continue attrname = attrs[0] attrval = attrs[1].strip(' ') - self._add_to_iface_config(iface_config, attrname, attrval, - line_idx+1) + self._add_to_iface_config(ifacename, iface_config, attrname, + attrval, line_idx+1) lines_consumed = line_idx - cur_idx # Create iface object @@ -228,39 +234,27 @@ class networkInterfaces(): line_idx += 1 return 0 - def run_template_engine(self, textdata): - try: - from mako.template import Template - except: - self.logger.warning('template engine mako not found. ' + - 'skip template parsing ..'); - return textdata - t = Template(text=textdata, output_encoding='utf-8') - return t.render() - def read_file(self, filename=None): - ifaces_file = filename - if not ifaces_file: - ifaces_file=self.ifaces_file - self._filestack.append(ifaces_file) - self.logger.info('reading interfaces file %s' %ifaces_file) - f = open(ifaces_file) + interfacesfile = filename + if not interfacesfile: + interfacesfile=self.interfacesfile + self._filestack.append(interfacesfile) + self.logger.info('reading interfaces file %s' %interfacesfile) + f = open(interfacesfile) filedata = f.read() f.close() # process line continuations filedata = ' '.join(d.strip() for d in filedata.split('\\')) # run through template engine try: - self.logger.info('template processing on interfaces file %s ...' - %ifaces_file) - rendered_filedata = self.run_template_engine(filedata) + rendered_filedata = self._template_engine.render(filedata) except Exception, e: self._parse_error(self._currentfile, -1, - 'failed to render template (%s).' %str(e) + + 'failed to render template (%s). ' %str(e) + 'Continue without template rendering ...') rendered_filedata = None pass - self.logger.info('parsing interfaces file %s ...' %ifaces_file) + self.logger.info('parsing interfaces file %s ...' %interfacesfile) if rendered_filedata: self.process_filedata(rendered_filedata) else: diff --git a/pkg/template.py b/pkg/template.py new file mode 100644 index 0000000..b4a3e35 --- /dev/null +++ b/pkg/template.py @@ -0,0 +1,51 @@ +#!/usr/bin/python + +import logging +import traceback +from utils import * + +class templateEngine(): + + def __init__(self, template_engine, template_lookuppath=None): + self.logger = logging.getLogger('ifupdown.' + + self.__class__.__name__) + self.tclass = None + self.tclassargs = {} + self.render = self._render_default + if template_engine == 'mako': + try: + self.tclass = utils.importName('mako.template', 'Template') + except Exception, e: + self.logger.warn('unable to load template engine %s (%s)' + %(template_engine, str(e))) + pass + if template_lookuppath: + try: + self.logger.debug('setting template lookuppath to %s' + %template_lookuppath) + lc = utils.importName('mako.lookup', 'TemplateLookup') + self.tclassargs['lookup'] = lc( + directories=template_lookuppath.split(':')) + except Exception, e: + self.logger.warn('unable to set template lookup path' + + ' %s (%s)' %(template_lookuppath, str(e))) + pass + self.render = self._render_mako + else: + self.logger.info('skip template processing.., ' + + 'template engine not found') + + def _render_default(self, textdata): + return textdata + + def _render_mako(self, textdata): + """ render textdata passed as argument using mako + + Returns rendered textdata """ + + if not self.tclass: + return textdata + self.logger.info('template processing on interfaces file ...') + t = self.tclass(text=textdata, output_encoding='utf-8', + lookup=self.tclassargs.get('lookup')) + return t.render() diff --git a/pkg/utils.py b/pkg/utils.py new file mode 100644 index 0000000..2b2229f --- /dev/null +++ b/pkg/utils.py @@ -0,0 +1,13 @@ +#!/usr/bin/python + + +class utils(): + + @classmethod + def importName(cls, modulename, name): + """ Import a named object """ + try: + module = __import__(modulename, globals(), locals(), [name]) + except ImportError: + return None + return getattr(module, name) diff --git a/sbin/ifupdown b/sbin/ifupdown index aa48a5e..848ede8 100755 --- a/sbin/ifupdown +++ b/sbin/ifupdown @@ -5,11 +5,15 @@ import sys import os import argcomplete import argparse +import ConfigParser +import StringIO from ifupdown.ifupdownmain import * import logging lockfile="/run/network/.lock" +configfile="/etc/network/ifupdown2/ifupdown2.conf" +configmap_g=None logger = None ENVPATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" @@ -24,15 +28,15 @@ def run_up(args): cachearg=(False if (iflist or args.nocache or args.perfmode or args.noact) else True) - ifupdown_handle = ifupdownMain(force=args.force, + ifupdown_handle = ifupdownMain(config=configmap_g, + force=args.force, withdepends=args.withdepends, perfmode=args.perfmode, - njobs=args.jobs, dryrun=args.noact, cache=cachearg, addons_enable=not args.noaddons, - statemanager_enable=not args.noaddons) - + statemanager_enable=not args.noaddons, + interfacesfile=args.interfacesfile) if args.noaddons: ifupdown_handle.up(['up'], args.all, args.CLASS, iflist, excludepats=args.excludepats, @@ -53,13 +57,13 @@ def run_down(args): try: iflist = args.iflist logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(force=args.force, + ifupdown_handle = ifupdownMain(config=configmap_g, force=args.force, withdepends=args.withdepends, perfmode=args.perfmode, - njobs=args.jobs, dryrun=args.noact, addons_enable=not args.noaddons, - statemanager_enable=not args.noaddons) + statemanager_enable=not args.noaddons, + interfacesfile=args.interfacesfile) ifupdown_handle.down(['pre-down', 'down', 'post-down'], args.all, args.CLASS, iflist, @@ -96,10 +100,11 @@ def run_query(args): iflist = [i for i in os.listdir('/sys/class/net/') if os.path.isdir('/sys/class/net/%s' %i)] logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(withdepends=args.withdepends, + ifupdown_handle = ifupdownMain(config=configmap_g, + withdepends=args.withdepends, perfmode=args.perfmode, - njobs=args.jobs, - cache=cachearg) + cache=cachearg, + interfacesfile=args.interfacesfile) ifupdown_handle.query([qop], args.all, args.CLASS, iflist, excludepats=args.excludepats, @@ -113,13 +118,14 @@ def run_reload(args): try: logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(withdepends=args.withdepends, - perfmode=args.perfmode, - njobs=args.jobs) + ifupdown_handle = ifupdownMain(config=configmap_g, + withdepends=args.withdepends, + perfmode=args.perfmode) ifupdown_handle.reload(['pre-up', 'up', 'post-up'], ['pre-down', 'down', 'post-down'], args.all, None, None, - excludepats=args.excludepats) + excludepats=args.excludepats, + usecurrentconfig=args.usecurrentconfig) except: raise @@ -161,20 +167,24 @@ def update_argparser(argparser): help=argparse.SUPPRESS) argparser.add_argument('--allow', dest='CLASS', help='ignore non-\"allow-CLASS\" interfaces') - argparser.add_argument('--with-depends', dest='withdepends', + argparser.add_argument('-w', '--with-depends', dest='withdepends', action='store_true', help='run with all dependent interfaces.'+ ' This option is redundant when \'-a\' is specified. With ' + '\'-a\' interfaces are always executed in dependency order') argparser.add_argument('--perfmode', dest='perfmode', action='store_true', help=argparse.SUPPRESS) - argparser.add_argument('-j', '--jobs', dest='jobs', type=int, - default=-1, choices=range(1,12), help=argparse.SUPPRESS) + #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, + # default=-1, choices=range(1,12), help=argparse.SUPPRESS) argparser.add_argument('--nocache', dest='nocache', action='store_true', help=argparse.SUPPRESS) argparser.add_argument('-X', '--exclude', dest='excludepats', action='append', help='Exclude interfaces from the list of interfaces' + ' to operate on. Can be specified multiple times.') + argparser.add_argument('-i', '--interfaces', dest='interfacesfile', + default='/etc/network/interfaces', + help='use interfaces file instead of default ' + + '/etc/network/interfaces') def update_ifupdown_argparser(argparser): """ common arg parser for ifup and ifdown """ @@ -185,13 +195,13 @@ def update_ifupdown_argparser(argparser): group.add_argument('-n', '--no-act', dest='noact', action='store_true', help='print out what would happen,' + 'but don\'t do it') - group.add_argument('--print-dependency', + group.add_argument('-p', '--print-dependency', dest='printdependency', choices=['list', 'dot'], help='print iface dependency') group.add_argument('--no-scripts', '--no-addons', dest='noaddons', action='store_true', - help='dont run any addon modules or scripts. Runs only link ' + - 'up/down') + help='dont run any addon modules/scripts. Only bring the ' + + 'interface administratively up/down') def update_ifup_argparser(argparser): argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', @@ -203,12 +213,11 @@ def update_ifdown_argparser(argparser): update_ifupdown_argparser(argparser) argparser.add_argument('--use-current-config', dest='usecurrentconfig', action='store_true', - help=argparse.SUPPRESS) - #help='By default ifdown looks at the saved state for ' + - #'interfaces to bring down. This option allows ifdown to ' + - #'look at the current interfaces file. Useful when your ' + - #'state file is corrupted or you want down to use the latest ' - #'from the interfaces file') + help='By default ifdown looks at the saved state for ' + + 'interfaces to bring down. This option allows ifdown to ' + + 'look at the current interfaces file. Useful when your ' + + 'state file is corrupted or you want down to use the latest ' + 'from the interfaces file') def update_ifquery_argparser(argparser): """ arg parser for ifquery options """ @@ -229,12 +238,13 @@ def update_ifquery_argparser(argparser): group.add_argument('--print-savedstate', action='store_true', dest='printsavedstate', help=argparse.SUPPRESS) - argparser.add_argument('--format', dest='format', default='native', - choices=['native', 'json'], help=argparse.SUPPRESS) - argparser.add_argument('--print-dependency', + argparser.add_argument('-m', '--format', dest='format', default='native', + choices=['native', 'json'], + help='interface display format') + argparser.add_argument('-p', '--print-dependency', dest='printdependency', choices=['list', 'dot'], help='print interface dependency') - argparser.add_argument('--syntax-help', action='store_true', + argparser.add_argument('-s', '--syntax-help', action='store_true', dest='syntaxhelp', help='print supported interface config syntax') @@ -251,7 +261,7 @@ def update_ifreload_argparser(argparser): argparser.add_argument('-d', '--debug', dest='debug', action='store_true', help='output debug info') - argparser.add_argument('--with-depends', dest='withdepends', + argparser.add_argument('-w', '--with-depends', dest='withdepends', action='store_true', help=argparse.SUPPRESS) argparser.add_argument('--perfmode', dest='perfmode', action='store_true', help=argparse.SUPPRESS) @@ -260,8 +270,19 @@ def update_ifreload_argparser(argparser): argparser.add_argument('-X', '--exclude', dest='excludepats', action='append', help=argparse.SUPPRESS) - argparser.add_argument('-j', '--jobs', dest='jobs', type=int, - default=-1, choices=range(1,12), help=argparse.SUPPRESS) + #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, + # default=-1, choices=range(1,12), help=argparse.SUPPRESS) + argparser.add_argument('-i', '--interfaces', dest='interfacesfile', + default='/etc/network/interfaces', + help='use interfaces file instead of default ' + + '/etc/network/interfaces') + argparser.add_argument('--use-current-config', + dest='usecurrentconfig', action='store_true', + help='By default ifreload looks at saved state for ' + + 'interfaces to bring down. With this option ifreload will' + ' only look at the current interfaces file. Useful when your ' + + 'state file is corrupted or you want down to use the latest ' + 'from the interfaces file') def parse_args(argsv, op): if op == 'query': @@ -283,9 +304,7 @@ def parse_args(argsv, op): update_ifquery_argparser(argparser) elif op == 'reload': update_ifreload_argparser(argparser) - argcomplete.autocomplete(argparser) - return argparser.parse_args(argsv) handlers = {'up' : run_up, @@ -308,6 +327,16 @@ def validate_args(op, args): return False return True +def read_config(): + global configmap_g + + config = open(configfile, 'r').read() + configStr = '[ifupdown2]\n' + config + configFP = StringIO.StringIO(configStr) + parser = ConfigParser.RawConfigParser() + parser.readfp(configFP) + configmap_g = dict(parser.items('ifupdown2')) + def main(argv): """ main function """ args = None @@ -329,6 +358,7 @@ def main(argv): args = parse_args(argv[1:], op) if not validate_args(op, args): exit(1) + read_config() init(args) handlers.get(op)(args) except Exception, e: diff --git a/setup.py b/setup.py index 0ecc33f..4398e40 100755 --- a/setup.py +++ b/setup.py @@ -16,6 +16,8 @@ setup(name='ifupdown2', ('/etc/init.d/', ['init.d/networking']), ('/sbin/', ['sbin/ifupdown']), + ('/etc/network/ifupdown2/', + ['config/ifupdown2.conf', 'config/templates']), ('/usr/share/doc/ifupdown/examples/', ['docs/examples/interfaces'])] )