#!/usr/bin/python
+#
+# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
+# Author: Roopa Prabhu, roopa@cumulusnetworks.com
+#
+
-from ifupdown.iface import *
-from ifupdownaddons.modulebase import moduleBase
-from ifupdownaddons.iproute2 import iproute2
-import ifupdown.rtnetlink_api as rtnetlink_api
-import logging
from sets import Set
+from ipaddr import IPNetwork, IPv4Address, IPv4Network, AddressValueError
+
+try:
+ import ifupdown2.ifupdown.policymanager as policymanager
+
+ from ifupdown2.nlmanager.nlmanager import Link
+
+ from ifupdown2.ifupdown.iface import *
+ from ifupdown2.ifupdown.utils import utils
+ from ifupdown2.ifupdown.netlink import netlink
+ from ifupdown2.ifupdownaddons.cache import *
+ from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils
+ from ifupdown2.ifupdownaddons.modulebase import moduleBase
+except ImportError:
+ import ifupdown.policymanager as policymanager
+
+ from nlmanager.nlmanager import Link
+
+ from ifupdown.iface import *
+ from ifupdown.utils import utils
+ from ifupdown.netlink import netlink
+
+ from ifupdownaddons.cache import *
+ from ifupdownaddons.LinkUtils import LinkUtils
+ from ifupdownaddons.modulebase import moduleBase
+
class vxlan(moduleBase):
_modinfo = {'mhelp' : 'vxlan module configures vxlan interfaces.',
'attrs' : {
'vxlan-id' :
{'help' : 'vxlan id',
+ 'validrange' : ['1', '16777214'],
'required' : True,
'example': ['vxlan-id 100']},
'vxlan-local-tunnelip' :
{'help' : 'vxlan local tunnel ip',
+ 'validvals' : ['<ipv4>'],
'example': ['vxlan-local-tunnelip 172.16.20.103']},
'vxlan-svcnodeip' :
{'help' : 'vxlan id',
+ 'validvals' : ['<ipv4>'],
'example': ['vxlan-svcnodeip 172.16.22.125']},
'vxlan-remoteip' :
{'help' : 'vxlan remote ip',
- 'example': ['vxlan-remoteip 172.16.22.127']},
+ 'validvals' : ['<ipv4>'],
+ 'example': ['vxlan-remoteip 172.16.22.127'],
+ 'multiline': True},
'vxlan-learning' :
- {'help' : 'vxlan learning on/off',
- 'example': ['vxlan-learning off'],
- 'default': 'on'},
+ {'help' : 'vxlan learning yes/no',
+ 'validvals' : ['yes', 'no', 'on', 'off'],
+ 'example': ['vxlan-learning no'],
+ 'default': 'yes'},
+ 'vxlan-ageing' :
+ {'help' : 'vxlan aging timer',
+ 'validrange' : ['0', '4096'],
+ 'example': ['vxlan-ageing 300'],
+ 'default': '300'},
+ 'vxlan-purge-remotes' :
+ {'help' : 'vxlan purge existing remote entries',
+ 'validvals' : ['yes', 'no'],
+ 'example': ['vxlan-purge-remotes yes'],},
+ 'vxlan-port': {
+ 'help': 'vxlan UDP port (transmitted to vxlan driver)',
+ 'validvals': ['<number>'],
+ 'example': ['vxlan-port 4789'],
+ 'validrange': ['1', '65536'],
+ 'default': '4789',
+ },
+ 'vxlan-physdev':
+ {'help': 'vxlan physical device',
+ 'example': ['vxlan-physdev eth1']},
+
}}
+ _clagd_vxlan_anycast_ip = ""
+ _vxlan_local_tunnelip = None
def __init__(self, *args, **kargs):
moduleBase.__init__(self, *args, **kargs)
self.ipcmd = None
+ purge_remotes = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vxlan-purge-remotes')
+ if purge_remotes:
+ self._purge_remotes = utils.get_boolean_from_string(purge_remotes)
+ else:
+ self._purge_remotes = False
+
+ def syntax_check(self, ifaceobj, ifaceobj_getfunc):
+ if self._is_vxlan_device(ifaceobj):
+ if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not vxlan._vxlan_local_tunnelip:
+ self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name)
+ return False
+ return self.syntax_check_localip_anycastip_equal(
+ ifaceobj.name,
+ ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or vxlan._vxlan_local_tunnelip,
+ vxlan._clagd_vxlan_anycast_ip
+ )
+ return True
+
+ def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip):
+ try:
+ if IPNetwork(local_ip) == IPNetwork(anycast_ip):
+ self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
+ % (ifname, local_ip))
+ return False
+ except:
+ pass
+ return True
def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None):
- if not self._is_vxlan_device(ifaceobj):
- return None
- ifaceobj.link_kind |= ifaceLinkKind.VXLAN
+ if self._is_vxlan_device(ifaceobj):
+ ifaceobj.link_kind |= ifaceLinkKind.VXLAN
+ self._set_global_local_ip(ifaceobj)
+ elif ifaceobj.name == 'lo':
+ clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip')
+ if clagd_vxlan_list:
+ if len(clagd_vxlan_list) != 1:
+ self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
+ % (ifaceobj.name,))
+ vxlan._clagd_vxlan_anycast_ip = clagd_vxlan_list[0]
+
+ self._set_global_local_ip(ifaceobj)
+
+ # If we should use a specific underlay device for the VXLAN
+ # tunnel make sure this device is set up before the VXLAN iface.
+ physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
+
+ if physdev:
+ return [physdev]
+
return None
+ def _set_global_local_ip(self, ifaceobj):
+ vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
+ if vxlan_local_tunnel_ip and not vxlan._vxlan_local_tunnelip:
+ vxlan._vxlan_local_tunnelip = vxlan_local_tunnel_ip
+
def _is_vxlan_device(self, ifaceobj):
if ifaceobj.get_attr_value_first('vxlan-id'):
return True
return False
- def _up(self, ifaceobj):
+ def _get_purge_remotes(self, ifaceobj):
+ if not ifaceobj:
+ return self._purge_remotes
+ purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes')
+ if purge_remotes:
+ purge_remotes = utils.get_boolean_from_string(purge_remotes)
+ else:
+ purge_remotes = self._purge_remotes
+ return purge_remotes
+
+ def should_create_set_vxlan(self, link_exists, ifname, vxlan_id, local, learning, ageing, group):
+ """
+ should we issue a netlink: ip link add dev %ifname type vxlan ...?
+ checking each attribute against the cache
+ """
+ if not link_exists:
+ return True
+
+ try:
+ if ageing:
+ ageing = int(ageing)
+ except:
+ pass
+
+ for attr_list, value in (
+ ((ifname, 'linkinfo', Link.IFLA_VXLAN_ID), vxlan_id),
+ ((ifname, 'linkinfo', Link.IFLA_VXLAN_AGEING), ageing),
+ ((ifname, 'linkinfo', Link.IFLA_VXLAN_LOCAL), local),
+ ((ifname, 'linkinfo', Link.IFLA_VXLAN_LEARNING), learning),
+ ((ifname, 'linkinfo', Link.IFLA_VXLAN_GROUP), group),
+ ):
+ if value and not self.ipcmd.cache_check(attr_list, value):
+ return True
+ return False
+
+ def _vxlan_create(self, ifaceobj):
vxlanid = ifaceobj.get_attr_value_first('vxlan-id')
if vxlanid:
- self.ipcmd.link_create_vxlan(ifaceobj.name, vxlanid,
- localtunnelip=ifaceobj.get_attr_value_first('vxlan-local-tunnelip'),
- svcnodeips=ifaceobj.get_attr_value('vxlan-svcnodeip'),
- remoteips=ifaceobj.get_attr_value('vxlan-remoteip'),
- learning=ifaceobj.get_attr_value_first('vxlan-learning'),
- ageing=ifaceobj.get_attr_value_first('vxlan-ageing'))
- if ifaceobj.addr_method == 'manual':
- rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up")
+ ifname = ifaceobj.name
+ anycastip = self._clagd_vxlan_anycast_ip
+ group = ifaceobj.get_attr_value_first('vxlan-svcnodeip')
+
+ local = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
+ if not local and vxlan._vxlan_local_tunnelip:
+ local = vxlan._vxlan_local_tunnelip
+
+ self.syntax_check_localip_anycastip_equal(ifname, local, anycastip)
+ # if both local-ip and anycast-ip are identical the function prints a warning
+
+ ageing = ifaceobj.get_attr_value_first('vxlan-ageing')
+ vxlan_port = ifaceobj.get_attr_value_first('vxlan-port')
+ physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
+ purge_remotes = self._get_purge_remotes(ifaceobj)
+
+ link_exists = self.ipcmd.link_exists(ifname)
+
+ if (not link_exists or
+ not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT):
+ vxlan_learning = ifaceobj.get_attr_value_first('vxlan-learning')
+ if not vxlan_learning:
+ vxlan_learning = self.get_attr_default_value('vxlan-learning')
+ learning = utils.get_boolean_from_string(vxlan_learning)
+ else:
+ learning = utils.get_boolean_from_string(
+ self.ipcmd.get_vxlandev_learning(ifname))
+
+ if link_exists:
+ vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifname)
+ # on ifreload do not overwrite anycast_ip to individual ip
+ # if clagd has modified
+ if vxlanattrs:
+ running_localtunnelip = vxlanattrs.get('local')
+ if (anycastip and running_localtunnelip and
+ anycastip == running_localtunnelip):
+ local = running_localtunnelip
+ if vxlanattrs.get('vxlanid') != vxlanid:
+ self.log_error('%s: Cannot change running vxlan id: '
+ 'Operation not supported' % ifname, ifaceobj)
+ try:
+ vxlanid = int(vxlanid)
+ except:
+ self.log_error('%s: invalid vxlan-id \'%s\'' % (ifname, vxlanid), ifaceobj)
+
+ if not group:
+ group = policymanager.policymanager_api.get_attr_default(
+ module_name=self.__class__.__name__,
+ attr='vxlan-svcnodeip'
+ )
+
+ if group:
+ try:
+ group = IPv4Address(group)
+ except AddressValueError:
+ try:
+ group_ip = IPv4Network(group).ip
+ self.logger.warning('%s: vxlan-svcnodeip %s: netmask ignored' % (ifname, group))
+ group = group_ip
+ except:
+ raise Exception('%s: invalid vxlan-svcnodeip %s: must be in ipv4 format' % (ifname, group))
+
+ if not local:
+ local = policymanager.policymanager_api.get_attr_default(
+ module_name=self.__class__.__name__,
+ attr='vxlan-local-tunnelip'
+ )
+
+ if local:
+ try:
+ local = IPv4Address(local)
+ except AddressValueError:
+ try:
+ local_ip = IPv4Network(local).ip
+ self.logger.warning('%s: vxlan-local-tunnelip %s: netmask ignored' % (ifname, local))
+ local = local_ip
+ except:
+ raise Exception('%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format' % (ifname, local))
+
+ if not ageing:
+ ageing = policymanager.policymanager_api.get_attr_default(
+ module_name=self.__class__.__name__,
+ attr='vxlan-ageing'
+ )
+
+ if not ageing and link_exists:
+ # if link doesn't exist we let the kernel define ageing
+ ageing = self.get_attr_default_value('vxlan-ageing')
+
+ if not vxlan_port:
+ vxlan_port = policymanager.policymanager_api.get_attr_default(
+ module_name=self.__class__.__name__,
+ attr='vxlan-port'
+ )
+
+ try:
+ vxlan_port = int(vxlan_port)
+ except TypeError:
+ # TypeError means vxlan_port was None
+ # ie: not provided by the user or the policy
+ vxlan_port = netlink.VXLAN_UDP_PORT
+ except ValueError as e:
+ self.logger.warning('%s: vxlan-port: using default %s: invalid configured value %s' % (ifname, netlink.VXLAN_UDP_PORT, str(e)))
+ vxlan_port = netlink.VXLAN_UDP_PORT
+
+ if link_exists:
+ cache_port = vxlanattrs.get(Link.IFLA_VXLAN_PORT)
+ if vxlan_port != cache_port:
+ self.logger.warning('%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s'
+ % (ifname, cache_port, ifname, ifname))
+ vxlan_port = cache_port
+
+ if self.should_create_set_vxlan(link_exists, ifname, vxlanid, local, learning, ageing, group):
+ try:
+ netlink.link_add_vxlan(ifname, vxlanid,
+ local=local,
+ learning=learning,
+ ageing=ageing,
+ group=group,
+ dstport=vxlan_port,
+ physdev=physdev)
+ except Exception as e_netlink:
+ self.logger.debug('%s: vxlan netlink: %s' % (ifname, str(e_netlink)))
+ try:
+ self.ipcmd.link_create_vxlan(ifname, vxlanid,
+ localtunnelip=local,
+ svcnodeip=group,
+ remoteips=ifaceobj.get_attr_value('vxlan-remoteip'),
+ learning='on' if learning else 'off',
+ ageing=ageing)
+ except Exception as e_iproute2:
+ self.logger.warning('%s: vxlan add/set failed: %s' % (ifname, str(e_iproute2)))
+ return
+
+ try:
+ # manually adding an entry to the caching after creating/updating the vxlan
+ if not ifname in linkCache.links:
+ linkCache.links[ifname] = {'linkinfo': {}}
+ linkCache.links[ifname]['linkinfo'].update({
+ 'learning': learning,
+ Link.IFLA_VXLAN_LEARNING: learning,
+ 'vxlanid': str(vxlanid),
+ Link.IFLA_VXLAN_ID: vxlanid
+ })
+ if ageing:
+ linkCache.links[ifname]['linkinfo'].update({
+ 'ageing': ageing,
+ Link.IFLA_VXLAN_AGEING: int(ageing)
+ })
+ except:
+ pass
+ else:
+ self.logger.info('%s: vxlan already exists' % ifname)
+ # if the vxlan already exists it's already cached
+
+ remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
+ if remoteips:
+ try:
+ for remoteip in remoteips:
+ IPv4Address(remoteip)
+ except Exception as e:
+ self.log_error('%s: vxlan-remoteip: %s' %(ifaceobj.name, str(e)))
+
+ if purge_remotes or remoteips:
+ # figure out the diff for remotes and do the bridge fdb updates
+ # only if provisioned by user and not by an vxlan external
+ # controller.
+ peers = self.ipcmd.get_vxlan_peers(ifaceobj.name, group)
+ if local and remoteips and local in remoteips:
+ remoteips.remove(local)
+ cur_peers = set(peers)
+ if remoteips:
+ new_peers = set(remoteips)
+ del_list = cur_peers.difference(new_peers)
+ add_list = new_peers.difference(cur_peers)
+ else:
+ del_list = cur_peers
+ add_list = []
+
+ for addr in del_list:
+ try:
+ self.ipcmd.bridge_fdb_del(ifaceobj.name,
+ '00:00:00:00:00:00',
+ None, True, addr)
+ except:
+ pass
+
+ for addr in add_list:
+ try:
+ self.ipcmd.bridge_fdb_append(ifaceobj.name,
+ '00:00:00:00:00:00',
+ None, True, addr)
+ except:
+ pass
+
+ def _up(self, ifaceobj):
+ self._vxlan_create(ifaceobj)
def _down(self, ifaceobj):
try:
except Exception, e:
self.log_warn(str(e))
- def _query_check_n_update(self, ifaceobjcurr, attrname, attrval,
+ def _query_check_n_update(self, ifaceobj, ifaceobjcurr, attrname, attrval,
running_attrval):
+ if not ifaceobj.get_attr_value_first(attrname):
+ return
if running_attrval and attrval == running_attrval:
ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
else:
def _query_check_n_update_addresses(self, ifaceobjcurr, attrname,
addresses, running_addresses):
if addresses:
- for a in addresses:
+ for a in addresses:
if a in running_addresses:
ifaceobjcurr.update_config_with_status(attrname, a, 0)
else:
ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj,
self.get_mod_attrs(), -1)
return
- self._query_check_n_update(ifaceobjcurr, 'vxlan-id',
- ifaceobj.get_attr_value_first('vxlan-id'),
+ self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-id',
+ ifaceobj.get_attr_value_first('vxlan-id'),
vxlanattrs.get('vxlanid'))
- self._query_check_n_update(ifaceobjcurr, 'vxlan-local-tunnelip',
- ifaceobj.get_attr_value_first('vxlan-local-tunnelip'),
- vxlanattrs.get('local'))
+ self._query_check_n_update(
+ ifaceobj,
+ ifaceobjcurr,
+ 'vxlan-port',
+ ifaceobj.get_attr_value_first('vxlan-port'),
+ str(vxlanattrs.get(Link.IFLA_VXLAN_PORT))
+ )
+
+ running_attrval = vxlanattrs.get('local')
+ attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
+ if not attrval:
+ attrval = vxlan._vxlan_local_tunnelip
+ ifaceobj.update_config('vxlan-local-tunnelip', attrval)
- self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-svcnodeip',
- ifaceobj.get_attr_value('vxlan-svcnodeip'),
- vxlanattrs.get('svcnode', []))
+ if running_attrval == self._clagd_vxlan_anycast_ip:
+ # if local ip is anycast_ip, then let query_check to go through
+ attrval = self._clagd_vxlan_anycast_ip
+ self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-local-tunnelip',
+ attrval, running_attrval)
- self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-remoteip',
- ifaceobj.get_attr_value('vxlan-remoteip'),
- vxlanattrs.get('remote', []))
+ self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-svcnodeip',
+ ifaceobj.get_attr_value_first('vxlan-svcnodeip'),
+ vxlanattrs.get('svcnode'))
+
+ purge_remotes = self._get_purge_remotes(ifaceobj)
+ if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
+ # If purge remotes or if vxlan-remoteip's are set
+ # in the config file, we are owners of the installed
+ # remote-ip's, lets check and report any remote ips we don't
+ # understand
+ self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-remoteip',
+ ifaceobj.get_attr_value('vxlan-remoteip'),
+ self.ipcmd.get_vxlan_peers(ifaceobj.name, vxlanattrs.get('svcnode')))
learning = ifaceobj.get_attr_value_first('vxlan-learning')
- if not learning:
- learning = 'on'
- running_learning = vxlanattrs.get('learning')
- if learning == running_learning:
- ifaceobjcurr.update_config_with_status('vxlan-learning',
- running_learning, 0)
- else:
- ifaceobjcurr.update_config_with_status('vxlan-learning',
- running_learning, 1)
+ if learning:
+ running_learning = vxlanattrs.get('learning')
+ if learning == 'yes' and running_learning == 'on':
+ running_learning = 'yes'
+ elif learning == 'no' and running_learning == 'off':
+ running_learning = 'no'
+ if learning == running_learning:
+ ifaceobjcurr.update_config_with_status('vxlan-learning',
+ running_learning, 0)
+ else:
+ ifaceobjcurr.update_config_with_status('vxlan-learning',
+ running_learning, 1)
+ ageing = ifaceobj.get_attr_value_first('vxlan-ageing')
+ if not ageing:
+ ageing = self.get_mod_subattr('vxlan-ageing', 'default')
+ self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-ageing',
+ ageing, vxlanattrs.get('ageing'))
+
+ physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
+
+ if physdev:
+ ifla_vxlan_link = vxlanattrs.get(Link.IFLA_VXLAN_LINK)
+
+ if ifla_vxlan_link:
+ self._query_check_n_update(
+ ifaceobj,
+ ifaceobjcurr,
+ 'vxlan-physdev',
+ physdev,
+ netlink.get_iface_name(ifla_vxlan_link)
+ )
+ else:
+ ifaceobjcurr.update_config_with_status('vxlan-physdev', physdev, 1)
+
def _query_running(self, ifaceobjrunning):
vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobjrunning.name)
attrval = vxlanattrs.get('vxlanid')
if attrval:
ifaceobjrunning.update_config('vxlan-id', vxlanattrs.get('vxlanid'))
+ else:
+ # if there is no vxlan id, this is not a vxlan port
+ return
+
+ ifaceobjrunning.update_config('vxlan-port', vxlanattrs.get(Link.IFLA_VXLAN_PORT))
+
attrval = vxlanattrs.get('local')
if attrval:
ifaceobjrunning.update_config('vxlan-local-tunnelip', attrval)
attrval = vxlanattrs.get('svcnode')
if attrval:
- [ifaceobjrunning.update_config('vxlan-svcnode', a)
- for a in attrval]
- attrval = vxlanattrs.get('remote')
- if attrval:
- [ifaceobjrunning.update_config('vxlan-remoteip', a)
- for a in attrval]
+ ifaceobjrunning.update_config('vxlan-svcnode', attrval)
+ purge_remotes = self._get_purge_remotes(None)
+ if purge_remotes:
+ # if purge_remotes is on, it means we own the
+ # remote ips. Query them and add it to the running config
+ attrval = self.ipcmd.get_vxlan_peers(ifaceobjrunning.name, vxlanattrs.get('svcnode'))
+ if attrval:
+ [ifaceobjrunning.update_config('vxlan-remoteip', a)
+ for a in attrval]
attrval = vxlanattrs.get('learning')
if attrval and attrval == 'on':
ifaceobjrunning.update_config('vxlan-learning', 'on')
+ attrval = vxlanattrs.get('ageing')
+ if attrval:
+ ifaceobjrunning.update_config('vxlan-ageing', vxlanattrs.get('ageing'))
+ ifla_vxlan_link = vxlanattrs.get(Link.IFLA_VXLAN_LINK)
+ if ifla_vxlan_link:
+ ifaceobjrunning.update_config(
+ 'vxlan-physdev',
+ netlink.get_iface_name(ifla_vxlan_link)
+ )
_run_ops = {'pre-up' : _up,
'post-down' : _down,
def _init_command_handlers(self):
if not self.ipcmd:
- self.ipcmd = iproute2(**self.get_flags())
+ self.ipcmd = LinkUtils()
def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
op_handler = self._run_ops.get(operation)