cd8cdecb47
Change-Id: I86c6b861b891e0a0dac3245a41d4fb611a3e438d
1758 lines
65 KiB
Python
Executable File
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)
|
|
|