#! /usr/bin/python
# -*- 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
# 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"
FORMAT = '%(asctime)s :[%(name)s] %(levelname)s: %(message)s'
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 += 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):
cmd = "/sbin/ip link show %s 2> /dev/null | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2} | /usr/bin/head -1'" % name
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
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:
##
##
## ..
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 1
if len(out) == 0:
return []
except:
self.logger.warning("could not find sysfs file (%s)!" % sysfs_path)
return 2
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"
#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")
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
# takes peer's macs in order to support veth interfaces (for dhcp client for example)
def get_peer_mac(self, nic):
peer = None
mac = None
#get the index of the peer in the ip link list.
cmd = "ethtool -S %s |grep peer_ifindex |awk -F': ' '{print $2}'" % nic
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.warning("Failed to run cmd:%s: for nic: %s" % (cmd, nic))
else:
peer_if_idx = out.strip()
#get the peer name
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.warning("Failed to get peer for nic: %s peer_idx %s, cmd: %s" % (nic, peer_if_idx, cmd))
else:
peer = out.strip()
if peer:
#getting the mac of the peer
peer_addr_path = "/sys/class/net/%s/address" % peer
cmd = "cat " + peer_addr_path
(rc, out) = commands.getstatusoutput(cmd)
if os.path.exists(peer_addr_path):
(rc, out) = commands.getstatusoutput(cmd)
if rc or len(out) == 0:
self.logger.warning("Failed to get peer mac address: peer %s, cmd: " % (peer, cmd))
else:
mac = out.strip()
return mac
#get the macs of all the virtual interfaces attached to the bridge.
def get_nic_vif_macs(self, nic):
macs = []
br = self.get_br_by_nic(nic)
if br == None:
return []
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)))
for nic in nics:
# if slave is not vif it will return None
mac = self.get_vif_mac(nic, domains)
if mac != None:
macs.append(mac)
self.logger.debug("Guest %s mac %s" % (nic, str(mac)))
#if the nic that connected to the bridge is peer than we need also the peer mac address
peer_mac = self.get_peer_mac(nic)
if peer_mac:
macs.append(peer_mac)
self.logger.debug("nic %s peer-mac %s" % (nic, str(peer_mac)))
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"))
rc = rc or self.parent.update_files("+", slave + " " + mac + " " + vlan_id, self.parent.get_sysfs_path(parent, "vifs"))
return rc
def create_nic (self, mac, parent):
self.logger.debug("Starting create nic")
rc = 0
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.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)
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)
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
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" % (vlan, vlan_mac))
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):
ib_vlan_if = self.get_slave_prefix(vlan)
ib_vlan_if_path = str("/sys/class/net/%s" % ib_vlan_if)
self.logger.debug("PATH for ib vlan: %s " % ib_vlan_if_path)
if os.path.exists(ib_vlan_if_path):
if vlan in current_vlans:
self.logger.debug("The child interface %s already exists:" % ib_vlan_if)
else:
self.logger.debug("===> Mapping for vlan %s %s" % (vlan, ib_vlan_if))
if self.create_child("0x" + str(self.get_pif_ib_vlan_index(vlan))):
self.logger.info("Clone vlan %s was created" % ib_vlan_if)
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))
# 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
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()
################################################
# OVS bridge support:
# prefix: KVMOvs
################################################
class KVMOvsNicObserver(VirtualNicObserver, KVMDomain):
def __init__(self):
super(KVMOvsNicObserver, self).__init__()
self.name = "ovs-nic"
def get_br_by_nic(self, nic):
self.logger.debug("Starting get_br_by_nic FOR OVS")
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 []
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()
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")
return XenEnvironment()
# if not (e.g. KVM) then we require virsh (via libvirt)
if os.path.exists(VIRSH_MANAGMENT):
self.supported_observers = ["bond","vlan","nic","ovs-nic","ovs-vlan","ovs-bond"]
self.logger.debug("Running on KVM environment")
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")
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):
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)
################################################################################################
#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)
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__':
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)