Reviewed-by: Daniel Barboza <danielhb(a)linux.vnet.ibm.com>
On 04/25/2017 01:15 PM, Aline Manera wrote:
The idea behind this patch is to eliminate the Ginger Base
dependency.
Almost all the functions in gingerbase.netinfo are for only Kimchi
matters. So move them to Kimchi and when posible use ethtool module to
provide the necessary info
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
control/interfaces.py | 7 +-
model/interfaces.py | 37 ++++-
model/networks.py | 22 ++-
model/ovsbridges.py | 4 +-
network.py | 434 +++++++++++++++++++++++++++++++++++++++++++++++++-
tests/test_model.py | 2 +-
6 files changed, 478 insertions(+), 28 deletions(-)
diff --git a/control/interfaces.py b/control/interfaces.py
index 7b6127a..7aba664 100644
--- a/control/interfaces.py
+++ b/control/interfaces.py
@@ -37,9 +37,4 @@ class Interface(Resource):
@property
def data(self):
- return {'name': self.ident,
- 'type': self.info['type'],
- 'ipaddr': self.info['ipaddr'],
- 'netmask': self.info['netmask'],
- 'status': self.info['status'],
- 'module': self.info['module']}
+ return self.info
diff --git a/model/interfaces.py b/model/interfaces.py
index 6be4356..0532072 100644
--- a/model/interfaces.py
+++ b/model/interfaces.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM Corp, 2015-2016
+# Copyright IBM Corp, 2015-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -17,11 +17,14 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import ethtool
+
from wok.exception import InvalidParameter, NotFoundError
+from wok.stringutils import encode_value
+from wok.utils import wok_log
-from wok.plugins.gingerbase import netinfo
+from wok.plugins.kimchi import network as netinfo
from wok.plugins.kimchi.model.networks import NetworksModel
-from wok.utils import wok_log
class InterfacesModel(object):
@@ -50,7 +53,29 @@ class InterfaceModel(object):
pass
def lookup(self, name):
- try:
- return netinfo.get_interface_info(name)
- except ValueError:
+ if encode_value(name) not in map(encode_value, ethtool.get_devices()):
raise NotFoundError("KCHIFACE0001E", {'name': name})
+
+ ipaddr = ''
+ netmask = ''
+ module = 'unknown'
+ status = 'down'
+ try:
+ ipaddr = ethtool.get_ipaddr(encode_value(name))
+ netmask = ethtool.get_netmask(encode_value(name))
+ module = ethtool.get_module(encode_value(name))
+
+ flags = ethtool.get_flags(encode_value(name))
+ status = 'up' if flags & (ethtool.IFF_RUNNING | ethtool.IFF_UP)
\
+ else 'down'
+ except IOError:
+ pass
+
+ iface_type = netinfo.get_interface_type(name)
+
+ return {'name': name,
+ 'type': iface_type,
+ 'status': status,
+ 'ipaddr': ipaddr,
+ 'netmask': netmask,
+ 'module': module}
diff --git a/model/networks.py b/model/networks.py
index 59b111b..722b97b 100644
--- a/model/networks.py
+++ b/model/networks.py
@@ -28,10 +28,7 @@ from wok.exception import MissingParameter, NotFoundError,
OperationFailed
from wok.utils import run_command, wok_log
from wok.xmlutils.utils import xpath_get_text
-from wok.plugins.gingerbase import netinfo
-from wok.plugins.gingerbase.netinfo import get_vlan_device, is_bridge, is_vlan
-from wok.plugins.gingerbase.netinfo import ports
-from wok.plugins.kimchi import network as knetwork
+from wok.plugins.kimchi import network as netinfo
from wok.plugins.kimchi.config import kimchiPaths
from wok.plugins.kimchi.model.featuretests import FeatureTests
from wok.plugins.kimchi.osinfo import defaults as tmpl_defaults
@@ -130,8 +127,8 @@ class NetworksModel(object):
xml = network.XMLDesc(0)
subnet = NetworkModel.get_network_from_xml(xml)['subnet']
subnet and invalid_addrs.append(ipaddr.IPNetwork(subnet))
- addr_pools = addr_pools if addr_pools else knetwork.PrivateNets
- return knetwork.get_one_free_network(invalid_addrs, addr_pools)
+ addr_pools = addr_pools if addr_pools else netinfo.PrivateNets
+ return netinfo.get_one_free_network(invalid_addrs, addr_pools)
def _set_network_subnet(self, params):
netaddr = params.get('subnet', '')
@@ -281,7 +278,7 @@ class NetworksModel(object):
conn = self.conn.get()
if iface_xml is None:
try:
- mac = knetwork.get_dev_macaddr(str(interface))
+ mac = netinfo.get_dev_macaddr(str(interface))
iface_xml = get_iface_xml({'type': 'ethernet',
'name': interface,
'mac': mac,
@@ -364,7 +361,7 @@ class NetworkModel(object):
connection = 'macvtap'
# exposing the network on linux bridge or macvtap interface
- interface_subnet = knetwork.get_dev_netaddr(interface)
+ interface_subnet = netinfo.get_dev_netaddr(interface)
subnet = subnet if subnet else interface_subnet
# libvirt use format 192.168.0.1/24, standard should be 192.168.0.0/24
@@ -536,10 +533,11 @@ class NetworkModel(object):
# get target device if bridge was created by Kimchi
if connection == 'bridge':
iface = info['interfaces'][0]
- if is_bridge(iface) and iface.startswith(KIMCHI_BRIDGE_PREFIX):
- port = ports(iface)[0]
- if is_vlan(port):
- dev = get_vlan_device(port)
+ if (netinfo.is_bridge(iface) and
+ iface.startswith(KIMCHI_BRIDGE_PREFIX)):
+ port = netinfo.ports(iface)[0]
+ if netinfo.is_vlan(port):
+ dev = netinfo.get_vlan_device(port)
info['interfaces'] = original['interfaces'] =
[dev]
# nic
else:
diff --git a/model/ovsbridges.py b/model/ovsbridges.py
index 212520f..f5bb3df 100644
--- a/model/ovsbridges.py
+++ b/model/ovsbridges.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM Corp, 2016
+# Copyright IBM Corp, 2016-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -17,7 +17,7 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-from wok.plugins.gingerbase.netinfo import ovs_bridges
+from wok.plugins.kimchi.network import ovs_bridges
class OVSBridgesModel(object):
diff --git a/network.py b/network.py
index ec567ca..69944e7 100644
--- a/network.py
+++ b/network.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM Corp, 2015-2016
+# Copyright IBM Corp, 2015-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -19,7 +19,13 @@
#
import ethtool
+import glob
import ipaddr
+import os
+from distutils.spawn import find_executable
+
+from wok.stringutils import encode_value
+from wok.utils import run_command
APrivateNets = ipaddr.IPNetwork("10.0.0.0/8")
@@ -30,6 +36,432 @@ DefaultNetsPool = [ipaddr.IPNetwork('192.168.122.0/23'),
ipaddr.IPNetwork('192.168.124.0/22'),
ipaddr.IPNetwork('192.168.128.0/17')]
+NET_PATH = '/sys/class/net'
+NIC_PATH = '/sys/class/net/*/device'
+BRIDGE_PATH = '/sys/class/net/*/bridge'
+BONDING_PATH = '/sys/class/net/*/bonding'
+WLAN_PATH = '/sys/class/net/*/wireless'
+NET_BRPORT = '/sys/class/net/%s/brport'
+NET_MASTER = '/sys/class/net/%s/master'
+PROC_NET_VLAN = '/proc/net/vlan/'
+BONDING_SLAVES = '/sys/class/net/%s/bonding/slaves'
+BRIDGE_PORTS = '/sys/class/net/%s/brif'
+
+
+def wlans():
+ """Get all wlans declared in /sys/class/net/*/wireless.
+
+ Returns:
+ List[str]: a list with the wlans found.
+
+ """
+ return [b.split('/')[-2] for b in glob.glob(WLAN_PATH)]
+
+
+def nics():
+ """Get all nics of the host.
+
+ This function returns every nic, including those
+ that might be loaded from an usb port.
+
+ Returns:
+ List[str]: a list with the nics found.
+
+ """
+ return list(set([b.split('/')[-2] for b in glob.glob(NIC_PATH)]) -
+ set(wlans()))
+
+
+def is_nic(iface):
+ """Checks if iface is a nic.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a nic, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, nics())
+
+
+def bondings():
+ """Get all bondings of the host.
+
+ Returns:
+ List[str]: a list with the bonds found.
+
+ """
+ return [b.split('/')[-2] for b in glob.glob(BONDING_PATH)]
+
+
+def is_bonding(iface):
+ """Checks if iface is a bond.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bond, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, bondings())
+
+
+def vlans():
+ """Get all vlans of the host.
+
+ Returns:
+ List[str]: a list with the vlans found.
+
+ """
+ return list(set([b.split('/')[-1]
+ for b in glob.glob(NET_PATH + '/*')]) &
+ set([b.split('/')[-1]
+ for b in glob.glob(PROC_NET_VLAN + '*')]))
+
+
+def is_vlan(iface):
+ """Checks if iface is a vlan.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a vlan, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, vlans())
+
+
+def bridges():
+ """Get all bridges of the host.
+
+ Returns:
+ List[str]: a list with the bridges found.
+
+ """
+ return list(set([b.split('/')[-2] for b in glob.glob(BRIDGE_PATH)] +
+ ovs_bridges()))
+
+
+def is_bridge(iface):
+ """Checks if iface is a bridge.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bridge, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, bridges())
+
+
+def is_openvswitch_running():
+ """Checks if the openvswitch service is running in the host.
+
+ Returns:
+ bool: True if openvswitch service is running, False otherwise.
+
+ """
+ cmd = ['systemctl', 'is-active', 'openvswitch',
'--quiet']
+ _, _, r_code = run_command(cmd, silent=True)
+ return r_code == 0
+
+
+def ovs_bridges():
+ """Get the OVS Bridges of the host.
+
+ In some distributions, like Fedora, the files bridge and brif are
+ not created under /sys/class/net/<ovsbridge> for OVS bridges.
+ These specific functions allows one to differentiate OVS bridges
+ from other types of bridges.
+
+ Returns:
+ List[str]: a list with the OVS bridges found.
+
+ """
+ if not is_openvswitch_running():
+ return []
+
+ ovs_cmd = find_executable("ovs-vsctl")
+
+ # openvswitch not installed: there is no OVS bridge configured
+ if ovs_cmd is None:
+ return []
+
+ out, _, r_code = run_command([ovs_cmd, 'list-br'], silent=True)
+ if r_code != 0:
+ return []
+
+ return [x.strip() for x in out.rstrip('\n').split('\n') if
x.strip()]
+
+
+def is_ovs_bridge(iface):
+ """Checks if iface is an OVS bridge.
+
+ In some distributions, like Fedora, the files bridge and brif are
+ not created under /sys/class/net/<ovsbridge> for OVS bridges.
+ These specific functions allows one to differentiate OVS bridges
+ from other types of bridges.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is an OVS bridge, False otherwise.
+
+ """
+ return iface in ovs_bridges()
+
+
+def ovs_bridge_ports(ovsbr):
+ """Get the ports of a OVS bridge.
+
+ In some distributions, like Fedora, the files bridge and brif are
+ not created under /sys/class/net/<ovsbridge> for OVS bridges.
+ These specific functions allows one to differentiate OVS bridges
+ from other types of bridges.
+
+ Args:
+ ovsbr (str): name of the OVS bridge
+
+ Returns:
+ List[str]: a list with the ports of this bridge.
+
+ """
+ if not is_openvswitch_running():
+ return []
+
+ ovs_cmd = find_executable("ovs-vsctl")
+
+ # openvswitch not installed: there is no OVS bridge configured
+ if ovs_cmd is None:
+ return []
+
+ out, _, r_code = run_command([ovs_cmd, 'list-ports', ovsbr], silent=True)
+ if r_code != 0:
+ return []
+
+ return [x.strip() for x in out.rstrip('\n').split('\n') if
x.strip()]
+
+
+def all_interfaces():
+ """Returns all interfaces of the host.
+
+ Returns:
+ List[str]: a list with all interfaces of the host.
+
+ """
+ return [d.rsplit("/", 1)[-1] for d in glob.glob(NET_PATH + '/*')]
+
+
+def slaves(bonding):
+ """Get all slaves from a bonding.
+
+ Args:
+ bonding (str): the name of the bond.
+
+ Returns:
+ List[str]: a list with all slaves.
+
+ """
+ with open(BONDING_SLAVES % bonding) as bonding_file:
+ res = bonding_file.readline().split()
+ return res
+
+
+def ports(bridge):
+ """Get all ports from a bridge.
+
+ Args:
+ bridge (str): the name of the OVS bridge.
+
+ Returns:
+ List[str]: a list with all ports.
+
+ """
+ if bridge in ovs_bridges():
+ return ovs_bridge_ports(bridge)
+
+ return os.listdir(BRIDGE_PORTS % bridge)
+
+
+def is_brport(nic):
+ """Checks if nic is a port of a bridge.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a port of a bridge, False otherwise.
+
+ """
+ ovs_brports = []
+
+ for ovsbr in ovs_bridges():
+ ovs_brports += ovs_bridge_ports(ovsbr)
+
+ return os.path.exists(NET_BRPORT % nic) or nic in ovs_brports
+
+
+def is_bondlave(nic):
+ """Checks if nic is a bond slave.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bond slave, False otherwise.
+
+ """
+ return os.path.exists(NET_MASTER % nic)
+
+
+def operstate(dev):
+ """Get the operstate status of a device.
+
+ Args:
+ dev (str): name of the device.
+
+ Returns:
+ str: "up" or "down"
+
+ """
+ flags = ethtool.get_flags(encode_value(dev))
+ return 'up' if flags & (ethtool.IFF_RUNNING | ethtool.IFF_UP) else
'down'
+
+
+def get_vlan_device(vlan):
+ """ Return the device of the given VLAN.
+
+ Args:
+ vlan (str): the vlan name.
+
+ Returns:
+ str: the device of the VLAN.
+
+ """
+ dev = None
+
+ if os.path.exists(PROC_NET_VLAN + vlan):
+ with open(PROC_NET_VLAN + vlan) as vlan_file:
+ for line in vlan_file:
+ if "Device:" in line:
+ dummy, dev = line.split()
+ break
+ return dev
+
+
+def get_bridge_port_device(bridge):
+ """Return the nics list that belongs to a port of 'bridge'.
+
+ Args:
+ bridge (str): the bridge name.
+
+ Returns:
+ List[str]: the nic list.
+
+ """
+ # br --- v --- bond --- nic1
+ if encode_value(bridge) not in map(encode_value, bridges()):
+ raise ValueError('unknown bridge %s' % bridge)
+ nics_list = []
+ for port in ports(bridge):
+ if encode_value(port) in map(encode_value, vlans()):
+ device = get_vlan_device(port)
+ if encode_value(device) in map(encode_value, bondings()):
+ nics_list.extend(slaves(device))
+ else:
+ nics_list.append(device)
+ if encode_value(port) in map(encode_value, bondings()):
+ nics_list.extend(slaves(port))
+ else:
+ nics_list.append(port)
+ return nics_list
+
+
+def aggregated_bridges():
+ """Get the list of aggregated bridges of the host.
+
+ Returns:
+ List[str]: the aggregated bridges list.
+
+ """
+ return [bridge for bridge in bridges() if
+ (set(get_bridge_port_device(bridge)) & set(nics()))]
+
+
+def bare_nics():
+ """Get the list of bare nics of the host.
+
+ A nic is called bare when it is not a port of a bridge
+ or a slave of bond.
+
+ Returns:
+ List[str]: the list of bare nics of the host.
+
+ """
+ return [nic for nic in nics() if not (is_brport(nic) or is_bondlave(nic))]
+
+
+def is_bare_nic(iface):
+ """Checks if iface is a bare nic.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bare nic, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, bare_nics())
+
+
+# The nic will not be exposed when it is a port of a bridge or
+# a slave of bond.
+# The bridge will not be exposed when all it's port are tap.
+def all_favored_interfaces():
+ """Get the list of all favored interfaces of the host.
+
+ The nic will not be exposed when it is a port of a bridge or
+ a slave of bond. The bridge will not be exposed when all its
+ port are tap.
+
+ Returns:
+ List[str]: the list of favored interfaces.
+
+ """
+ return aggregated_bridges() + bare_nics() + bondings()
+
+
+def get_interface_type(iface):
+ """Get the interface type of iface.
+
+ Types supported: nic, bonding, bridge, vlan. If the type
+ can't be verified, 'unknown' is returned.
+
+ Args:
+ iface (str): the interface name.
+
+ Returns:
+ str: the interface type.
+
+ """
+ try:
+ if is_nic(iface):
+ return "nic"
+ if is_bonding(iface):
+ return "bonding"
+ if is_bridge(iface):
+ return "bridge"
+ if is_vlan(iface):
+ return "vlan"
+ return 'unknown'
+ except IOError:
+ return 'unknown'
+
def get_dev_macaddr(dev):
info = ethtool.get_interfaces_info(dev)[0]
diff --git a/tests/test_model.py b/tests/test_model.py
index 95c9e08..5398d08 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -48,7 +48,7 @@ from wok.rollbackcontext import RollbackContext
from wok.utils import convert_data_size
from wok.xmlutils.utils import xpath_get_text
-from wok.plugins.gingerbase import netinfo
+from wok.plugins.kimchi import network as netinfo
from wok.plugins.kimchi import osinfo
from wok.plugins.kimchi.config import kimchiPaths as paths
from wok.plugins.kimchi.model import model