From 05ac52f075b3097c4952897765c350aabef5b8f8 Mon Sep 17 00:00:00 2001 From: Roopa Prabhu Date: Thu, 31 Mar 2016 16:09:37 -0700 Subject: [PATCH] addons: vrf: redo iproute2 vrf interface map handling Ticket: CM-10188, CM-10061 Reviewed By: dsa, nikhil, julien Testing Done: Tested static routes with vrf names for tables This patch does the following: - if a single vrf device is present in the config, builds the vrf map by reading vrf interfaces from the kernel (with existing link cache. Builds a shadow vrf only attribute cache) - reads existing table map and adjusts it if required - main change is the iproute2 map file on disk is updated immediately on vrf creation, so that static routes used along with the vrf slaves can use the vrf name for the table. This also helps dhclient dns hook script which may use mgmt table name directly. - cleans up default routes on down Signed-off-by: Roopa Prabhu --- addons/vrf.py | 185 ++++++++++++++++++++++++++----------- ifupdown/ifupdownmain.py | 2 + ifupdownaddons/cache.py | 2 + ifupdownaddons/iproute2.py | 19 +++- 4 files changed, 147 insertions(+), 61 deletions(-) diff --git a/addons/vrf.py b/addons/vrf.py index ff4a348..bb25427 100644 --- a/addons/vrf.py +++ b/addons/vrf.py @@ -81,38 +81,10 @@ class vrf(moduleBase): #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_initialized = False 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[int(t)] = v - else: - try: - # cleanup rules - self._del_vrf_rules(v, t) - except Exception: - pass - self.iproute2_vrf_map = iproute2_vrf_map_pruned + self.iproute2_vrf_map_fd = None + self.iproute2_vrf_map_sync_to_disk = False self.vrf_table_id_start = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-start') if not self.vrf_table_id_start: @@ -122,16 +94,6 @@ class vrf(moduleBase): self.vrf_table_id_end = self.VRF_TABLE_END self.vrf_max_count = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-max-count') - last_used_vrf_table = None - for t in range(self.vrf_table_id_start, - self.vrf_table_id_end): - if not self.iproute2_vrf_map.get(t): - break - last_used_vrf_table = t - self.last_used_vrf_table = last_used_vrf_table - - self.iproute2_write_vrf_map = False - atexit.register(self.iproute2_vrf_map_write) self.vrf_fix_local_table = True self.vrf_count = 0 self.vrf_cgroup_create = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-cgroup-create') @@ -141,19 +103,103 @@ class vrf(moduleBase): self.vrf_cgroup_create = True else: self.vrf_cgroup_create = False - self.vrf_mgmt_devname = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-mgmt-devname') - def iproute2_vrf_map_write(self): - if not self.iproute2_write_vrf_map: + def _iproute2_vrf_map_initialize(self): + if self._iproute2_vrf_map_initialized: return - self.logger.info('vrf: writing table map to %s' + + # XXX: check for vrf reserved overlap in /etc/iproute2/rt_tables + self.iproute2_vrf_map = {} + iproute2_vrf_map_force_rewrite = False + # read or create /etc/iproute2/rt_tables.d/ifupdown2.vrf_map + if os.path.exists(self.iproute2_vrf_filename): + vrf_map_fd = open(self.iproute2_vrf_filename, 'r+') + lines = vrf_map_fd.readlines() + for l in lines: + l = l.strip() + if l[0] == '#': + continue + try: + (table, vrf_name) = l.strip().split() + if self.iproute2_vrf_map.get(int(table)): + # looks like the existing file has + # duplicate entries, force rewrite of the + # file + iproute2_vrf_map_force_rewrite = True + continue + self.iproute2_vrf_map[int(table)] = vrf_name + except Exception, e: + self.logger.info('vrf: iproute2_vrf_map: unable to parse %s' + %l) + pass + + vrfs = self.ipcmd.link_get_vrfs() + running_vrf_map = {} + if vrfs: + for v, lattrs in vrfs.iteritems(): + table = lattrs.get('table', None) + if table: + running_vrf_map[int(table)] = v + + if running_vrf_map and (running_vrf_map != self.iproute2_vrf_map): + self.iproute2_vrf_map = running_vrf_map + iproute2_vrf_map_force_rewrite = True + + self.iproute2_vrf_map_fd = None + if iproute2_vrf_map_force_rewrite: + # reopen the file and rewrite the map + self._iproute2_vrf_map_open(True, False) + else: + self._iproute2_vrf_map_open(False, True) + + self.iproute2_vrf_map_sync_to_disk = False + atexit.register(self._iproute2_vrf_map_sync_to_disk) + + self.logger.info("vrf: dumping iproute2_vrf_map") + self.logger.info(self.iproute2_vrf_map) + + last_used_vrf_table = None + for t in range(self.vrf_table_id_start, + self.vrf_table_id_end): + if not self.iproute2_vrf_map.get(t): + break + last_used_vrf_table = t + self.last_used_vrf_table = last_used_vrf_table + self._iproute2_vrf_map_initialized = True + + def _iproute2_vrf_map_sync_to_disk(self): + if not self.iproute2_vrf_map_sync_to_disk: + return + self.logger.info('vrf: syncing table map to %s' %self.iproute2_vrf_filename) with open(self.iproute2_vrf_filename, 'w') as f: f.write(self.iproute2_vrf_filehdr %(self.vrf_table_id_start, self.vrf_table_id_end)) for t, v in self.iproute2_vrf_map.iteritems(): f.write('%s %s\n' %(t, v)) + f.flush() + + def _iproute2_vrf_map_open(self, sync_vrfs=False, append=False): + self.logger.info('vrf: syncing table map to %s' + %self.iproute2_vrf_filename) + fmode = 'a+' if append else 'w' + try: + self.iproute2_vrf_map_fd = open(self.iproute2_vrf_filename, + '%s' %fmode) + except Exception, e: + self.log_warn('vrf: error opening %s (%s)' + %(self.iproute2_vrf_filename, str(e))) + return + + if not append: + # write file header + self.iproute2_vrf_map_fd.write(self.iproute2_vrf_filehdr + %(self.vrf_table_id_start, + self.vrf_table_id_end)) + for t, v in self.iproute2_vrf_map.iteritems(): + self.iproute2_vrf_map_fd.write('%s %s\n' %(t, v)) + self.iproute2_vrf_map_fd.flush() def _is_vrf(self, ifaceobj): if ifaceobj.get_attr_value_first('vrf-table'): @@ -197,13 +243,19 @@ class vrf(moduleBase): return None def _iproute2_vrf_table_entry_add(self, vrf_dev_name, table_id): - self.iproute2_vrf_map[int(table_id)] = vrf_dev_name - self.iproute2_write_vrf_map = True + old_vrf_name = self.iproute2_vrf_map.get(int(table_id)) + if not old_vrf_name or (old_vrf_name != vrf_dev_name): + self.iproute2_vrf_map[int(table_id)] = vrf_dev_name + if self.iproute2_vrf_map_fd: + self.iproute2_vrf_map_fd.write('%s %s\n' + %(table_id, vrf_dev_name)) + self.iproute2_vrf_map_fd.flush() def _iproute2_vrf_table_entry_del(self, table_id): try: + # with any del of vrf map, we need to force sync to disk + self.iproute2_vrf_map_sync_to_disk = True del self.iproute2_vrf_map[int(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))) @@ -481,7 +533,7 @@ class vrf(moduleBase): return vrf_table - def _add_vrf_default_route(self, ifaceobj, vrf_table): + def _add_del_vrf_default_route(self, ifaceobj, vrf_table, add=True): vrf_default_route = ifaceobj.get_attr_value_first('vrf-default-route') if not vrf_default_route: vrf_default_route = policymanager.policymanager_api.get_attr_default( @@ -491,19 +543,33 @@ class vrf(moduleBase): return if str(vrf_default_route).lower() == "yes": try: - self.exec_command('ip route add table %s unreachable default' - ' metric %d' %(vrf_table, 240)) + if add: + self.exec_command('ip route add table %s unreachable ' + 'default metric %d' %(vrf_table, 240)) + else: + self.exec_command('ip route del table %s unreachable ' + 'default metric %d' %(vrf_table, 240)) except OSError, e: - if e.errno != 17: + if add and e.errno != 17: raise + else: + self.logger.info('%s: error deleting default route (%s)' + %(ifaceobj.name, str(e))) pass try: - self.exec_command('ip -6 route add table %s unreachable ' - 'default metric %d' %(vrf_table, 240)) + if add: + self.exec_command('ip -6 route add table %s unreachable ' + 'default metric %d' %(vrf_table, 240)) + else: + self.exec_command('ip -6 route del table %s unreachable ' + 'default metric %d' %(vrf_table, 240)) except OSError, e: - if e.errno != 17: + if add and e.errno != 17: raise + else: + self.logger.info('%s: error deleting default route (%s)' + %(ifaceobj.name, str(e))) pass def _up_vrf_dev(self, ifaceobj, vrf_table, add_slaves=True, @@ -521,7 +587,7 @@ class vrf(moduleBase): self._create_cgroup(ifaceobj) if add_slaves: self._add_vrf_slaves(ifaceobj, ifaceobj_getfunc) - self._add_vrf_default_route(ifaceobj, vrf_table) + self._add_del_vrf_default_route(ifaceobj, vrf_table) self._set_vrf_dev_processed_flag(ifaceobj) rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up") except Exception, e: @@ -596,6 +662,7 @@ class vrf(moduleBase): try: vrf_table = ifaceobj.get_attr_value_first('vrf-table') if vrf_table: + self._iproute2_vrf_map_initialize() # This is a vrf device if self.vrf_count == self.vrf_max_count: self.log_error('%s: max vrf count %d hit...not ' @@ -605,6 +672,7 @@ class vrf(moduleBase): else: vrf = ifaceobj.get_attr_value_first('vrf') if vrf: + self._iproute2_vrf_map_initialize() # This is a vrf slave self._up_vrf_slave(ifaceobj.name, vrf, ifaceobj, ifaceobj_getfunc) @@ -663,6 +731,7 @@ class vrf(moduleBase): try: self._iproute2_vrf_table_entry_del(vrf_table) + self._add_del_vrf_default_route(ifaceobj, vrf_table, False) self._delete_cgroup(ifaceobj) except Exception, e: self.logger.info('%s: %s' %(ifaceobj.name, str(e))) @@ -681,10 +750,12 @@ class vrf(moduleBase): try: vrf_table = ifaceobj.get_attr_value_first('vrf-table') if vrf_table: + self._iproute2_vrf_map_initialize() self._down_vrf_dev(ifaceobj, vrf_table, ifaceobj_getfunc) else: vrf = ifaceobj.get_attr_value_first('vrf') if vrf: + self._iproute2_vrf_map_initialize() self._down_vrf_slave(ifaceobj.name, ifaceobj, None) except Exception, e: self.log_warn(str(e)) @@ -729,10 +800,12 @@ class vrf(moduleBase): try: vrf_table = ifaceobj.get_attr_value_first('vrf-table') if vrf_table: + self._iproute2_vrf_map_initialize() self._query_check_vrf_dev(ifaceobj, ifaceobjcurr, vrf_table) else: vrf = ifaceobj.get_attr_value_first('vrf') if vrf: + self._iproute2_vrf_map_initialize() self._query_check_vrf_slave(ifaceobj, ifaceobjcurr, vrf) except Exception, e: self.log_warn(str(e)) diff --git a/ifupdown/ifupdownmain.py b/ifupdown/ifupdownmain.py index cb5ac7a..0f6a019 100644 --- a/ifupdown/ifupdownmain.py +++ b/ifupdown/ifupdownmain.py @@ -1440,6 +1440,8 @@ class ifupdownMain(ifupdownBase): else: # oldconfig not available, continue with 'up' with new config op = 'up' + new_ifaceobjdict = self.ifaceobjdict + new_dependency_graph = self.dependency_graph if op == 'reload' and ifacenames: ifacenames = self.ifaceobjdict.keys() diff --git a/ifupdownaddons/cache.py b/ifupdownaddons/cache.py index 61773b5..311082e 100644 --- a/ifupdownaddons/cache.py +++ b/ifupdownaddons/cache.py @@ -23,6 +23,8 @@ class linkCache(): : { } """ links = {} + vrfs = {} + @classmethod def get_attr(cls, mapList): return reduce(lambda d, k: d[k], mapList, linkCache.links) diff --git a/ifupdownaddons/iproute2.py b/ifupdownaddons/iproute2.py index add2782..9d842f9 100644 --- a/ifupdownaddons/iproute2.py +++ b/ifupdownaddons/iproute2.py @@ -24,10 +24,16 @@ class iproute2(utilsBase): def __init__(self, *args, **kargs): utilsBase.__init__(self, *args, **kargs) - if self.CACHE and not iproute2._cache_fill_done: + if self.CACHE: + self._fill_cache() + + def _fill_cache(self): + if not iproute2._cache_fill_done: self._link_fill() self._addr_fill() iproute2._cache_fill_done = True + return True + return False def _link_fill(self, ifacename=None, refresh=False): """ fills cache with link information @@ -99,6 +105,7 @@ class iproute2(utilsBase): vattrs = {'table' : citems[i+2]} linkattrs['linkinfo'] = vattrs linkattrs['kind'] = 'vrf' + linkCache.vrfs[ifname] = vattrs break elif citems[i] == 'vrf_slave': linkattrs['kind'] = 'vrf_slave' @@ -173,10 +180,8 @@ class iproute2(utilsBase): if self.DRYRUN: return False if self.CACHE: - if not iproute2._cache_fill_done: - self._link_fill() - self._addr_fill() - iproute2._cache_fill_done = True + if self._fill_cache(): + # if we filled the cache, return new data return linkCache.get_attr(attrlist) if not refresh: return linkCache.get_attr(attrlist) @@ -847,3 +852,7 @@ class iproute2(utilsBase): return os.path.basename(upper[0])[6:] except: return None + + def link_get_vrfs(self): + self._fill_cache() + return linkCache.vrfs