fuel-plugin-mellanox/deployment_scripts/ipoibd
Aviram Bar-Haim cd8cdecb47 Fix EIPOIBD bug when removing None slave
Change-Id: I86c6b861b891e0a0dac3245a41d4fb611a3e438d
2015-05-25 16:43:11 +03:00

1758 lines
65 KiB
Python
Executable File

#! /usr/bin/python
# Copyright 2015 Mellanox Technologies, Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- python -*-
# Author: Galia Magen galiam@mellanox.com
# Description: This daemon maintains current active eth.ipoib slaves
import sys
import os
import logging
import time
import re
import commands
import string
from logging.handlers import RotatingFileHandler
from optparse import OptionParser
import libxml2
import signal
VERSION = "2.3-2.0.8"
# Global parameters
logger = None
SCRIPT = os.path.basename(sys.argv[0])
DESC = "Run daemon for Child nics managment."
XEN_MANAGMENT = "/usr/bin/xenstore"
VIRSH_MANAGMENT = "/usr/bin/virsh"
OVS_MANAGMENT = "/etc/init.d/openvswitch"
FORMAT = '%(asctime)s :[%(name)s] %(levelname)s: %(message)s'
NONE_ENV= 0
XEN_ENV = 1
KVM_ENV = 2
OVS_ENV = 3
class initialize_once(object):
def __init__(self, initialize_func, *args, **kwargs):
self.initialize_func = initialize_func
self.args = args
self.kwargs = kwargs
self.initialized = False
def __call__(self, func):
def wrapper(*args, **kwargs):
if self.initialized is False:
self.initialize_func(*self.args, **self.kwargs)
self.initialized = True
return func(*args, **kwargs)
return wrapper
def init_logger (log_level=logging.DEBUG, log_file="/var/log/eipoib_daemon.log"):
logHandler = RotatingFileHandler(log_file, 'a', 24537209, 10)
logFormatter = logging.Formatter(FORMAT)
logHandler.setLevel(logging.DEBUG)
logHandler.setFormatter(logFormatter)
logger = logging.getLogger()
logger.addHandler(logHandler)
@initialize_once(init_logger)
def get_logger(name, level=logging.DEBUG):
'''
create a Logger instance according to level and the given handlers and return it.
@param level: threshold for this logger to level.
@type level: Level
@return Logger instance.
@type Logger
'''
# create logger instance.
logger = logging.getLogger(name)
# set level of the logger.
logger.setLevel(level)
return logger
def print_args (name, desc, args):
prefix_format = "%13s" % ""
title = prefix_format + "-" * 62
pattern = prefix_format + "%-15s : %s" + os.linesep
obj_str = title + os.linesep
obj_str += pattern % ("Name", name)
obj_str += pattern % ("Description", desc)
obj_str += pattern % ("Driver name", args.driver_name)
obj_str += pattern % ("Run mode", args.run_mode)
obj_str += pattern % ("Interval", args.interval)
obj_str += pattern % ("Version", VERSION)
obj_str += title + os.linesep
logger.info(os.linesep + obj_str)
def parse_args (name, desc, args):
usage = "usage: %s --help\nINFO :%s" % (sys.argv[0], "")
parser = OptionParser(usage = usage )
parser.add_option("-D", "--driver_name", help='The drievr name (for example: eth_ipoib)')
parser.add_option("-R", "--run_mode", help='the mode to run at (polling/events)', default="polling")
parser.add_option("-I", "--interval", help='How frequently the daemon runs {secs} (default: 2)', default=2)
parser.add_option("-L", "--log_level", help='Log level {error|info|debug} (default: debug)', default=logging.DEBUG)
try:
args = parser.parse_args()
except Exception, e:
logger.error("exception: %s" % str(e))
sys.exit(1)
return args[0]
def check_double_run ():
#check if another instance is running
cmd = "pidof -x -o %s %s" % (os.getpid(), SCRIPT)
(rc, out) = commands.getstatusoutput(cmd)
if rc == 0:
logger.error("Another instance of %s (pid %s) is already running!" % (SCRIPT, out.strip()))
return True
return False
def get_mac_by_name(name, namespace = None):
sub_cmd = ""
if namespace:
sub_cmd = "/sbin/ip netns exec %s" %namespace
cmd = "%s /sbin/ip link show %s 2> /dev/null | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2} | /usr/bin/head -1'" % (sub_cmd, name)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
logger.debug("Failed find mac for interface: %s and ns: %s!" % (name, str(namespace)))
#one more check, perhaps this mac is some other namespace:
cmd = "ip netns list"
(rc, out) = commands.getstatusoutput(cmd)
if rc == 0 and len(out) != 0:
all_ns = out.strip().split()
logger.debug("No nic in global space for peer_idx %s, will seek in all namespacess: %s \n" % (name, all_ns))
for ns in all_ns:
sub_cmd = "/sbin/ip netns exec %s" %ns
cmd = "%s /sbin/ip link show %s 2> /dev/null | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2} | /usr/bin/head -1'" % (sub_cmd, name)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
logger.debug("No port %s in namespace %s \n" % (name, ns))
continue
logger.debug("Found mac %s in namespace %s for port %s\n" % (out.strip().lower(), ns, name))
return out.strip().lower()
else:
logger.warning("Failed to run cmd: %s" % (cmd))
return None
return out.strip().lower()
################################################################################
#Abstract metaclass
################################################################################
class AbstractMeta(type):
def __init__(cls, name, bases, dct):
cls.logger = get_logger(cls.__name__)
return super(AbstractMeta, cls).__init__(name, bases, dct)
################################################################################
#Abstract class IDomain
################################################################################
class IDomain(object):
__metaclass__ = AbstractMeta
def get_domains(self): pass
#The function should return string or None
def get_vif_mac(self, *args, **kwargs): pass
class XENDomain(IDomain):
def __init__ (self):
super(XENDomain, self).__init__()
self.mgmt = "/usr/bin/xenstore"
def get_domains(self):
cmd = "for i in `%s list /local/domain`; do %s ls /local/domain/$i/name; done" % (self.mgmt, self.mgmt)
(rc, output) = commands.getstatusoutput(cmd)
if rc or len(output) == 0:
return []
domains = output.strip().split('\n')
self.logger.debug("Domain list : %s" % domains)
return domains
def get_vif_mac(self, vif, domains):
_vif_name = filter(lambda x: not x.isdigit(), vif.split('.')[0])
_vif = _vif_name + os.sep + vif.replace(_vif_name, '').replace('.', os.sep)
fn = "/local/domain/0/backend/%s/mac" % _vif
cmd = "%s read %s 2>&1" % (self.mgmt, fn)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
# non-vif slaves are ok to fail here
return None
mac = out.strip().lower()
if self.is_ucast_eth_mac(mac):
return mac
return None
class KVMDomain(IDomain):
def __init__ (self):
super(KVMDomain, self).__init__()
self.mgmt = "/usr/bin/virsh -r" # read-only argument
def get_domains(self, cmd=None):
cmd = self.mgmt + \
" list | /bin/sed 's/^[ \t]*//;s/[ \t]*$//' | " + \
"/bin/grep ^[0-9] | awk '{print $2}'"
(rc, output) = commands.getstatusoutput(cmd)
if rc or len(output) == 0:
return []
domains = output.strip().split('\n')
self.logger.debug("Domain list : %s" % domains)
return domains
def get_vif_mac(self, vif, domains):
for dom in domains:
# dump xml (per domain); and check the vif's mac in the xml output
cmd = "%s dumpxml %s" % (self.mgmt, dom)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
continue
# run xml xpath query, example output:
## <mac address="02:00:05:3e:aa:6a"/>
## <target dev="vnet0"/>
## ..
try: doc = libxml2.parseDoc(out)
except: continue
mac = None
dev = None
for node in doc.xpathEval('//interface/target|//interface/mac'):
node = str(node)
attr = node.split('=')[-1].strip()
attr = attr.replace('"', '')
attr = attr.replace("/>", '')
if node.count("target dev"):
dev = attr
if node.count("mac address"):
mac = attr
if mac != None and dev == vif:
mac = mac.lower()
if self.is_ucast_eth_mac(mac):
doc.freeDoc()
return mac
doc.freeDoc()
self.logger.debug("No domain object here, returns the original mac for %s" % vif)
return get_mac_by_name(vif)
class NativeDomain(IDomain):
def __init__ (self):
super(NativeDomain, self).__init__()
def get_domains(self, cmd=None):
return []
def get_vif_mac(self, vif, domains):
return get_mac_by_name(vif)
################################################################################
# Abstract class: Subject
################################################################################
class Subject(object):
__metaclass__ = AbstractMeta
def register_observer(observer):
"""Registers an observer with Subject."""
pass
def remove_observer(observer):
"""Removes an observer from Subject."""
pass
def notify_observers():
"""Notifies observers that Subject data has changed."""
pass
class Parent(Subject):
def __init__ (self, name):
super(Parent, self).__init__()
self.name = name
self.mac = get_mac_by_name(name)
self._observer_dict = {}
def register_observer(self, observer):
"""Registers an observer with Parent if the observer is not
already registered."""
exist = False
if observer.name in self._observer_dict:
self.logger.debug("observer %s is already registered" % observer.name)
exist = True
if not exist:
self.logger.debug("adding observer %s to parent %s " % (observer.name, self.name))
self._observer_dict[observer.name] = observer
observer.register_subject(self)
def remove_observer(self, observer):
"""Removes an observer from Parent if the observer is currently
subscribed to Parent."""
if observer.name in self._observer_dict:
observer.remove_subject()
del self._observer_dict[observer.name]
else:
raise ValueError("ERROR: Observer currently not subscribed to Subject!")
def notify_observers(self):
"""Notifies subscribed observers of change in Parent data."""
for observer in self._observer_dict.values():
observer.update()
def set_mac(self):
self.mac = get_mac_by_name(self.name)
def update_parent(self):
self.set_mac()
class EipoibParent(Parent):
def __init__ (self, name):
super(EipoibParent, self).__init__(name)
self.physical_port = self.get_physical_port(name)
self.logger.info("Create parent %s physical port: %s mac: %s" % (name, self.physical_port, self.mac))
@classmethod
def get_physical_port(cls, name):
cmd = "ethtool -i %s | /bin/grep bus-info:" % name
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
cls.logger.warning("Failed to find parent: %s:" % name)
return None
cls.logger.debug("Parent %s Got line: %s:" % (name, out))
m = re.findall('(ib\d+)', out)
if len(m) != 1:
cls.logger.warning("Parent %s Got line: %s:" % (name, out))
return None
return m[0]
@classmethod
def get_sysfs_path(cls, nic, leaf):
if nic != None:
return "/sys/class/net/%s/eth/%s" % (nic, leaf)
return "/sys/class/net/%s" % leaf
#get the macs of all the slaves of parent ethX. if vlan_id != None returns macs with the same vlan id.
#if vlan_id = None returns only the macs that are not vlan mac
def get_parent_slave_macs (self, vlan_id = None):
macs = []
cmd = "/bin/cat %s | awk '{print $2 \" \" $3}'" % self.get_sysfs_path(self.name, "vifs")
try:
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
return macs
except:
self.logger.warning("could not find sysfs file!")
return 2
macs_lines = out.split('\n')
for line in macs_lines:
m = re.findall("\s*MAC=(\S+) VLAN=(\S+)", line)
if len(m) != 1:
self.logger.debug("get_parent_vlan_macs: PROBLEM: parent %s vlan: %s line: %s: " % (self.name, vlan_id, line))
continue
mac = m[0][0]
vid = m[0][1]
if mac.count(":") != 5:
continue
if vlan_id != None and vid == vlan_id:
macs.append(mac)
elif vlan_id == None and vid == "N/A":
macs.append(mac)
return macs
# get info from the sysfs files for example: slaves, vifs
def get_parent_sysfs_info (self, info, name):
sysfs_path = self.get_sysfs_path(name, info)
cmd = "/bin/cat %s" % sysfs_path
try:
(rc, out) = commands.getstatusoutput(cmd)
if rc:
self.logger.error("Failed to run cmd: %s" % cmd)
return []
if len(out) == 0:
return []
except:
self.logger.warning("could not find sysfs file (%s)!" % sysfs_path)
return []
info_list = out.strip().split('\n')
return info_list
def get_parent_vifs (self):
return self.get_parent_sysfs_info("vifs", self.name)
def get_parent_slaves (self):
return self.get_parent_sysfs_info("slaves", self.name)
def get_parent_served (self, name):
return self.get_parent_sysfs_info("served", name)
def get_parent_bond_master(self, name):
self.logger.debug("Check if parent %s is slave to bond" % (name))
cmd = "ip link show | grep %s | cut -d':' -f2- | egrep '^ %s'| grep master" % (name, name)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("No master for parent: %s:" %(name))
return None
self.logger.debug("Parent %s has master %s:" % (name, out))
m = re.findall('.* master (\S+) ', out)
if len(m) != 1: # something wrong with the line if we have more than one master
self.logger.warning("Parent %s Got line: %s:" % (name, out))
return None
vlan_re = '(%s.\d+)@%s' % (name, name)
vlan_name = re.findall(vlan_re, out) # check if the slave is vlan, if not returns the parent name
if vlan_name == []:
interface_name = name
else:
interface_name = vlan_name
self.logger.debug(" %s is slave to bond %s" % (interface_name, m[0]))
#check if it is bonding master:
cmd = "cat /sys/class/net/bonding_masters | grep %s" %m[0]
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("Master %s is not bonding master" %(m[0]))
return None
return m[0]
def update_files (self, op, update_element, file_name):
cmd = "/bin/echo %s%s > %s" % (op, update_element, file_name)
self.logger.info("Update %s file" % file_name)
self.logger.debug("Runing cmd:%s\n" % cmd)
rc = os.system(cmd)
if rc:
self.logger.warning("cmd failed! (%s)" % cmd)
return 1
return 0
################################################################################
# Abstract class :Observer
################################################################################
class Observer(object):
__metaclass__ = AbstractMeta
def __init__(self):
super(Observer, self).__init__()
self.update_list = []
self.name = ""
def update(self):
"""Observer updates by pulling data from Subject."""
pass
def register_subject(self, subject):
"""Observer saves reference to Subject."""
self.parent = subject
def remove_subject(self):
"""Observer replaces Subject reference to None."""
del self.parent
class VlanObserver(Observer):
def __init__(self):
super(VlanObserver, self).__init__()
self.name = "vlan"
def update (self):
self.logger.debug("Updating vlan")
class NicObserver(Observer):
def __init__(self):
super(NicObserver, self).__init__()
self.name = "nic"
#on every port over the bridges one vlan per mac
self.map_mac_2_vlan = {}
# keeps the ports that already scaned in that loop
self.scaned_port = []
self.all_bridges = []
self.first_ovs_br = None
self.known_vlan_macs = {}
def add_to_scaned_list(self, nic):
#self.scaned_port = self.scaned_port.append(nic)
if nic not in self.scaned_port:
self.scaned_port.append(nic)
#get interface ibx.y and return the index after the dot
def get_index (self, interface):
self.logger.debug("Get index of interface %s " % (interface))
m = re.findall("(\d+\.\d+)", interface)
if len(m) == 1:
return "." + m[0].split(".")[1]
else:
self.logger.error("the interface %s is not in the right format ibx.y" % interface)
return None
def is_ucast_eth_mac(self, mac): #check if the mac address is valid
mac_len = 6
mac_hex_len = 2 * mac_len
if len(mac.replace(':', '')) != mac_hex_len:
return False
if not mac[1] in string.hexdigits:
return False
if mac.replace(':', '') == ("0" * mac_hex_len):
return False
return True
def create_child (self, new_child):
cmd = "/bin/echo %s > %s" % (new_child, "/sys/class/net/" + self.parent.physical_port + os.sep + "create_child")
self.logger.debug("Runing cmd:%s\n" % cmd)
try:
(rc, out) = commands.getstatusoutput(cmd)
if rc:
self.logger.warning("cmd failed! (%s)" % cmd)
return 1
except:
self.logger.warning("could not find sysfs file!")
return 2
return 0
def delete_child (self, old_child):
cmd = "/bin/echo %s > %s" % (old_child, "/sys/class/net/" + self.parent.physical_port + os.sep + "delete_child")
self.logger.debug("Runing cmd:%s\n" % cmd)
try:
(rc, out) = commands.getstatusoutput(cmd)
if rc:
self.logger.warning("cmd failed! (%s)" % cmd)
return 1
except:
self.logger.warning("could not find sysfs file!")
return 2
return 0
#return slave name (ibx.y) of a specific mac addres
def get_slave_by_mac (self, mac, parent, vlan_id = None):
vifs = self.parent.get_parent_vifs()
for vif in vifs:
if mac in vif:
if (vlan_id != None):
vlan_str = "VLAN=" + str(vlan_id)
if vlan_str in vif:
return vif.split()[0].replace("SLAVE=", "")
else:
vlan_str = "VLAN=N/A"
if vlan_str in vif:
return vif.split()[0].replace("SLAVE=", "")
self.logger.warning("could not find mac %s at parent %s" % (mac, self.name))
return None
def is_vlan (self, interface):
m = re.findall("(\S+\.\d+)", interface)
if len(m) == 1:
return True
else:
return False
def update (self):
self.logger.debug("Updating nic")
def get_vlan_by_br_and_nic(self, br, nic):
return None
class VirtualNicObserver(NicObserver):
def get_br_nics(self, br):
fn = "/sys/class/net/%s/brif" % br
if not os.path.exists(fn):
self.logger.debug("Failed to find path: %s: for br: %s" % (fn, br))
return []
cmd = "/bin/ls %s" % fn
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("Failed to run cmd:%s: for br: %s" % (cmd, br))
return []
return out.strip().split()
def get_brs(self):
brs = []
cmd = "/bin/ls -d /sys/class/net/*/bridge"
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
return brs
brs_fn = out.strip().split('\n')
for br_fn in brs_fn:
(head, tail) = os.path.split(br_fn)
(head, tail) = os.path.split(head)
if len(tail) == 0:
continue
brs += [tail]
self.logger.debug("bridges list %s" % (str(brs)))
return brs
def get_br_by_nic(self, nic):
brs = self.get_brs()
for br in brs:
nics = self.get_br_nics(br)
if nic in nics:
return br
return None
#####################################################################################
# some OS's/Topologies keep the peer interface in different namespaces
# The function searches all the namespaces in order to see where it is located.
def find_peer_by_idx(self, peer_if_idx):
peer = None
all_ns = None
cmd = "ip -o link show |egrep ^%s: | awk -F': ' '{print $2}'" % peer_if_idx
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("Failed to get peer from global namespace peer_idx %s, cmd: %s" % (peer_if_idx, cmd))
#try searching in all other namespaces:
cmd = "ip netns list"
(rc, out) = commands.getstatusoutput(cmd)
if rc == 0 and len(out) != 0:
all_ns = out.strip().split()
self.logger.debug("No peer in global space for peer_idx %s, will seek in all namespacess: %s \n" % (peer_if_idx, all_ns))
for ns in all_ns:
cmd = "ip netns exec %s ip link list |egrep ^%s: | awk -F': ' '{print $2}' " % (ns, peer_if_idx)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("No peer in namespace %s for peer_idx %s\n" % (ns, peer_if_idx))
continue
peer = out.strip()
self.logger.debug("Found peer %s in namespace %s for peer_idx %s\n" % (peer, ns, peer_if_idx))
return (peer, ns)
else:
self.logger.warning("Failed to run cmd: %s" % (cmd))
else:
peer = out.strip()
# found in global ns, or didn't find in at any namespace
return (peer, None)
#####################################################################################
# takes peer's macs in order to support veth interfaces (for dhcp client for example)
def get_peer_macs(self, nic, forced_vlan = None):
peer = None
mac = None
ns = None
#get the index of the peer in the ip link list.
cmd = "ethtool -S %s 2> /dev/null |grep peer_ifindex |awk -F': ' '{print $2}'" % nic
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("cmd:%s. No peer for nic: %s" % (cmd, nic))
return []
peer_if_idx = out.strip()
#get the peer name
(peer, ns) = self.find_peer_by_idx(peer_if_idx)
if None == peer:
self.logger.warning("Failed to get peer for nic: %s peer_idx %s, cmd: %s" % (nic, peer_if_idx, cmd))
return []
#getting the mac of the peer
mac = get_mac_by_name(peer, ns)
if mac != None:
return [mac]
self.map_mac_2_vlan[mac] = None
return []
def get_patch_macs(self, nic, vlan = None):
return []
#get the macs of all the virtual interfaces attached to the bridge.
# forced_vlan in case of patch-port etc.
def get_nic_vif_macs(self, nic, forced_vlan = None):
# Get handlers of LB / OVS
ovs_br = KVMOvsNicObserver()
lb_br = KVMVirtualNicObserver()
vlan_ob = VirtualVlanObserver()
vlan_ob.parent = self.parent
ovs_br.parent = self.parent
lb_br.parent = self.parent
macs = []
br = self.get_br_by_nic(nic)
if br == None:
return []
if br not in self.all_bridges:
self.all_bridges.append(br)
self.logger.debug("Parent %s linked with bridge %s" % (nic, br))
nics = self.get_br_nics(br)
domains = self.get_domains()
self.logger.debug("%s nics list %s" % (br, str(nics)))
self.add_to_scaned_list(nic)
for n in nics:
if (n in self.scaned_port):
self.logger.debug("No need to check peer for n: %s Parent %s and scaned_port: %s forced_vlan: %s" % (n, nic, str(self.scaned_port), str(forced_vlan)))
continue
# if slave is not vif it will return its mac
mac = self.get_vif_mac(n, domains)
if mac != None:
macs.append(mac)
self.logger.debug("Guest %s mac %s" % (n, str(mac)))
nic_br = self.get_br_by_nic(n)
self.add_to_scaned_list(nic_br)
vlan = self.get_vlan_by_br_and_nic(nic_br, n)
#if vlan == None:
#vlan = forced_vlan
if mac != None and (not self.map_mac_2_vlan.has_key(mac)):
self.logger.debug("get_nic_vif_macs mac: %s for n %s EXISTS nic: %s scaned_port: %s \n self.map_mac_2_vlan %s forced_vlan %s" % (mac, n, nic, str(self.scaned_port), str(self.map_mac_2_vlan), str(forced_vlan)))
self.map_mac_2_vlan[mac] = vlan
#if the nic that connected to the bridge is peer than we need also the peer mac address
# only not over the parent interface:
self.logger.debug("get_nic_vif_macs n: %s nic %s scaned_port: %s \n self.map_mac_2_vlan %s forced_vlan %s" % (n, nic, str(self.scaned_port), str(self.map_mac_2_vlan), str(forced_vlan)))
if (n == nic or (n in self.scaned_port)):
self.logger.debug("No need to check peer for n: %s Parent %s and scaned_port: %s" % (n, nic, str(self.scaned_port)))
continue
peer_macs = self.get_peer_macs(n, forced_vlan)
if len(peer_macs) != 0:
macs = macs + peer_macs
self.logger.debug("nic %s peer-macs %s" % (n, str(peer_macs)))
# check for special connections, for example OVS support patch port
patch_macs = self.get_patch_macs(n, vlan)
if len(patch_macs) != 0:
macs = macs + patch_macs
self.logger.debug("nic %s patch-macs %s" % (n, str(patch_macs)))
# Check if interface is connected to OVS too
# br = br-prv / br-int ..
# ovs_br = KVMOvsNicObserver object
br = ovs_br.get_br_by_nic(n)
lbr = lb_br.get_br_by_nic(n)
if br and lbr:
if self.first_ovs_br is None:
self.first_ovs_br = br
ovs_br.first_ovs_br = self.first_ovs_br
self.logger.debug("Found OVS br %s for port %s." % (br, n))
ovs_br.scaned_port = self.scaned_port
macs_ovsbr = list(set(ovs_br.get_nic_vif_macs(n, None)))
if macs_ovsbr:
self.logger.debug("Found macs in OVS over LB port %s: %s" % (n, macs_ovsbr))
macs = macs + macs_ovsbr
# Create a mapping with converted VLANs
self.logger.debug("original_mapped_mac_2_vlan is %s" % self.map_mac_2_vlan)
self.logger.debug("ovs_mapped_mac_2_vlan is %s" % ovs_br.map_mac_2_vlan)
self.map_mac_2_vlan.update(ovs_br.map_mac_2_vlan)
self.logger.debug("updated_mapped_mac_2_vlan is %s" % self.map_mac_2_vlan)
vlan_rules = ovs_br.get_vlan_vifs([self.first_ovs_br])
self.logger.debug("vlan rules %s for bridge %s" % (vlan_rules, self.first_ovs_br))
for vlan, macs_for_vlan in vlan_rules.items():
for m in macs_for_vlan:
self.map_mac_2_vlan[m] = vlan
ovs_br.map_mac_2_vlan[m] = vlan
# Add loop
for vlan_id, vlan_macs in vlan_rules.items():
if vlan_id not in self.known_vlan_macs:
self.known_vlan_macs[vlan_id] = []
self.logger.debug("Starting vlan add loop with vlan %s." % (vlan_id))
self.logger.debug("vlan_macs %s self.known_vlan_macs[%s] %s." % (vlan_macs, vlan_id, self.known_vlan_macs[vlan_id]))
new_vlan_macs = list(set(vlan_macs) - set(self.known_vlan_macs[vlan_id]))
if new_vlan_macs:
vlan_if = self.parent.name + "." + vlan_id
self.logger.debug("Found new vlan macs %s for vlan %s." % (new_vlan_macs, vlan_id))
vlan_ob.add_loop(vlan_if, [], new_vlan_macs, [vlan_if])
self.known_vlan_macs[vlan_id] = self.known_vlan_macs[vlan_id] + new_vlan_macs
# Remove loop
for vlan_id, vlan_macs in self.known_vlan_macs.items():
if vlan_id not in vlan_rules:
vlan_macs_to_remove = self.known_vlan_macs[vlan_id]
else:
vlan_macs_to_remove = list(set(self.known_vlan_macs[vlan_id]) - set(vlan_rules[vlan_id]))
vlan_if = self.parent.name + "." + vlan_id
for mac_to_remove in vlan_macs_to_remove:
current_slave = self.get_slave_by_mac(mac_to_remove, self.parent.name, vlan_id = vlan_id)
if not current_slave:
continue
self.logger.debug("Need to remove slave: %s of vlan id %s" % (current_slave, vlan_id))
self.destroy_nic(mac_to_remove, self.parent.name, current_slave)
self.known_vlan_macs[vlan_id] = list(set(self.known_vlan_macs[vlan_id]) - set([mac_to_remove]))
self.logger.debug("Connected interface %s to ovs-br %s macs2vlan: %s." % (n, br, str(self.map_mac_2_vlan)))
self.add_to_scaned_list(n)
return macs
def confirm_update(self, action, op_name, retry_cnt, retry_max, retry_nap, mac, parent):
# confirm that mac was destroyed/created
while retry_cnt <= retry_max:
time.sleep(retry_nap)
retry_cnt = retry_cnt + 1
self.logger.debug("Confirm that mac was %s: iter %d, max %d, nap %d" % (op_name, retry_cnt, retry_max, retry_nap))
# check if there is slave with this mac addres in the vifs list
slave = self.get_slave_by_mac(mac, parent)
if (slave == None and action == "remove") or (slave != None and action == "create"):
self.logger.debug("Child nic %s of parent %s was %s" % (mac, parent, op_name))
return 0
self.logger.error("Child nic %s of parent %s was not %s" % (mac, parent, op_name))
return 1
def get_slave_prefix(self, parent):
self.logger.debug("===> Mapping for parent %s %s" % (parent, self.parent.physical_port))
return self.parent.physical_port # pysical port is ib0/ib1...
#finds a slave number for the new slave (ib0.x)
def get_free_slave (self, mac, slave_prefix, slaves):
i = 1
while True:
slave_index = i
slave = str(slave_prefix) + "." + str(i)
self.logger.debug("Checking slave: %s\n" % slave)
# if clone not found, or found but not used, use it.
if get_mac_by_name(slave) == None:
self.logger.debug("not exists: %s\n" % slave)
break
elif (slave not in slaves):
self.logger.debug("slave:%s, slaves: %s\n" % (slave, str(slaves)))
break
i = i + 1
return slave
# create vif if remove flag is clear
def create_vif (self, parent, slave, mac, vlan_id = ""):
rc = 0
cmd = "/sbin/ip link set %s up" % parent
(rc, out) = commands.getstatusoutput(cmd)
if rc:
self.logger.warning("cmd failed! (%s)" % cmd)
rc = rc or self.parent.update_files("+", slave, self.parent.get_sysfs_path(parent, "slaves"))
if rc == 0:
rc = self.parent.update_files("+", slave + " " + mac + " " + vlan_id, self.parent.get_sysfs_path(parent, "vifs"))
if rc:
# clean what was created via the "slaves" command and create_child
self.logger.warning("failed to create vif, going to clean the slave entry:%s" % slave)
self.destroy_nic (None, self.parent.name, slave)
return rc
def create_nic (self, mac, parent):
self.logger.debug("Starting create nic")
rc = 0
if None == mac:
self.logger.warning("No mac for create_nic return.." )
return 2
slave_prefix = self.get_slave_prefix(parent) # if physical port is'ib0' the function return '0'
slaves = self.parent.get_parent_slaves() # get all slaves of parent ethx : /sys/class/net/ethx/
if slaves == 1:
return 1
slave = self.get_free_slave(mac, slave_prefix, slaves)# getting the slave to add ib0.x
if get_mac_by_name(slave) == None:
slave_index = self.get_index(slave)
rc = self.create_child(slave_index)
if rc != 0:
self.logger.warning("Failed to create child, index: (%s)" % slave_index)
return rc
self.logger.info("Clone nic %s was created" % slave)
rc = rc or self.create_vif(parent, slave, mac)
return rc
def destroy_nic (self, mac, parent, slave):
self.logger.debug("Starting destroy nic")
rc = self.parent.update_files("-", slave, self.parent.get_sysfs_path(parent, "slaves"))
sysfs_line = "%s %s" % (slave, mac)
slave_index = self.get_index(slave)
# let the system recover..
time.sleep(1)
if self.delete_child(slave_index) == 0:
self.logger.info("Clone nic %s was deleted" % slave)
return 0
return 1
def destroy_and_confirm (self, parent, slave_mac):
self.logger.info("%s slave %s has no guest vif" % (parent, slave_mac))
slave = self.get_slave_by_mac(slave_mac, parent) #getting the slave name before it is being deleted
if None == slave:
self.logger.info("No slave to destroy for mac: %s !!!" % slave_mac)
return -1
self.logger.info("destroy slave %s" % slave)
rc = self.destroy_nic(slave_mac, parent, slave)
rc = rc or self.confirm_update("remove", "destroyed", 0, 10, 1, slave_mac, parent)
if rc:
self.logger.warning("%s slave mac %s destruction failed" % (parent, slave_mac))
else:
self.logger.info("%s slave mac %s was destroyed" % (parent, slave_mac))
self.logger.debug("removing slave mac %s from update list" % slave_mac)
self.update_list.remove(slave_mac)
def create_and_confirm(self, owner, parent, vif_mac):
self.logger.info(owner + " vif mac %s has no slave under %s" % (vif_mac, parent))
rc = self.create_nic(vif_mac, parent)
rc = rc or self.confirm_update("create", "created", 0, 5, 2, vif_mac, parent)
if rc:
self.logger.warning("%s slave mac %s creation failed" % (parent, vif_mac))
else:
self.logger.info("%s slave mac %s was created" % (parent, vif_mac))
self.update_list.append(vif_mac)
def remove_loop (self, parent, slaves_macs, vif_macs, current_vlan = None):
self.logger.debug("Starting remove loop")
if not len(slaves_macs):
self.logger.debug("Parent %s has no child nics to clean, nop" % parent)
for slave_mac in slaves_macs:
if slave_mac in vif_macs:
self.logger.debug("%s slave %s is active, nop all inf: %s" % (parent, slave_mac, vif_macs))
continue
if slave_mac in self.update_list: # remove the slave only if this observer added it from the first place!
self.destroy_and_confirm(parent, slave_mac)
def add_loop (self, parent, slaves_macs, vif_macs, current_vlan = None):
self.logger.debug("Starting add loop")
if not len(vif_macs):
self.logger.debug("Parent %s has no associated guest vifs, nop" % parent)
for vif_mac in vif_macs:
owner = "Guest"
if vif_mac == self.parent.mac:
owner = "Parent"
if vif_mac in slaves_macs:
self.logger.debug(owner + " mac %s already has slave under %s, nop" % (vif_mac, parent))
continue
self.create_and_confirm(owner, parent, vif_mac)
#############################################################################
def update (self):
self.logger.debug("Updating virtual nic (parent=%s)" % self.parent.name)
# no ports in the already scaned list:
self.scaned_port = []
vif_macs = [self.parent.mac] + self.get_nic_vif_macs(self.parent.name) #the macs of all the virtual interfaces connected to the bridge
slaves_macs = self.parent.get_parent_slave_macs() # the macs of all parent slaves
# make sure no duplicate macs here.
vif_macs = list(set(vif_macs))
self.logger.debug("%s vif macs = %s" % (self.parent.name, vif_macs))
self.logger.debug("%s slaves macs = %s" % (self.parent.name, slaves_macs))
# destroy child mac w/o guest vif mac
self.remove_loop(self.parent.name, slaves_macs, vif_macs)
# if no Child nic for guest vif, create it
self.add_loop(self.parent.name, slaves_macs, vif_macs)
###################################################
class MacvtapObserver(Observer):
def update (self):
self.logger.debug("Updating macvtap")
class KVMVirtualNicObserver(VirtualNicObserver, KVMDomain):
def __init__(self):
super(KVMVirtualNicObserver, self).__init__()
class XENVirtualNicObserver(VirtualNicObserver, XENDomain):
def __init__(self):
super(XENVirtualNicObserver, self).__init__()
#####################################################################
#VLAN observer
#####################################################################
class VirtualVlanObserver(VirtualNicObserver):
def __init__(self):
super(VirtualVlanObserver, self).__init__()
self.known_vlans = []
self.name = "vlan"
def create_vif (self, parent, slave, mac):
#calling the basic vreate_vif with the vlan id
super(VirtualVlanObserver, self).create_vif(parent.split(".")[0], slave, mac, parent.split(".")[1])
def get_index (self, interface):
#build the index string of the ib interface for the vlan (ib0.8005.1)
self.logger.debug("Get index of interface %s " % (interface))
m = re.findall("(\d+\.[\d|\w]+.\d+)", interface)
if len(m) == 1:
m = m[0].split(".")
return "0x" + m[1] + '.' + m[2]
else:
self.logger.error("the interface %s is not in the right format ibx.y" % interface)
return None
def get_vlan_mac (self, vlan):
mac = get_mac_by_name(vlan)
if mac == None:
mac = self.parent.mac
return mac
def get_slave_prefix(self, vlan):
#the slave prefix of the vlan is ib0.8005 for example
vlan_index = self.get_pif_ib_vlan_index(vlan)
ib_vlan_if = self.parent.physical_port + "." + str(vlan_index)
return ib_vlan_if
def get_pif_ib_vlan_index(self, vlan):
eth_index = vlan.split(".")[1]
# full member vlan:
vlan_index = int(eth_index) | 0x8000
return hex(vlan_index).split("0x")[1]
def get_vlan_interfaces_per_parent(self, parent):
all_vlans = []
cmd = "ls /sys/class/net/ | grep %s" % parent
(rc, out) = commands.getstatusoutput(cmd)
if rc :
logger.error("Failed to run command: %s" % cmd)
return all_vlans
interface_list = out.split()
for interface in interface_list:
if self.is_vlan(interface):
all_vlans.append(interface)
return all_vlans
def create_nic(self, mac, vlan_if):
self.logger.debug("Create virtual Nic for vlan %s" % vlan_if)
super(VirtualVlanObserver, self).create_nic(mac, vlan_if)
def destroy_nic(self, mac, parent, slave):
self.logger.debug("Destroy virtual Nic for vlan %s" % parent)
super(VirtualVlanObserver, self).destroy_nic(mac, parent.split(".")[0], slave)
def get_slave_by_mac(self, vlan_mac, vlan, vlan_id = None):
return super(VirtualVlanObserver, self).get_slave_by_mac(vlan_mac, vlan, vlan.split(".")[1])
def remove_vlan_loop(self, vlan, vlan_mac, vif_macs, current_vlans):
#this function is solving the problem with removing the vlan vif with the same mac as the regular ibx.y vif.
self.logger.info("Starting remove vlan loop for vlan %s with mac: %s, curent vlans are: %s" % (vlan, vlan_mac, current_vlans))
if vlan not in current_vlans:
if (vlan_mac in vif_macs) and (vlan_mac in self.update_list):
self.destroy_and_confirm(vlan, vlan_mac)
def remove_loop(self, vlan, slaves_macs, vif_macs, current_vlans):
#in case that vlan is a slave to bond the mac of the vlan is not the mac of the parent,
#so we neeed to get the current vlan mac!
self.remove_vlan_loop(vlan, self.get_vlan_mac(vlan), vif_macs, current_vlans)
super(VirtualVlanObserver, self).remove_loop(vlan, slaves_macs, vif_macs)
def add_loop (self, vlan, slaves_macs, vif_macs, current_vlans):
super(VirtualVlanObserver, self).add_loop(vlan, slaves_macs, vif_macs)
def update (self):
self.logger.debug("Updating virtual vlan")
# get all the vlan interfaces
current_vlans = self.get_vlan_interfaces_per_parent(self.parent.name)
self.logger.debug("%s has vlans: %s" % (self.parent.name, current_vlans))
all_vlans = list(set(self.known_vlans + current_vlans))
self.scaned_port = []
# run over all the vlans, add slave per node:
for vlan in all_vlans:
self.logger.debug("taking care at vlan: %s" % vlan)
#to support bond the get_nic_vif_macs should return the vif_macs that connected to the bond
bond = self.parent.get_parent_bond_master(vlan)
if bond != None:
vif_macs = [get_mac_by_name(bond)] + self.get_nic_vif_macs(bond)
else:
vif_macs = [self.get_vlan_mac(vlan)] + self.get_nic_vif_macs(vlan) #the macs of all the virtual interfaces connected to the bridge
# make sure no duplicate macs here.
vif_macs = list(set(vif_macs))
self.logger.debug("vlan vif macs: %s" % vif_macs)
slaves_macs = self.parent.get_parent_slave_macs(vlan_id = vlan.split(".")[1]) # the macs of all parent slaves
self.logger.debug("vlan slaves macs: %s" % slaves_macs)
# destroy child mac w/o guest vif mac
self.remove_loop(vlan, slaves_macs, vif_macs, current_vlans)
# if no Child nic for guest vif, create it
if len(current_vlans) != 0:
self.add_loop(vlan, slaves_macs, vif_macs, current_vlans)
#updating the known_vlans list with the current ones
self.known_vlans = current_vlans
class KVMVirtualVlanObserver(VirtualVlanObserver, KVMDomain):
def update (self):
self.logger.debug("Updating KVM virtual vlan")
super(KVMVirtualVlanObserver, self).update()
class XENVirtualVlanObserver(VirtualVlanObserver,XENDomain):
def update (self):
self.logger.debug("Updating XEN virtual vlan")
super(XENVirtualVlanObserver, self).update()
#################################################################
#BOND observer
#################################################################
class BondObserver(Observer):
pass
class VirtualBondObserver(VirtualNicObserver):#TODO: add other class to take care of slave vlan of bond(VirtualVlanObserver) and add basic bond class
def __init__(self):
super(VirtualBondObserver, self).__init__()
self.bond_active_slave_map = {}
self.slaves_list = []
self.name = "bond"
# gets bond slave (e.g ethX.Y or ethX) and return the parent (e.g ethX)
# or if it is not vlan interface return the input back
def get_parent_of_bond_slave(self, bond_slave):
m = re.findall('(\S+)\.\d+' ,bond_slave)
if len(m) == 1:
return m[0]
return bond_slave
def get_active_slave(self, bond):
self.logger.debug(" Check %s active slave" % (bond))
cmd = "cat /sys/class/net/%s/bonding/active_slave" % bond
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("No active slave for %s:" %(bond))
return None
self.logger.debug("%s has active slave: %s:" % (bond, out))
return out.strip()
def get_bond_slaves (self, bond):
self.logger.debug(" Get %s slaves" % (bond))
cmd = "cat /sys/class/net/%s/bonding/slaves" % bond
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("No slaves for %s:" %(bond))
return []
self.logger.debug("%s has slaves: %s:" % (bond, out))
return out.split()
def switch_active_slave(self, prev_active_slave, current_active_slave):
rc = 0
# get the served IP's from the previous active slave.
served_list = self.parent.get_parent_served(prev_active_slave)
if len(served_list) == 0:
self.logger.debug("No IP's served under %s served list: %s" % (prev_active_slave, served_list))
return None
self.logger.debug("%s has served list: %s:" % (prev_active_slave, served_list))
# take each line and move it to the active:
#1. get one of the new active parent slaves
prev_active_slave_served_file = self.parent.get_sysfs_path(prev_active_slave, "served")
current_active_slave_served_file = self.parent.get_sysfs_path(current_active_slave, "served")
for served in served_list:
new_line = re.sub('SLAVE=\S+ ', '', served) # served format: SLAVE=ib0.1 MAC=00:02:c9:43:3b:f1 IP=11.134.45.2 VLAN=N/A
new_line = re.sub('\S+=', '', new_line)
new_line = re.sub('N/A', '', new_line)
self.logger.debug("LINE %s" % (new_line))
rc = self.parent.update_files("-", new_line , prev_active_slave_served_file) #2. remove the served from the prev_active_slave
rc = rc or self.parent.update_files("+", new_line , current_active_slave_served_file)#3. add the served to the current_active_slave
return rc
def update_bond_slave(self, master, bond_slaves):
self.logger.debug("Update bond slaves")
if master != None:
bond_slaves = self.get_bond_slaves(master)
else:
bond_slaves = []
self.slaves_list = list(set(self.slaves_list + bond_slaves))
self.logger.info("bond slaves list = %s" % self.slaves_list)
for slave in self.slaves_list:
if self.parent.name == slave:
self.logger.debug("Updating bond slave %s" % slave)
vif_macs = [get_mac_by_name(slave)] + self.get_nic_vif_macs(master) #the macs of all the virtual interfaces connected to the bridge
self.logger.debug("bond vif_macs= %s " % vif_macs)
slaves_macs = self.parent.get_parent_slave_macs() # the macs of all parent slaves
self.logger.debug("bond slaves_macs = %s" % slaves_macs)
# destroy child mac w/o guest vif mac
self.remove_loop(slave, slaves_macs, vif_macs)
# if no Child nic for guest vif, create it
self.add_loop(slave, slaves_macs, vif_macs)
def search_for_slave(self, slave):
for bond_slave in self.slaves_list:
if slave in bond_slave:
return bond_slave
return None
def update (self):
self.logger.debug("Updating bond")
#get master: the bond name
master = self.parent.get_parent_bond_master(self.parent.name)
if (None == master):
bond_slave_to_remove = self.search_for_slave(self.parent.name)
if bond_slave_to_remove != None:
#if self.parent.name in self.slaves_list:
self.logger.debug("Need to clean bond slave %s" % bond_slave_to_remove)
else:
self.logger.debug("No master for parent %s" % self.parent.name)
return 2
#get the current from the bond and check if that was before
else: # if there is master for parent
current_active_slave = self.get_active_slave(master);
if (None == current_active_slave):
self.logger.debug("No active slave for master %s" % master)
current_active_slave = ""
self.update_bond_slave(master, self.slaves_list)
#check if bond exist, if yes check for failover event
if self.bond_active_slave_map.has_key(master):
prev_active_slave = self.bond_active_slave_map[master]
if (prev_active_slave != "") and (prev_active_slave != current_active_slave):
# in case the bond's slaves are vlans (e.g ethx.y) we resotre over the origin pv interfaces (e.g ethx)
prev_active_slave_parent = self.get_parent_of_bond_slave(prev_active_slave)
current_active_slave_parent = self.get_parent_of_bond_slave(current_active_slave)
self.logger.info("Found failover event master: %s, cur: %s prev: %s " %(master, current_active_slave, prev_active_slave))
rc = self.switch_active_slave(prev_active_slave_parent, current_active_slave_parent)
else:
self.logger.info("NO failover event master: %s, cur: %s prev: %s " %(master, current_active_slave, prev_active_slave))
if master != None:
self.logger.info("Update master: %s, active slave:: %s " %(master, current_active_slave))
self.bond_active_slave_map[master] = current_active_slave
else:
if bond_slave_to_remove in self.slaves_list:
self.logger.debug(" removing %s from list %s" % (bond_slave_to_remove, self.slaves_list))
self.slaves_list.remove(bond_slave_to_remove)
class KVMVirtualBondObserver(VirtualBondObserver, KVMDomain):
def update (self):
self.logger.debug("Updating KVM bond")
super(KVMVirtualBondObserver, self).update()
class XENVirtualBondObserver(VirtualBondObserver, XENDomain):
def update (self):
self.logger.debug("Updating XEN bond")
super(XENVirtualBondObserver, self).update()
class NativeNicObserver(VirtualNicObserver, NativeDomain):
def update (self):
self.logger.debug("Updating Native Nic ")
super(NativeNicObserver, self).update()
class NativeVlanObserver(VirtualVlanObserver, NativeDomain):
def update (self):
self.logger.debug("Updating Native vlan ")
super(NativeVlanObserver, self).update()
class NativeBondObserver(VirtualBondObserver, NativeDomain):
def update (self):
self.logger.debug("Updating Native bond ")
super(NativeBondObserver, self).update()
#################################################################################
# OVSVlanHandler: Helper class for OVS, used to handle vlan that been set by ovs
################################################################################
class OVSVlanHandler(KVMVirtualVlanObserver):
def __init__(self):
super(KVMVirtualVlanObserver, self).__init__()
self.name = "ovs-vlan-handler"
self.previous_vlans = []
self.vlan_fake_name = None
self.slaves_macs = None
self.ovs_vif_macs = None
self.full_vlan_names = None
def set_param(self, vlan_fake_name, slaves_macs, ovs_vif_macs, full_vlan_names):
self.vlan_fake_name = vlan_fake_name
self.slaves_macs = slaves_macs
self.ovs_vif_macs = ovs_vif_macs
self.full_vlan_names = full_vlan_names
########################################################
#def remove_loop(self, vlan, vlan_mac, vif_macs, current_vlans):
# self.logger.debug("OVSVlanHandler remove loop for =%s" % self.parent.name)
# return
########################################################
def update (self):
# remove the vlan itself:
self.logger.debug("OVSVlanHandler:Update: before calling remove loop self.vlan_fake_name : %s self.slaves_macs: %s, self.ovs_vif_macs: %s, self.full_vlan_names : %s" %(self.vlan_fake_name, self.slaves_macs, self.ovs_vif_macs, self.full_vlan_names))
self.remove_loop(self.vlan_fake_name, self.slaves_macs, self.ovs_vif_macs, self.full_vlan_names)
# remove all interfaces that were added because of the vlan (peers, bridge members)
if self.vlan_fake_name not in self.full_vlan_names:
self.logger.debug("%s: Nothing to do here: comes from (%s) slaves_macs: %s ovs_vif_macs: %s" % ((self.parent.name, self.vlan_fake_name, self.slaves_macs, self.ovs_vif_macs)))
else:
self.logger.debug("%s: %s is not in full list: %s\n " % ((self.parent.name, self.vlan_fake_name, self.full_vlan_names)))
# if no child nic for guest vif, create it
self.add_loop(self.vlan_fake_name, self.slaves_macs, self.ovs_vif_macs, self.full_vlan_names)
#######################################
# check in ovs-flow-controll if there is a mppaing between vlan to new vlan
def get_fc_vlan_map(self, ovs_name, vlan):
if None == ovs_name:
self.logger.debug("no ovs bridge returning")
return None
cmd = "ovs-ofctl dump-flows %s 2> /dev/null |grep 'dl_vlan=%s ' " % (ovs_name, vlan)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("cmd:%s. No rule for vlan: %s in bridge:%s" % (cmd, vlan, ovs_name))
return None
m = re.findall('mod_vlan_vid:(\d+)', out)
if len(m) > 0:
self.logger.debug("Found vlan: %s that mapped to vlan: %s in ovs: %s" % (vlan, m[0], ovs_name))
return m[0]
self.logger.warning("failed to parse line: %s\n" %(out))
return None
################################################
################################################
# OVS bridge support:
# prefix: KVMOvs
################################################
class KVMOvsNicObserver(VirtualNicObserver, KVMDomain):
def __init__(self):
super(KVMOvsNicObserver, self).__init__()
self.name = "ovs-nic"
self.VlanHandler = OVSVlanHandler()
self.ovs_name = None
#returns list
def get_non_vlan_vifs(self):
out = []
for key in self.map_mac_2_vlan:
if self.map_mac_2_vlan[key] == None:
out.append(key)
return out
#returns dict
def get_vlan_vifs(self, all_bridges = None):
if not all_bridges:
all_bridges = self.all_bridges
out = {} # vlan -> [list of macs]
for key in self.map_mac_2_vlan:
vlan = self.map_mac_2_vlan[key]
self.logger.debug("in get_vlan_vifs checking vlan %s" % vlan)
if vlan != None:
# change vlan according to ovs flow-controll:
for br in all_bridges:
new_vlan = self.VlanHandler.get_fc_vlan_map(br, vlan)
if new_vlan != None:
self.logger.debug("get_vlan_vifs parent: %s: bridge: %s vlan %s was mapped to vlan: %s" % (self.parent.name, br, vlan, new_vlan))
vlan = new_vlan
#find if it is the first time in out hash
if vlan not in out:
out[vlan] = []
out[vlan].append(key)
self.logger.debug("get_vlan_vifs parent: %s: return: out %s" % (self.parent.name, str(out)))
return out
def remove_vlan_loop(self, vlan, vlan_mac, vif_macs, current_vlans):
self.logger.debug("remove loop for =%s" % self.parent.name)
return
def update (self):
self.logger.debug("Updating parent=%s" % self.parent.name)
self.map_mac_2_vlan = {}
self.scaned_port = []
# update the name of the ovs that this parent attaches to it:
self.ovs_name = self.get_br_by_nic(self.parent.name)
vif_macs = [self.parent.mac] + self.get_nic_vif_macs(self.parent.name) #the macs of all the virtual interfaces connected to the bridge
# 2 loops: one for vlan vifs and one for non vlans.
# vlan loop, first in order to have the parent vifs off first, the non-vlan will get rid of the rest.:
all_vlan_dict = self.get_vlan_vifs()
full_vlan_names = []
for key in all_vlan_dict:
full_vlan_names.append(self.parent.name + "." + key)
self.logger.debug("%s map_mac_2_vlan: %s vlans = %s full_vlan_names: %s" % ((self.parent.name, self.map_mac_2_vlan, all_vlan_dict, full_vlan_names)))
for vlan in all_vlan_dict.keys() + self.VlanHandler.previous_vlans:
if vlan in all_vlan_dict:
current_macs_in_vlan = list(set(all_vlan_dict[vlan]))
else:
current_macs_in_vlan = []
vlan_fake_name = self.parent.name + "." + vlan
self.VlanHandler.parent = self.parent
self.logger.debug("%s: vlan = %s contains: %s\n " % ((self.parent.name, vlan, current_macs_in_vlan)))
slaves_macs = self.parent.get_parent_slave_macs(vlan) # the macs of all parent slaves
self.logger.debug("%s vlan = %s current_macs_in_vlan: %s, has: %s\n " % ((self.parent.name, vlan, current_macs_in_vlan, slaves_macs)))
ovs_vif_macs = current_macs_in_vlan
self.VlanHandler.set_param(vlan_fake_name, slaves_macs, ovs_vif_macs, full_vlan_names)
self.VlanHandler.update()
#keep the last list of vlan, in order to be able to delete them the next loop.
self.VlanHandler.previous_vlans = all_vlan_dict.keys()
# non vlan loop:
ovs_vif_macs = [self.parent.mac] + self.get_non_vlan_vifs()#the macs of all the virtual interfaces connected to the bridge
slaves_macs = self.parent.get_parent_slave_macs() # the macs of all parent slaves
ovs_vif_macs = list(set(ovs_vif_macs))
self.logger.debug("%s map_mac_2_vlan: %s vif macs = %s ovs_vif_macs: %s slaves_macs: %s" % ((self.parent.name,self.map_mac_2_vlan, vif_macs, ovs_vif_macs, slaves_macs)))
# destroy child mac w/o guest vif mac
self.remove_loop(self.parent.name, slaves_macs, ovs_vif_macs, None)
# if no Child nic for guest vif, create it
self.add_loop(self.parent.name, slaves_macs, ovs_vif_macs)
#####################################################################
# OVS supports connections of 2 bridges, one is the peer of the other
# So, needs to go over all the bridge-peer nics
def get_peer_macs(self, nic, forced_vlan = None):
peer = None
macs = None
ns = None
vlan = None
#take nic vlan if exists
nic_br = self.get_br_by_nic(nic)
vlan = self.get_vlan_by_br_and_nic(nic_br, nic)
if None == vlan:
vlan = forced_vlan
self.logger.debug("get_peer_macs: peer %s vlan:%s." % (peer, str(vlan)))
mac = get_mac_by_name(nic);
if None != mac:
self.map_mac_2_vlan[mac] = vlan
#get the index of the peer in the ip link list.
cmd = "ethtool -S %s 2> /dev/null |grep peer_ifindex |awk -F': ' '{print $2}'" % nic
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.debug("cmd:%s. No peer for nic: %s" % (cmd, nic))
return []
peer_if_idx = out.strip()
#get the peer name
(peer, ns) = self.find_peer_by_idx(peer_if_idx)
if None == peer:
self.logger.warning("Failed to get peer for nic: %s peer_idx %s, cmd: %s" % (nic, peer_if_idx, cmd))
return []
# now we have the peer, let's check if it connected to other bridge:
peer_to_bridge = self.get_br_by_nic(peer)
if None == peer_to_bridge:
# no bridge, return the mac of that peer:
self.logger.debug("NO OVS bridge for peer %s (attached to nic: %s) peer_idx %s." % (peer, nic, peer_if_idx))
#Check if there is a linux-br that connected to that port
# TODO: this is atemporaly fix, need to change the design
linuxbr = KVMVirtualNicObserver()
linuxbr.parent = self.parent
br = linuxbr.get_br_by_nic(peer)
macs_linuxbr = []
if None != br:
macs_linuxbr = linuxbr.get_nic_vif_macs(peer, vlan)
self.logger.debug("peer %s to linux-br %s macs: %s vlan: %s." % (peer, br, str(macs_linuxbr), str(vlan)))
for mac2 in macs_linuxbr:
self.map_mac_2_vlan[mac2] = vlan
macs = get_mac_by_name(peer, ns)
if None != macs:
self.logger.debug("returning peer %s mac %s." % (peer, macs))
self.map_mac_2_vlan[macs] = vlan
return [macs] + macs_linuxbr
return []
self.logger.debug("Bridge %s for peer %s (attached to nic: %s) peer_idx %s." % (peer_to_bridge, peer, nic, peer_if_idx))
#getting all the macs attached to that bridge
macs = self.get_nic_vif_macs(peer, vlan)
self.logger.debug("Found macs: %s for peer: %s in Bridge %s " % (macs, peer, peer_to_bridge))
return macs
##############################################################
def get_patch_macs(self, nic, vlan = None):
mac = None
all_macs = None
self.logger.debug("get_patch_macs for %s vlan %s" % (nic, vlan))
# get the next side of the patch port:
cmd = r'ovs-vsctl show | grep -A 2 Interface |grep -A 2 -w %s | grep -A 1 patch |grep -v %s | grep peer= ' %(nic, nic)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
if rc:
self.logger.debug("Failed to run cmd:%s rc: %d" % (cmd, rc))
return []
self.logger.debug("cmd:%s: for nic: %s return: %s" % (cmd, nic, out.strip()))
m = re.findall('peer=(\S+)}', out)
if len(m) != 1:
self.logger.warning("No patch port in line %s, m: %s " % (out, str(m)))
return []
self.logger.debug("get_patch_macs return %s" % (m[0]))
br = self.get_br_by_nic(m[0])
if None != br:
mac = get_mac_by_name(br)
if None != mac:
self.map_mac_2_vlan[mac] = vlan
if m[0] in self.scaned_port:
return []
self.add_to_scaned_list(m[0])
macs = self.get_nic_vif_macs(m[0], vlan)
if macs != None:
if mac != None:
macs = macs + [mac]
all_macs = macs
self.logger.debug("get_patch_macs all_macs are: %s" % str(all_macs))
return all_macs
#############################################################################
def get_br_by_nic(self, nic):
self.logger.debug("Starting get_br_by_nic FOR OVS nic: %s", nic)
cmd = "ovs-vsctl iface-to-br %s" %nic
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
if rc:
self.logger.debug("Failed to run cmd:%s: for nic: %s rc: %s" % (cmd, nic, rc))
return None
self.logger.debug("cmd:%s: for nic: %s return: %s" % (cmd, nic, out.strip().split()))
return out.strip()
def get_br_nics(self, br):
if None == br or len(br) == 0:
return []
cmd = "ovs-vsctl list-ports %s" % br
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
if rc:
self.logger.debug("Failed to run cmd:%s: for br: %s rc: %s" % (cmd, br, rc))
return []
self.logger.debug("cmd:%s: for br: %s return: %s" % (cmd, br, out.strip().split()))
return out.strip().split()
#############################################################################
def get_vlan_by_br_and_nic(self, br, nic):
self.logger.debug("Starting get_vlan_by_br_and_nic br: %s nic:%s" %(br, nic))
if br == None or nic == None:
return None
cur_br = self.get_br_by_nic(nic)
if cur_br != br:
self.logger.debug("No interface: %s in bridge: %s it is at bridge: %s\n" %(nic, br, cur_br))
return None
#cmd = r'ovs-vsctl show | grep -A 1 -w %s | grep tag' %(nic)
cmd = r' ovs-vsctl get port %s tag' %(nic)
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
if rc:
self.logger.debug("Failed to run cmd:%s: nic: %s br: %s, rc: %s" % (cmd, nic, br, rc))
return None
self.logger.debug("cmd:%s: for nic: %s return: %s" % (cmd, nic, out.strip()))
m = re.findall('(\d+)', out)
if len(m) != 1:
self.logger.warning("No vlan in line %s " % (out))
return None
self.logger.debug("get_vlan_by_br_and_nic parsing return %s" % (m[0]))
return m[0]
#####################################################################################
class KVMOvsVlanObserver(KVMOvsNicObserver,KVMVirtualVlanObserver):
def __init__(self):
super(KVMVirtualVlanObserver, self).__init__()
self.name = "ovs-vlan"
class KVMOvsBondObserver(KVMOvsNicObserver,KVMVirtualBondObserver):
def __init__(self):
super(KVMVirtualBondObserver, self).__init__()
self.name = "ovs-bond"
################################################################################
# Abstract class :ParentScanner
################################################################################
class ParentScanner(object):
__metaclass__ = AbstractMeta
def __init__(self):
self.parent_list = []
def get_parent_list (self): pass
class EipoibParentScanner(ParentScanner):
def get_parents_name_list (self):
parent_name = []
fn = EipoibParent.get_sysfs_path(None, "eth_ipoib_interfaces")
if not os.path.exists(fn):
self.logger.error("No such file %s" % fn)
return parent_name
cmd = "/bin/cat %s | awk '{print $1}'" % fn
try:
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.error("Failed to get the eth_ipoib interfaces")
return parent_name
except:
self.logger.warning("could not find sysfs file (%s)!" % fn)
return 2
return out.strip().split('\n')
def get_parent_list (self):
name_list = self.get_parents_name_list()
if len(name_list) == 0:
self.parent_list = []
return self.parent_list
for name in name_list:
exist = False
for parent in self.parent_list:
if (name == parent.name) and (parent.physical_port == EipoibParent.get_physical_port(name)):
self.logger.debug("already have parent for %s" % name)
exist = True
if not exist:
self.parent_list.append(EipoibParent(name))
return self.parent_list
################################################################################
# Abstract factory:EnvironmentFactory
################################################################################
class EnvironmentFactory:
__metaclass__ = AbstractMeta
def get_observer (self, observer): pass
class KVMEnvironment(EnvironmentFactory):
def get_observer(self, obs):
if obs == "vlan":
return KVMVirtualVlanObserver()
if obs == "bond":
return KVMVirtualBondObserver()
if obs == "nic":
return KVMVirtualNicObserver()
if obs == "ovs-nic":
return KVMOvsNicObserver()
if obs == "ovs-vlan":
return KVMOvsVlanObserver()
if obs == "ovs-bond":
return KVMOvsBondObserver()
class XenEnvironment(EnvironmentFactory):
def get_observer(self, obs):
if obs == "vlan":
return XENVirtualVlanObserver()
if obs == "bond":
return XENVirtualBondObserver()
if obs == "nic":
return XENVirtualNicObserver()
class NativeEnvironment(EnvironmentFactory):
def get_observer(self, obs):
if obs == "vlan":
return NativeVlanObserver()
if obs == "bond":
return NativeBondObserver()
if obs == "nic":
return NativeNicObserver()
################################################################################
# class DaemonRunner
################################################################################
class DaemonRunner:
def __init__ (self, driver_name):
self.logger = get_logger(self.__class__.__name__)
self.driver_name = driver_name
self.environment = self.get_environment()
self.parent_scanners = self.get_parent_scanners(driver_name)
self.parents = self.get_parents()
def get_environment (self):
# If xenstore is avaiable, use it
# (OVS/XS do not have libvirt, but has xenstore)
if os.path.exists(XEN_MANAGMENT):
self.supported_observers = ["bond","vlan","nic"]
logger.debug("Running on XEN environment")
self.env_type = XEN_ENV
return XenEnvironment()
# if not (e.g. KVM) then we require virsh (via libvirt)
if os.path.exists(VIRSH_MANAGMENT) or os.path.exists(OVS_MANAGMENT):
#self.supported_observers = ["bond","vlan","nic","ovs-nic","ovs-vlan","ovs-bond"]
self.supported_observers = ["bond","vlan","nic","ovs-nic"]
self.logger.debug("Running on KVM environment")
if os.path.exists(VIRSH_MANAGMENT):
self.env_type = KVM_ENV
# overwrite kvm with ovs
if os.path.exists(OVS_MANAGMENT):
self.logger.debug("Running on OVS environment")
self.env_type = OVS_ENV
return KVMEnvironment()
self.logger.debug("NO MANAGMENT: this is a native server")
self.supported_observers = ["bond","vlan","nic"]
self.logger.debug("Running on Native environment")
self.env_type = NONE_ENV
return NativeEnvironment()
def get_parent_scanners (self, driver_name):
parent_scanners = []
if driver_name == "eth_ipoib": #TODO for loop for capple of driver names
parent_scanners.append(EipoibParentScanner())
return parent_scanners
def get_parents (self):
parent_list = []
for paren_scanner in self.parent_scanners:
parent_list = parent_list + paren_scanner.get_parent_list()
return parent_list
def attach_observers(self):
self.logger.debug("Attach observers to parents")
for parent in self.parents:
for observer in self.supported_observers:
obs = self.environment.get_observer(observer)
parent.register_observer(obs)
def run (self, run_mode, interval):
self.attach_observers()
while(True):
#check if the env changed:
curent_env_type = self.env_type
self.get_environment()
if curent_env_type != self.env_type:
#reinit
self.logger.info("+++++ ENV was changed from %d to %d\n" %(curent_env_type, self.env_type))
self.__init__(self.driver_name)
self.attach_observers()
"""
try:
for parent in self.parents:
if parent.mac == "00:00:00:00:00:00":
self.logger.info("parent: %s has zero mac. skiping..." % (parent))
continue
parent.update_parent()
parent.notify_observers()
except Exception ,e:
logger.error("exception: %s" % str(e))
time.sleep(interval)
"""
for parent in self.parents:
if parent.mac == "00:00:00:00:00:00":
self.logger.info("parent: %s has zero mac. skiping..." % (parent))
continue
parent.update_parent()
parent.notify_observers()
time.sleep(interval)
################################################################################################
#main function
################################################################################################
def main (args):
global logger
parsed_args = parse_args(SCRIPT, DESC, args) #driver_name, run_mode(polling/events), interval
logger = get_logger("Main", parsed_args.log_level)
logger.info("ipoibd: eth over ipoib daemon (%s)" % VERSION)
if signal.signal(signal.SIGTERM, signal.SIG_IGN) == 0:
logger.debug("Signal handler was initialized")
else:
logger.error("Failed to initialized signal handler")
return 1
print_args(SCRIPT, DESC, parsed_args)
if check_double_run():
return 2
runner = DaemonRunner(parsed_args.driver_name)
rc = runner.run(parsed_args.run_mode, parsed_args.interval)
return rc
if __name__ == '__main__':
print "ipoibd: eth over ipoib daemon (%s)" % VERSION
# let the udev to change the name of the interface.
# it is a w/a, the full solution is much more complicated, and need to rebuild the whole topology view, not for now.
time.sleep(1)
try:
rc = main(args = sys.argv[1:])
except KeyboardInterrupt, e:
print "" # start new line after ^C
print("%s daemon (pid %d) interrupted!" % (SCRIPT, os.getpid()))
rc = 2
sys.exit(rc)