From 8465de90770caaa134bdd0364379fc39939a40f5 Mon Sep 17 00:00:00 2001 From: Roopa Prabhu Date: Thu, 28 Jan 2016 14:00:09 -0800 Subject: [PATCH] addons: support for new addon module for vrf This patch adds initial support for vrf in ifupdown2. Example interfaces file section: auto swp1.100 iface swp1.100 vrf blue auto blue iface blue vrf-table 10 iproute2 vrf map is generated under: /etc/iproute2/rt_tables.d/ifupdown2.vrf_map this patch also adds prelimnary support for 'vrf-table auto'. But this needs more work. Signed-off-by: Roopa Prabhu --- addons/vrf.py | 373 +++++++++++++++++++++++++++++++++++++ config/addons.conf | 2 + ifupdownaddons/iproute2.py | 24 ++- setup.py | 2 +- 4 files changed, 397 insertions(+), 4 deletions(-) create mode 100644 addons/vrf.py diff --git a/addons/vrf.py b/addons/vrf.py new file mode 100644 index 0000000..d087059 --- /dev/null +++ b/addons/vrf.py @@ -0,0 +1,373 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +import atexit +from ifupdown.iface import * +import ifupdownaddons +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.bondutil import bondutil +from ifupdownaddons.iproute2 import iproute2 + +class vrf(moduleBase): + """ ifupdown2 addon module to configure vrfs """ + _modinfo = { 'mhelp' : 'vrf configuration module', + 'attrs' : { + 'vrf-table': + {'help' : 'vrf device table id. key to ' + + 'creating a vrf device', + 'example': ['vrf-table-id 1']}, + 'vrf': + {'help' : 'vrf the interface is part of.', + 'example': ['vrf blue']}}} + + iproute2_vrf_filename = '/etc/iproute2/rt_tables.d/ifupdown2.vrf_map' + iproute2_vrf_filehdr = '# This file is autogenerated by ifupdown2.\n' + \ + '# It contains the vrf name to table mapping.\n' + \ + '# Reserved table range 150-200\n' + vrf_table_reserved_start = 150 + vrf_table_reserved_end = 200 + + def __init__(self, *args, **kargs): + ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.bondcmd = None + try: + ip_rules = self.exec_command('/sbin/ip rule show').splitlines() + self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules] + except Exception, e: + self.ip_rule_cache = [] + self.logger.warn('%s' %str(e)) + + try: + ip_rules = self.exec_command('/sbin/ip -6 rule show').splitlines() + self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules] + except Exception, e: + self.ip6_rule_cache = [] + self.logger.warn('%s' %str(e)) + + #self.logger.debug("vrf: ip rule cache") + #self.logger.info(self.ip_rule_cache) + + #self.logger.info("vrf: ip -6 rule cache") + #self.logger.info(self.ip6_rule_cache) + + # XXX: check for vrf reserved overlap in /etc/iproute2/rt_tables + self.iproute2_vrf_map = {} + # read or create /etc/iproute2/rt_tables.d/ifupdown2.vrf_map + if os.path.exists(self.iproute2_vrf_filename): + self.vrf_map_fd = open(self.iproute2_vrf_filename, 'a+') + lines = self.vrf_map_fd.readlines() + for l in lines: + l = l.strip() + if l[0] == '#': + continue + try: + (table, vrf_name) = l.strip().split() + self.iproute2_vrf_map[table] = vrf_name + except Exception, e: + self.logger.info('vrf: iproute2_vrf_map: unable to parse %s' + %l) + pass + #self.logger.info("vrf: dumping iproute2_vrf_map") + #self.logger.info(self.iproute2_vrf_map) + + # purge vrf table entries that are not around + iproute2_vrf_map_pruned = {} + for t, v in self.iproute2_vrf_map.iteritems(): + if os.path.exists('/sys/class/net/%s' %v): + iproute2_vrf_map_pruned[t] = v + else: + try: + # cleanup rules + self._del_vrf_rules(v, t) + except Exception: + pass + self.iproute2_vrf_map = iproute2_vrf_map_pruned + + last_used_vrf_table = self.vrf_table_reserved_start + for t in range(self.vrf_table_reserved_start, + self.vrf_table_reserved_end): + last_used_vrf_table = t + if not self.iproute2_vrf_map.get(t): + break + self.last_used_vrf_table = last_used_vrf_table + self.iproute2_write_vrf_map = False + atexit.register(self.iproute2_vrf_map_write) + + def iproute2_vrf_map_write(self): + if not self.iproute2_write_vrf_map: + return + self.logger.info('vrf: writing table map to %s' + %self.iproute2_vrf_filename) + with open(self.iproute2_vrf_filename, 'w') as f: + f.write(self.iproute2_vrf_filehdr) + for t, v in self.iproute2_vrf_map.iteritems(): + f.write('%s %s\n' %(t, v)) + + def _is_vrf(self, ifaceobj): + if ifaceobj.get_attr_value_first('vrf-table'): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + """ Returns list of interfaces dependent on ifaceobj """ + + vrf_iface_name = ifaceobj.get_attr_value_first('vrf') + if not vrf_iface_name: + return None + return [vrf_iface_name] + + def get_dependent_ifacenames_running(self, ifaceobj): + return None + + def _get_iproute2_vrf_table(self, vrf_dev_name): + for t, v in self.iproute2_vrf_map.iteritems(): + if v == vrf_dev_name: + return t + return None + + def _get_avail_vrf_table_id(self): + for t in range(self.last_used_vrf_table + 1, + self.vrf_table_reserved_end): + if not self.iproute2_vrf_map.get(t): + self.last_used_vrf_table = t + return t + return None + + def _iproute2_vrf_table_entry_add(self, vrf_dev_name, table_id): + self.iproute2_vrf_map[table_id] = vrf_dev_name + self.iproute2_write_vrf_map = True + + def _iproute2_vrf_table_entry_del(self, table_id): + try: + del self.iproute2_vrf_map[table_id] + self.iproute2_write_vrf_map = True + except Exception, e: + self.logger.info('vrf: iproute2 vrf map del failed for %d (%s)' + %(table_id, str(e))) + pass + + def _up_vrf_slave(self, ifaceobj, vrf): + try: + self.ipcmd.link_set(ifaceobj.name, 'master', vrf) + except Exception, e: + self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) + + def _del_vrf_rules(self, vrf_dev_name, vrf_table): + pref = 200 + ip_rule_out_format = '%s: from all %s %s lookup %s' + ip_rule_cmd = 'ip %s rule del pref %s %s %s table %s' + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_table) + if rule in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, vrf_table) + self.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_table) + if rule in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, vrf_table) + self.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_table) + if rule in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, + vrf_table) + self.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_table) + if rule in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name, + vrf_table) + self.exec_command(rule_cmd) + + def _add_vrf_rules(self, vrf_dev_name, vrf_table): + pref = 200 + ip_rule_out_format = '%s: from all %s %s lookup %s' + ip_rule_cmd = 'ip %s rule add pref %s %s %s table %s' + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_table) + if rule not in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, vrf_table) + self.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_table) + if rule not in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, vrf_table) + self.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_table) + if rule not in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, + vrf_table) + self.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_table) + if rule not in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name, + vrf_table) + self.exec_command(rule_cmd) + + + def _up_vrf_dev(self, ifaceobj, vrf_table): + if vrf_table == 'auto': + vrf_table = _get_avail_vrf_table_id(ifaceobj.name) + + try: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'vrf', + {'table' : '%s' %vrf_table}) + self._iproute2_vrf_table_entry_add(ifaceobj.name, vrf_table) + self._add_vrf_rules(ifaceobj.name, vrf_table) + except Exception, e: + self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) + + def _up(self, ifaceobj): + try: + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if vrf_table: + self._up_vrf_dev(ifaceobj, vrf_table) + else: + vrf = ifaceobj.get_attr_value_first('vrf') + if vrf: + self._up_vrf_slave(ifaceobj, vrf) + except Exception, e: + self.log_error(str(e)) + + def _down_vrf_dev(self, ifaceobj, vrf_table): + if vrf_table == 'auto': + vrf_table = self._get_iproute2_vrf_table(ifaceobj.name) + try: + self.ipcmd.link_delete(ifaceobj.name) + self._iproute2_vrf_table_entry_del(vrf_table) + self._del_vrf_rules(ifaceobj.name, vrf_table) + except Exception, e: + self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) + + def _down_vrf_slave(self, ifaceobj, vrf): + try: + self.ipcmd.link_set(ifaceobj.name, 'nomaster') + except Exception, e: + self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) + + def _down(self, ifaceobj): + try: + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if vrf_table: + self._down_vrf_dev(ifaceobj, vrf_table) + else: + vrf = ifaceobj.get_attr_value_first('vrf') + if vrf: + self._down_vrf_slave(ifaceobj, vrf) + except Exception, e: + self.log_warn(str(e)) + + def _query_check_vrf_slave(self, ifaceobj, ifaceobjcurr, vrf): + try: + master = self.ipcmd.link_get_master(ifacename) + if not master or master != vrf: + ifaceobjcurr.update_config_with_status('vrf', master, 1) + else: + ifaceobjcurr.update_config_with_status('vrf', master, 0) + except Exception, e: + self.log_warn(str(e)) + + def _query_check_vrf_dev(self, ifaceobj, ifaceobjcurr, vrf_table): + try: + if not self.ipcmd.link_exists(ifaceobj.name): + self.logger.info('%s: vrf: does not exist' %(ifaceobj.name)) + return + if vrf_table == 'auto': + config_table = self._get_iproute2_vrf_table(ifaceobj.name) + else: + config_table = vrf_table + vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name) + if not vrfdev_attrs: + ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1) + return + running_table = vrfdev_attrs.get('table') + if not running_table: + ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1) + return + if config_table != running_table: + ifaceobjcurr.update_config_with_status('vrf-table', + running_table, 1) + else: + ifaceobjcurr.update_config_with_status('vrf-table', + running_table, 0) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + try: + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if vrf_table: + self._query_check_vrf_dev(ifaceobj, ifaceobjcurr, vrf_table) + else: + vrf = ifaceobj.get_attr_value_first('vrf') + if vrf: + self._query_check_vrf_slave(ifaceobj, ifaceobjcurr, vrf) + except Exception, e: + self.log_warn(str(e)) + + def _query_running(self, ifaceobjrunning): + try: + kind = self.ipcmd.link_get_kind(ifaceobjrunning.name) + if kind == 'vrf': + vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name) + if vrfdev_attrs: + running_table = vrfdev_attrs.get('table') + if running_table: + ifaceobjrunning.update_config('vrf-table', + running_table) + elif kind == 'vrf_slave': + vrf = self.link_get_master(ifaceobjrunning.name) + if vrf: + ifaceobjrunning.update_config('vrf', vrf) + except Exception, e: + self.log_warn(str(e)) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-running' : _query_running, + 'query-checkcurr' : _query_check} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.bondcmd: + self.bondcmd = bondutil(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run bond configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/config/addons.conf b/config/addons.conf index ebfeb7b..73fc699 100644 --- a/config/addons.conf +++ b/config/addons.conf @@ -7,6 +7,7 @@ pre-up,usercmds pre-up,bridge pre-up,bridgevlan pre-up,mstpctl +pre-up,vrf up,dhcp up,address up,addressvirtual @@ -22,6 +23,7 @@ down,dhcp down,addressvirtual down,address down,usercmds +post-down,vrf post-down,clagd post-down,mstpctl post-down,bridgevlan diff --git a/ifupdownaddons/iproute2.py b/ifupdownaddons/iproute2.py index a09234c..b8f7ae2 100644 --- a/ifupdownaddons/iproute2.py +++ b/ifupdownaddons/iproute2.py @@ -585,6 +585,9 @@ class iproute2(utilsBase): def get_vxlandev_attrs(self, ifacename): return self._cache_get('link', [ifacename, 'linkinfo']) + def link_get_linkinfo_attrs(self, ifacename): + return self._cache_get('link', [ifacename, 'linkinfo']) + def link_get_mtu(self, ifacename): return self._cache_get('link', [ifacename, 'mtu']) @@ -600,13 +603,17 @@ class iproute2(utilsBase): %ifacename) return address - def link_create(self, ifacename, type, link=None): + def link_create(self, ifacename, type, attrs={}): + """ generic link_create function """ if self.link_exists(ifacename): return cmd = 'link add' - if link: - cmd += ' link %s' %link cmd += ' name %s type %s' %(ifacename, type) + if attrs: + for k, v in attrs.iteritems(): + cmd += ' %s' %k + if v: + cmd += ' %s' %v if self.ipbatch and not self.ipbatch_pause: self.add_to_batch(cmd) else: @@ -623,6 +630,17 @@ class iproute2(utilsBase): self.exec_command('ip %s' %cmd) self._cache_invalidate() + def link_get_master(self, ifacename): + sysfs_master_path = '/sys/class/net/%s/master' %ifacename + if os.path.exists(sysfs_master_path): + link_path = os.readlink(sysfs_master_path) + if link_path: + return os.path.basename(link_path) + else: + return None + else: + return self._cache_get('link', [ifacename, 'master']) + def bridge_port_vids_add(self, bridgeportname, vids): [self.exec_command('bridge vlan add vid %s dev %s' %(v, bridgeportname)) for v in vids] diff --git a/setup.py b/setup.py index 3629a9a..70ba6e2 100755 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup(name='ifupdown2', 'addons/dhcp.py', 'addons/usercmds.py', 'addons/ethtool.py', 'addons/addressvirtual.py', 'addons/vxlan.py', - 'addons/link.py', + 'addons/link.py', 'addons/vrf.py', 'addons/bridgevlan.py']), ('/etc/network/ifupdown2/', ['config/addons.conf']), ('/var/lib/ifupdown2/policy.d/', []), -- 2.39.5