[node-patches] Change in ovirt-node[master]: [DRAFT] network: Rework runtime network model
fabiand at fedoraproject.org
fabiand at fedoraproject.org
Thu Jun 6 13:13:31 UTC 2013
Fabian Deutsch has uploaded a new change for review.
Change subject: [DRAFT] network: Rework runtime network model
......................................................................
[DRAFT] network: Rework runtime network model
Previously there were strong assumptions about the runtime env, they are
relaxed now. The code is able to parse more complex setups more generic.
Bug-Url: https://bugzilla.redhat.com/show_bug.cgi?id=971410
Change-Id: I8053ea21af5029190bc16b89ee109d2d7937a05f
Signed-off-by: Fabian Deutsch <fabiand at fedoraproject.org>
---
M src/ovirt/node/base.py
M src/ovirt/node/config/defaults.py
M src/ovirt/node/config/network.py
M src/ovirt/node/setup/cim/cim_model.py
M src/ovirt/node/setup/core/network_page.py
M src/ovirt/node/setup/snmp/snmp_model.py
M src/ovirt/node/utils/fs.py
M src/ovirt/node/utils/network.py
8 files changed, 700 insertions(+), 529 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/11/15411/1
diff --git a/src/ovirt/node/base.py b/src/ovirt/node/base.py
index 5d6d12f..6d62b1e 100644
--- a/src/ovirt/node/base.py
+++ b/src/ovirt/node/base.py
@@ -52,6 +52,14 @@
return [(k, v) for k, v in self.__dict__.items()
if isinstance(v, Base.Signal)]
+ def build_str(self, attributes=[], additional_pairs={}, name=None):
+ name = name or self.__class__.__name__
+ attrs = dict((k, self.__dict__[k]) for k in attributes)
+ attrs.update(additional_pairs)
+ attrs = " ".join(["%s='%s'" % i for i in sorted(attrs.items())])
+ addr = hex(id(self))
+ return ("<%s>" % " ".join(v for v in [name, attrs, "at", addr] if v))
+
class Signal(object):
"""A convenience class for easier access to signals
"""
diff --git a/src/ovirt/node/config/defaults.py b/src/ovirt/node/config/defaults.py
index 1d7d54d..246fc51 100644
--- a/src/ovirt/node/config/defaults.py
+++ b/src/ovirt/node/config/defaults.py
@@ -19,7 +19,8 @@
# MA 02110-1301, USA. A copy of the GNU General Public License is
# also available at http://www.gnu.org/copyleft/gpl.html.
from ovirt.node import base, exceptions, valid, utils, config
-from ovirt.node.utils import fs, storage
+from ovirt.node.utils import storage, process
+from ovirt.node.utils.fs import ShellVarFile
import glob
import logging
import os
@@ -45,101 +46,23 @@
OVIRT_NODE_DEFAULTS_FILENAME = "/etc/default/ovirt"
-class SimpleProvider(base.Base):
- """SimpleProvider writes simple KEY=VALUE (shell-like) configuration file
-
- >>> fn = "/tmp/cfg_dummy.simple"
- >>> open(fn, "w").close()
- >>> cfg = {
- ... "IP_ADDR": "127.0.0.1",
- ... "NETMASK": "255.255.255.0",
- ... }
- >>> p = SimpleProvider(fn)
- >>> p.get_dict()
- {}
- >>> p.update(cfg, True)
- >>> p.get_dict() == cfg
- True
+def exists():
+ """Determin if the defaults file exists
"""
- def __init__(self, filename):
- super(SimpleProvider, self).__init__()
- self.filename = filename
- if not os.path.exists(self.filename):
- raise RuntimeError("File does not exist: %s" % self.filename)
- self.logger.debug("Using %s" % self.filename)
-
- def update(self, new_dict, remove_empty):
- cfg = self.get_dict()
- cfg.update(new_dict)
-
- for key, value in cfg.items():
- if remove_empty and value is None:
- del cfg[key]
- if value is not None and type(value) not in [str, unicode]:
- raise TypeError("The type (%s) of %s is not allowed" %
- (type(value), key))
- self._write(cfg)
-
- def get_dict(self):
- with open(self.filename) as source:
- cfg = self._parse_dict(source)
- return cfg
-
- def _parse_dict(self, source):
- """Parse a simple shell-var-style lines into a dict:
-
- >>> import StringIO
- >>> txt = "# A comment\\n"
- >>> txt += "A=ah\\n"
- >>> txt += "B=beh\\n"
- >>> txt += "C=\\"ceh\\"\\n"
- >>> txt += "D=\\"more=less\\"\\n"
- >>> p = SimpleProvider("/tmp/cfg_dummy")
- >>> sorted(p._parse_dict(StringIO.StringIO(txt)).items())
- [('A', 'ah'), ('B', 'beh'), ('C', 'ceh'), ('D', 'more=less')]
- """
- cfg = {}
- for line in source:
- line = line.strip()
- if line.startswith("#"):
- continue
- try:
- key, value = line.split("=", 1)
- cfg[key] = value.strip("\"' \n")
- except:
- self.logger.info("Failed to parse line: '%s'" % line)
- return cfg
-
- def _write(self, cfg):
- lines = []
- # Sort the dict, looks nicer
- for key in sorted(cfg.iterkeys()):
- lines.append("%s=\"%s\"" % (key, cfg[key]))
- contents = "\n".join(lines) + "\n"
-
- # The following logic is mainly needed to allow an "offline" testing
- config_fs = fs.Config()
- if config_fs.is_enabled():
- with config_fs.open_file(self.filename, "w") as dst:
- dst.write(contents)
- else:
- try:
- fs.atomic_write(self.filename, contents)
- except Exception as e:
- self.logger.warning("Atomic write failed: %s" % e)
- with open(self.filename, "w") as dst:
- dst.write(contents)
+ return os.path.exists(OVIRT_NODE_DEFAULTS_FILENAME)
-class ConfigFile(base.Base):
- """ConfigFile is a specififc interface to some configuration file with a
- specififc syntax
+class NodeConfigFile(base.Base):
+ """NodeConfigFile is a specififc interface to some configuration file
+ with a specififc syntax
"""
- def __init__(self, filename=None, provider_class=None):
- super(ConfigFile, self).__init__()
- filename = filename or OVIRT_NODE_DEFAULTS_FILENAME
- provider_class = provider_class or SimpleProvider
- self.provider = provider_class(filename)
+ def __init__(self, filename=OVIRT_NODE_DEFAULTS_FILENAME):
+ super(NodeConfigFile, self).__init__()
+ if filename == OVIRT_NODE_DEFAULTS_FILENAME \
+ and not os.path.exists(filename):
+ raise RuntimeError("Node config file does not exist: %s" %
+ filename)
+ self.provider = ShellVarFile(filename, create=True)
def update(self, new_dict, remove_empty=False):
"""Reads /etc/defaults/ovirt and creates a dictionary
@@ -167,7 +90,7 @@
def __init__(self, cfgfile=None):
super(NodeConfigFileSection, self).__init__()
- self.defaults = cfgfile or ConfigFile()
+ self.defaults = cfgfile or NodeConfigFile()
def update(self, *args, **kwargs):
"""This function set's the correct entries in the defaults file for
@@ -295,8 +218,7 @@
- OVIRT_IP_ADDRESS, OVIRT_IP_NETMASK, OVIRT_IP_GATEWAY
- OVIRT_VLAN
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = Network(cfgfile)
>>> n.update("eth0", "static", "10.0.0.1", "255.0.0.0", "10.0.0.255",
... "20")
@@ -365,9 +287,10 @@
def configure_no_networking(self, iface=None):
"""Can be used to disable all networking
"""
- iface = iface or self.retrieve()["iface"]
- name = iface + "-DISABLED"
- self.update(name, None, None, None, None, None)
+ #iface = iface or self.retrieve()["iface"]
+ #name = iface + "-DISABLED"
+ # FIXME why should we use ifname-DISABLED here?
+ self.update(None, None, None, None, None, None)
def configure_dhcp(self, iface, vlanid=None):
"""Can be used to configure NIC iface on the vlan vlanid with DHCP
@@ -380,6 +303,29 @@
self.update(iface, "static", ipaddr, netmask, gateway, vlanid)
+class NetworkTopology(NodeConfigFileSection):
+ """Sets the network topology
+ - OVIRT_NETWORK_TOPOLOGY
+
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
+ >>> n = NetworkTopology(cfgfile)
+ >>> n.update("legacy")
+ >>> sorted(n.retrieve().items())
+ [('topology', 'legacy')]
+ """
+ keys = ("OVIRT_NETWORK_TOPOLOGY",)
+ known_topologies = ["legacy",
+ # Legacy way, a bridge is created for BOOTIF
+
+ "direct"
+ # The BOOTIF NIC is configured directly
+ ]
+
+ @NodeConfigFileSection.map_and_update_defaults_decorator
+ def update(self, topology="legacy"):
+ assert topology in self.known_topologies
+
+
class IPv6(NodeConfigFileSection):
"""Sets IPv6 network stuff
- OVIRT_IPV6 (static, auto, dhcp)
@@ -387,8 +333,7 @@
- OVIRT_IPV6_NETMASK
- OVIRT_IPV6_GATEWAY
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = IPv6(cfgfile)
>>> n.update("auto", "11::22", "11::33", "11::44")
>>> data = sorted(n.retrieve().items())
@@ -446,8 +391,7 @@
class Hostname(NodeConfigFileSection):
"""Configure hostname
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> hostname = "host.example.com"
>>> n = Hostname(cfgfile)
>>> n.update(hostname)
@@ -496,8 +440,7 @@
class Nameservers(NodeConfigFileSection):
"""Configure nameservers
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> servers = ["10.0.0.2", "10.0.0.3"]
>>> n = Nameservers(cfgfile)
>>> n.update(servers)
@@ -615,8 +558,7 @@
class Timeservers(NodeConfigFileSection):
"""Configure timeservers
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> servers = ["10.0.0.4", "10.0.0.5", "0.example.com"]
>>> n = Timeservers(cfgfile)
>>> n.update(servers)
@@ -672,8 +614,7 @@
class Syslog(NodeConfigFileSection):
"""Configure rsyslog
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> server = "10.0.0.6"
>>> port = "514"
>>> n = Syslog(cfgfile)
@@ -711,8 +652,7 @@
class Collectd(NodeConfigFileSection):
"""Configure collectd
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> server = "10.0.0.7"
>>> port = "42"
>>> n = Collectd(cfgfile)
@@ -756,8 +696,7 @@
class KDump(NodeConfigFileSection):
"""Configure kdump
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> nfs_url = "host.example.com:/dst/path"
>>> ssh_url = "root at host.example.com"
>>> n = KDump(cfgfile)
@@ -905,8 +844,7 @@
class iSCSI(NodeConfigFileSection):
"""Configure iSCSI
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = iSCSI(cfgfile)
>>> n.update("iqn.1992-01.com.example:node",
... "iqn.1992-01.com.example:target", "10.0.0.8", "42")
@@ -948,8 +886,7 @@
class Netconsole(NodeConfigFileSection):
"""Configure netconsole
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = Netconsole(cfgfile)
>>> server = "10.0.0.9"
>>> port = "666"
@@ -984,8 +921,7 @@
class Logrotate(NodeConfigFileSection):
"""Configure logrotate
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = Logrotate(cfgfile)
>>> max_size = "42"
>>> n.update(max_size)
@@ -1018,8 +954,7 @@
class Keyboard(NodeConfigFileSection):
"""Configure keyboard
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = Keyboard(cfgfile)
>>> layout = "de_DE.UTF-8"
>>> n.update(layout)
@@ -1056,8 +991,7 @@
class NFSv4(NodeConfigFileSection):
"""Configure NFSv4
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = NFSv4(cfgfile)
>>> domain = "foo.example"
>>> n.update(domain)
@@ -1092,8 +1026,7 @@
class SSH(NodeConfigFileSection):
"""Configure SSH
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = SSH(cfgfile)
>>> pwauth = True
>>> num_bytes = "24"
@@ -1162,8 +1095,7 @@
"""Configure storage
This is a class to handle the storage parameters used at installation time
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = Installation(cfgfile)
>>> kwargs = {"init": ["/dev/sda"], "root_install": "1"}
>>> n.update(**kwargs)
diff --git a/src/ovirt/node/config/network.py b/src/ovirt/node/config/network.py
index 5a3a8e6..0f70a77 100644
--- a/src/ovirt/node/config/network.py
+++ b/src/ovirt/node/config/network.py
@@ -20,9 +20,10 @@
# also available at http://www.gnu.org/copyleft/gpl.html.
from ovirt.node import utils, base
from ovirt.node.utils import AugeasWrapper as Augeas
+from ovirt.node.utils.fs import ShellVarFile
+import glob
import logging
import os
-import glob
"""
Some convenience functions related to networking
@@ -32,61 +33,102 @@
LOGGER = logging.getLogger(__name__)
-def iface(iface):
- """Retuns the config of an iface
-
- Args:
- iface: Interface to retrieve the config for
- Returns:
- A dict of (nic-name, nic-infos-dict)
+class NicConfig(base.Base):
+ """A common interface to NIC configuration options
"""
- LOGGER.debug("Getting configuration for '%s'" % iface)
- Augeas.force_reload()
+ ifname = None
- info = {}
+ type = None
+ bootproto = None
+ ipaddr = None
+ netmask = None
+ gateway = None
+ bridge = None
+ vlan = None
+ device = None
+ onboot = None
- aug = Augeas()
- filepath = "/etc/sysconfig/network-scripts/ifcfg-%s" % iface
- augdevicepath = "/files%s" % filepath
+ ipv6init = None
+ ipv6forwarding = None
+ ipv6_autoconf = None
+ dhcpv6c = None
+ ipv6addr = None
+ ipv6_defaultgw = None
- if not os.path.exists(filepath):
- LOGGER.debug("No config file %s" % filepath)
+ vlan_parent = None
- # Type
- info["type"] = aug.get(augdevicepath + "/TYPE", True)
+ _backend = None
+ _keys = ["bridge", "type", "bootproto", "ipaddr", "netmask",
+ "gateway", "vlan", "device", "onboot", "hwaddr",
+ "ipv6init", "ipv6forwarding", "ipv6_autoconf",
+ "dhcpv6c", "ipv6addr", "ipv6_defaultgw"]
- # Bootprotocol
- info["bootproto"] = aug.get(augdevicepath + "/BOOTPROTO", True)
+ def __init__(self, ifname):
+ super(NicConfig, self).__init__()
+ self.ifname = ifname
+ self._backend = NicConfig.IfcfgBackend(self)
+ self.load()
- # IPV4
- for p in ["IPADDR", "NETMASK", "GATEWAY"]:
- info[p.lower()] = aug.get(augdevicepath + "/" + p, True)
+ def exists(self):
+ """Return if a config exists
+ """
+ return self._backend.exists(self.ifname)
- # FIXME IPv6
+ def load(self):
+ data = self._backend.read(self.ifname)
+ # Additional convenience stuff
+ self.vlan = self.vlan.strip() if self.vlan else self.vlan
+ if self.vlan:
+ parts = self.ifname.split(".")
+ self.vlan_id = parts[-1:][0]
+ self.vlan_parent = ".".join(parts[:-1])
- # Parent bridge
- info["bridge"] = aug.get(augdevicepath + "/BRIDGE", True)
+ self.logger.debug("Found VLAN %s on %s" %
+ (str(self.vlan_id), self.ifname))
+ return data
- # VLAN
- info["is_vlan"] = aug.get(augdevicepath + "/VLAN", True) is not None
- name_says_vlan = "." in iface
- if info["is_vlan"] != name_says_vlan:
- LOGGER.warning("NIC config says the device is a VLAN, but the name" +
- "doesn't reflect that: %s (%s vs %s)" % (iface,
- info["is_vlan"], name_says_vlan))
+ def save(self):
+ return self._backend.write(self.ifname)
- if info["is_vlan"] is True:
- parts = iface.split(".")
- vlanid = parts[-1:][0]
- info["vlanid"] = vlanid
- info["vlan_parent"] = ".".join(parts[:-1])
+ def __str__(self):
+ return self.build_str("ifname")
- info["type"] = "vlan"
- LOGGER.debug("Found VLAN %s on %s" % (str(vlanid), iface))
- else:
- info["vlanid"] = None
+ class IfcfgBackend(base.Base):
+ filename_tpl = "/etc/sysconfig/network-scripts/ifcfg-%s"
- return info
+ def __init__(self, cfg):
+ super(NicConfig.IfcfgBackend, self).__init__()
+ self.cfg = cfg
+
+ def exists(self, ifname):
+ """Return true if a config for ifname exists
+ """
+ return os.path.isfile(self.filename_tpl % ifname)
+
+ def read(self, ifname):
+ """Read values frmo a ifcfg file and update self.cfg
+ """
+ filename = self.filename_tpl % ifname
+
+ if not os.path.exists(filename):
+ self.logger.info("Config not found for %s" % ifname)
+ return
+
+ data = ShellVarFile(filename).get_dict()
+
+ for k in self.cfg._keys:
+ self.cfg.__dict__[k] = data.get(k.upper(), None)
+
+ def write(self, cfg):
+ """Write a ifcfg file from the cfg
+ """
+ assert type(cfg) is NicConfig
+ filename = self.filename_tpl % cfg.ifname
+ dst = ShellVarFile(filename)
+ data = {}
+ for k in self.cfg._keys:
+ data[k.upper()] = cfg.__dict__.get(k)
+ dst.update(data, True)
def _aug_get_or_set(augpath, new_servers=None):
@@ -173,44 +215,6 @@
return cfg_hostname
-class Ifcfg(base.Base):
- """Object to access ifcfg-%ifname
- """
- bridge = None
- type = None
- bootproto = None
- ipaddr = None
- netmask = None
- gateway = None
- vlan = None
- device = None
- hwaddr = None
- onboot = None
-
- def __init__(self, iface):
- self.iface = iface
- self.aug = Augeas()
- self.load_properties()
-
- def load_properties(self):
- Augeas.force_reload()
- for p in ["bridge", "type", "bootproto", "ipaddr", "netmask",
- "gateway", "vlan", "device", "onboot", "hwaddr"]:
- self.__dict__[p] = self.ifcfg_property(p.upper())
-
- def ifcfg_property(self, name):
- filepath = "/etc/sysconfig/network-scripts/ifcfg-%s" % self.iface
- augdevicepath = "/files%s" % filepath
-
- value = None
- if os.path.exists(filepath):
- value = self.aug.get("%s/%s" % (augdevicepath, name), True)
- else:
- LOGGER.debug("No config file %s" % filepath)
-
- return value
-
-
def ifaces():
"""Returns all configured ifaces
"""
@@ -220,32 +224,3 @@
iface = fn[len(filepath):]
ifaces.append(iface)
return ifaces
-
-
-def node_bridge():
- """Return the configured bridge
-
- Returns:
- Ifname of a configured bridge or None if none is configured
- """
- bridge = None
- for iface in ifaces():
- nic = Ifcfg(iface)
- if nic.type == u"Bridge":
- bridge = iface
- break
- return bridge
-
-
-def node_bridge_slave():
- """Returns the interface which is the slave of the configfured bridge
- """
- slave = None
- bridge = node_bridge()
- if bridge:
- for iface in ifaces():
- nic = Ifcfg(iface)
- if nic.bridge == bridge:
- slave = iface
- break
- return slave
diff --git a/src/ovirt/node/setup/cim/cim_model.py b/src/ovirt/node/setup/cim/cim_model.py
index 72453b0..798aa0e 100644
--- a/src/ovirt/node/setup/cim/cim_model.py
+++ b/src/ovirt/node/setup/cim/cim_model.py
@@ -31,9 +31,8 @@
class CIM(NodeConfigFileSection):
"""Configure CIM
- >>> from ovirt.node.config.defaults import ConfigFile, SimpleProvider
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> from ovirt.node.config.defaults import NodeConfigFile
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = CIM(cfgfile)
>>> n.update(True)
>>> n.retrieve()
diff --git a/src/ovirt/node/setup/core/network_page.py b/src/ovirt/node/setup/core/network_page.py
index 4a7aedd..6b668af 100644
--- a/src/ovirt/node/setup/core/network_page.py
+++ b/src/ovirt/node/setup/core/network_page.py
@@ -21,8 +21,9 @@
from ovirt.node import plugins, ui, valid, utils, config
from ovirt.node.config import defaults
from ovirt.node.plugins import Changeset
-from ovirt.node.utils import network
from ovirt.node.setup.core import ping
+from ovirt.node.utils import network
+from ovirt.node.utils.network import BridgedNIC
"""
Network page plugin
@@ -126,17 +127,20 @@
return page
def _get_nics(self):
- justify = lambda txt, l: txt.ljust(l)[0:l]
+ def justify(txt, l):
+ txt = txt if txt else ""
+ return txt.ljust(l)[0:l]
node_nics = []
first_nic = None
- for name, nic in sorted(utils.network.node_nics().items()):
+ model = utils.network.NodeNetwork()
+ for name, nic in sorted(model.nics().items()):
if first_nic is None:
first_nic = name
- bootproto = "Configured" if nic["bootproto"] else "Unconfigured"
- description = " ".join([justify(nic["name"], 7),
- justify(bootproto, 13),
- justify(nic["vendor"], 14),
- justify(nic["hwaddr"], 17)
+ is_cfg = "Configured" if nic.is_configured() else "Unconfigured"
+ description = " ".join([justify(nic.ifname, 7),
+ justify(is_cfg, 13),
+ justify(nic.vendor, 14),
+ justify(nic.hwaddr, 17)
])
node_nics.append((name, description))
return node_nics
@@ -216,8 +220,8 @@
return
if "dialog.nic.identify" in changes:
- iface = self._model_extra["dialog.nic.iface"]
- utils.network.NIC(iface).identify()
+ ifname = self._model_extra["dialog.nic.ifname"]
+ utils.network.NIC(ifname).identify()
self.application.notice("Flashing lights now")
return
@@ -275,7 +279,7 @@
ipv6_bootproto, ipv6_address, ipv6_netmask,
ipv6_gateway, vlanid):
vlanid = vlanid or None
- iface = self._model_extra["dialog.nic.iface"]
+ iface = self._model_extra["dialog.nic.ifname"]
model = defaults.Network()
ipv6model = defaults.IPv6()
@@ -332,64 +336,63 @@
class NicDetailsDialog(ui.Dialog):
plugin = None
- def __init__(self, plugin, iface):
+ def __init__(self, plugin, ifname):
super(NicDetailsDialog, self).__init__("dialog.nic",
- "NIC Details: %s" % iface, [])
+ "NIC Details: %s" % ifname, [])
self.plugin = plugin
# Populate model with nic specific informations
- self.logger.debug("Building NIC details dialog for %s" % iface)
+ self.logger.debug("Building NIC details dialog for %s" % ifname)
- self.logger.debug("Getting informations for NIC details page")
- live = utils.network.node_nics()[iface]
- cfg = defaults.Network().retrieve()
- ip6cfg = defaults.IPv6().retrieve()
+ nic = utils.network.NodeNetwork().nics()[ifname]
- self.logger.debug("live: %s" % live)
- self.logger.debug("cfg: %s" % cfg)
- self.logger.debug("ipv6cfg: %s" % ip6cfg)
+ model = defaults.Network().retrieve()
+ ip6model = defaults.IPv6().retrieve()
- # The primary interface of this Node:
- node_bridge_slave = config.network.node_bridge_slave()
+ self.logger.debug("nic: %s" % nic)
+ self.logger.debug("model: %s" % model)
+ self.logger.debug("ip6model: %s" % ip6model)
- if node_bridge_slave and node_bridge_slave != iface:
- # The config contains the information for the primary iface,
- # because this iface is not the primary iface we clear the config
- cfg = dict((k, "") for k in cfg.keys())
+ is_primary_interface = model["iface"] == ifname
- ipaddr, netmask, gateway, vlanid = (cfg["ipaddr"], cfg["netmask"],
- cfg["gateway"], cfg["vlanid"])
+ if not is_primary_interface:
+ # The config contains the information for the primary ifnamee,
+ # because this ifnamee is not the primaryifnameme we clear the
+ # config
+ model = dict((k, "") for k in model.keys())
- ip6addr, ip6netmask, ip6gateway, ip6bootproto = (ip6cfg["ipaddr"],
- ip6cfg["netmask"],
- ip6cfg["gateway"],
- ip6cfg["bootproto"])
+ ipaddr, netmask, gateway, vlanid = (model["ipaddr"], model["netmask"],
+ model["gateway"], model["vlanid"])
- bridge_nic = utils.network.NIC(live["bridge"])
- if cfg["bootproto"] == "dhcp":
- if bridge_nic.exists():
- routes = utils.network.Routes()
- ipaddr, netmask = bridge_nic.ipv4_address().items()
- gateway = routes.default()
- vlanid = bridge_nic.vlanid()
- else:
- self.logger.warning("Bridge assigned but couldn't gather " +
- "live info: %s" % bridge_nic)
+ ip6addr, ip6netmask, ip6gateway, ip6bootproto = (ip6model["ipaddr"],
+ ip6model["netmask"],
+ ip6model["gateway"],
+ ip6model["bootproto"])
- link_status_txt = ("Connected" if live["link_detected"]
+ if type(nic) is BridgedNIC:
+ if model["bootproto"] == "dhcp":
+ if nic.bridge_nic.exists():
+ routes = utils.network.Routes()
+ gateway = routes.default()
+ ipaddr, netmask = nic.bridge_nic.ipv4_address().items()
+ vlanid = ",".join(nic.bridge_nic.vlanids())
+ else:
+ self.logger.warning("Bridge assigned but couldn't " +
+ "gather nic info: %s" % nic.bridge_nic)
+
+ link_status_txt = ("Connected" if nic.has_link()
else "Disconnected")
-
- self.logger.debug("xxxxx bootporoto: %s" % cfg)
+ vendor_txt = nic.vendor[:24] if nic.vendor else ""
self.plugin._model_extra.update({
- "dialog.nic.iface": live["name"],
- "dialog.nic.driver": live["driver"],
- "dialog.nic.protocol": live["bootproto"] or "N/A",
- "dialog.nic.vendor": live["vendor"][:24],
+ "dialog.nic.ifname": nic.ifname,
+ "dialog.nic.driver": nic.driver,
+ "dialog.nic.protocol": nic.config.bootproto or "N/A",
+ "dialog.nic.vendor": vendor_txt,
"dialog.nic.link_status": link_status_txt,
- "dialog.nic.hwaddress": live["hwaddr"],
+ "dialog.nic.hwaddress": nic.hwaddr,
- "dialog.nic.ipv4.bootproto": cfg["bootproto"],
+ "dialog.nic.ipv4.bootproto": model["bootproto"],
"dialog.nic.ipv4.address": ipaddr,
"dialog.nic.ipv4.netmask": netmask,
"dialog.nic.ipv4.gateway": gateway,
@@ -404,7 +407,8 @@
padd = lambda l: l.ljust(12)
ws = [ui.Row("dialog.nic._row[0]",
- [ui.KeywordLabel("dialog.nic.iface", padd("Interface: ")),
+ [ui.KeywordLabel("dialog.nic.ifname",
+ padd("Interface: ")),
ui.KeywordLabel("dialog.nic.driver", padd("Driver: ")),
]),
ui.Row("dialog.nic._row[1]",
diff --git a/src/ovirt/node/setup/snmp/snmp_model.py b/src/ovirt/node/setup/snmp/snmp_model.py
index 5855e35..2ad6d5b 100644
--- a/src/ovirt/node/setup/snmp/snmp_model.py
+++ b/src/ovirt/node/setup/snmp/snmp_model.py
@@ -70,9 +70,8 @@
class SNMP(NodeConfigFileSection):
"""Configure SNMP
- >>> from ovirt.node.config.defaults import ConfigFile, SimpleProvider
- >>> fn = "/tmp/cfg_dummy"
- >>> cfgfile = ConfigFile(fn, SimpleProvider)
+ >>> from ovirt.node.config.defaults import NodeConfigFile
+ >>> cfgfile = NodeConfigFile("/tmp/cfg_dummy")
>>> n = SNMP(cfgfile)
>>> n.update("secret")
>>> n.retrieve().items()
diff --git a/src/ovirt/node/utils/fs.py b/src/ovirt/node/utils/fs.py
index 429c9e8..580d543 100644
--- a/src/ovirt/node/utils/fs.py
+++ b/src/ovirt/node/utils/fs.py
@@ -215,3 +215,105 @@
def open_file(self, filename, mode="r"):
return open(self._config_path(filename), mode)
+
+
+class ShellVarFile(base.Base):
+ """ShellVarFile writes simple KEY=VALUE (shell-like) configuration file
+
+ >>> fn = "/tmp/cfg_dummy.simple"
+ >>> open(fn, "w").close()
+ >>> cfg = {
+ ... "IP_ADDR": "127.0.0.1",
+ ... "NETMASK": "255.255.255.0",
+ ... }
+ >>> p = ShellVarFile(fn)
+ >>> p.get_dict()
+ {}
+ >>> p.update(cfg, True)
+ >>> p.get_dict() == cfg
+ True
+ """
+ def __init__(self, filename, create=False):
+ super(ShellVarFile, self).__init__()
+ self.filename = filename
+ if not os.path.exists(self.filename):
+ if create:
+ self._write("")
+ else:
+ raise RuntimeError("File does not exist: %s" % self.filename)
+
+ def get_dict(self):
+ """Returns a dict of (key, value) pairs
+ """
+ with open(self.filename) as source:
+ cfg = self._parse_dict(source)
+ return cfg
+
+ def write(self, cfg, remove_empty=True):
+ for key, value in cfg.items():
+ if remove_empty and value is None:
+ del cfg[key]
+ if value is not None and type(value) not in [str, unicode]:
+ raise TypeError("The type (%s) of %s is not allowed" %
+ (type(value), key))
+ lines = []
+ # Sort the dict, looks nicer
+ for key in sorted(cfg.iterkeys()):
+ lines.append("%s=\"%s\"" % (key, cfg[key]))
+ contents = "\n".join(lines) + "\n"
+ self._write(contents)
+
+ def update(self, new_dict, remove_empty):
+ """Update the file using new_dict
+ Keys not present in the dict, but present in the file will be kept in
+ the file.
+
+ Args:
+ new_dict: A dictionary containing the keys to be updated
+ remove_empty: Remove keys from file if their value in new_dict
+ is None.
+ If False then the keys will be added to the file
+ without any value. (Aka flags)
+ """
+ cfg = self.get_dict()
+ cfg.update(new_dict)
+ self.write(cfg)
+
+ def _parse_dict(self, source):
+ """Parse a simple shell-var-style lines into a dict:
+
+ >>> import StringIO
+ >>> txt = "# A comment\\n"
+ >>> txt += "A=ah\\n"
+ >>> txt += "B=beh\\n"
+ >>> txt += "C=\\"ceh\\"\\n"
+ >>> txt += "D=\\"more=less\\"\\n"
+ >>> p = ShellVarFile("/tmp/cfg_dummy")
+ >>> sorted(p._parse_dict(StringIO.StringIO(txt)).items())
+ [('A', 'ah'), ('B', 'beh'), ('C', 'ceh'), ('D', 'more=less')]
+ """
+ cfg = {}
+ for line in source:
+ line = line.strip()
+ if line.startswith("#"):
+ continue
+ try:
+ key, value = line.split("=", 1)
+ cfg[key] = value.strip("\"' \n")
+ except:
+ self.logger.info("Failed to parse line: '%s'" % line)
+ return cfg
+
+ def _write(self, contents):
+ # The following logic is mainly needed to allow an "offline" testing
+ config_fs = Config()
+ if config_fs.is_enabled():
+ with config_fs.open_file(self.filename, "w") as dst:
+ dst.write(contents)
+ else:
+ try:
+ atomic_write(self.filename, contents)
+ except Exception as e:
+ self.logger.warning("Atomic write failed: %s" % e)
+ with open(self.filename, "w") as dst:
+ dst.write(contents)
diff --git a/src/ovirt/node/utils/network.py b/src/ovirt/node/utils/network.py
index 3336f13..6513012 100644
--- a/src/ovirt/node/utils/network.py
+++ b/src/ovirt/node/utils/network.py
@@ -18,12 +18,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA. A copy of the GNU General Public License is
# also available at http://www.gnu.org/copyleft/gpl.html.
-from ovirt.node import base, utils, config
+from ovirt.node import base, utils
+from ovirt.node.config import defaults
+from ovirt.node.config.network import NicConfig
import glob
import gudev
import logging
import os.path
-import ovirt.node.config.network
import ovirt.node.utils.fs
import ovirt.node.utils.process as process
import re
@@ -82,222 +83,122 @@
return _query_udev_ifaces()
-def is_configured():
- """Determin if any NIC has been configured for/by Node
-
- Returns:
- True if any nic has been configured
+class UdevNICInfo(base.Base):
+ """Gather NIC infos form udev
"""
- return any(key["bootproto"] is not None for key in node_nics().values())
+ _client = gudev.Client(['net'])
+ _device = None
+
+ ifname = None
+
+ def __init__(self, iface):
+ super(UdevNICInfo, self).__init__()
+ self.ifname = iface
+ for d in self._client.query_by_subsystem("net"):
+ if d.get_property("INTERFACE") == iface:
+ self._device = d
+
+ if not self._device:
+ raise UnknownNicError("udev has no infos for %s" % iface)
+
+ @property
+ def name(self):
+ return self._device.get_property("INTERFACE")
+
+ @property
+ def vendor(self):
+ return self._device.get_property("ID_VENDOR_FROM_DATABASE")
+
+ @property
+ def devtype(self):
+ return self._device.get_property("DEVTYPE")
+
+ @property
+ def devpath(self):
+ return self._device.get_property("DEVPATH")
-def iface_information(iface, with_slow=True):
- """Retuns all system NICs (via udev)
-
- FIXME move into NIC
-
- Args:
- nics: List of NIC names
- Returns:
- A dict of (nic-name, nic-infos-dict)
+class SysfsNICInfo(base.Base):
+ """Gather NIC infos fom sysfs
"""
- info = None
+ ifname = None
- client = gudev.Client(['net'])
- for d in client.query_by_subsystem("net"):
-# assert d.has_property("ID_VENDOR_FROM_DATABASE"), \
-# "udev informations are incomplete (udevadm re-trigger?)"
+ def __init__(self, ifname):
+ super(SysfsNICInfo, self).__init__()
+ self.ifname = ifname
- uinfo = {"name": d.get_property("INTERFACE"),
- "vendor": d.get_property("ID_VENDOR_FROM_DATABASE")
- or "unkown",
- "devtype": d.get_property("DEVTYPE") or "unknown",
- "devpath": d.get_property("DEVPATH")
- }
+ @property
+ def driver(self):
+ driver_symlink = "/sys/class/net/%s/device/driver" % self.ifname
+ driver = "unknown"
+ if os.path.islink(driver_symlink):
+ try:
+ driver = os.path.basename(os.readlink(driver_symlink))
+ except Exception as e:
+ self.logger.warning(("Exception %s while reading driver " +
+ "of '%s' from '%s'") % (e, self.ifname,
+ driver_symlink))
+ return driver
- if uinfo["name"] == iface:
- info = uinfo
+ @property
+ def hwaddr(self):
+ #hwaddr = "unkown"
+ hwfilename = "/sys/class/net/%s/address" % self.ifname
+ hwaddr = ovirt.node.utils.fs.get_contents(hwfilename).strip()
+ return hwaddr
- assert info, "Unknown nic %s" % iface
+ @property
+ def systype(self):
+ systype = "ethernet"
- LOGGER.debug("Getting live information for '%s'" % iface)
+ if len(glob.glob("/proc/net/vlan/%s" % self.ifname)) > 0:
+ # Check if vlan
+ systype = "vlan"
- # Driver
- driver_symlink = "/sys/class/net/%s/device/driver" % iface
- driver = "unknown"
- if os.path.islink(driver_symlink):
- try:
- driver = os.path.basename(os.readlink(driver_symlink))
- except Exception as e:
- LOGGER.warning(("Exception %s while reading driver " +
- "of '%s' from '%s'") % (e, iface, driver_symlink))
- info["driver"] = driver
+ elif os.path.exists("/sys/class/net/%s/bridge" % self.ifname):
+ # Check if bridge
+ systype = "bridge"
- # Hwaddr
- hwaddr = "unkown"
- hwfilename = "/sys/class/net/%s/address" % iface
- hwaddr = ovirt.node.utils.fs.get_contents(hwfilename).strip()
- info["hwaddr"] = hwaddr
-
- # Check bridge
- if os.path.exists("/sys/class/net/%s/bridge" % iface):
- info["type"] = "bridge"
-
- # Check vlan
- if len(glob.glob("/proc/net/vlan/%s" % iface)) > 0:
- info["type"] = "vlan"
-
- if "type" not in info:
- devtype = info["devtype"]
- LOGGER.warning(("Type of %s still unknown, using devtype " +
- "%s") % (iface, devtype))
- info["type"] = devtype
-
- if with_slow:
- info.update(_slow_iface_information(iface))
-
- return info
-
-
-def _slow_iface_information(iface):
- info = {}
-
- nic = NIC(iface)
- # Current IP addresses
- info["addresses"] = nic.ip_addresses()
-
- # Current link state
- info["link_detected"] = nic.has_link()
-
- return info
-
-
-def relevant_ifaces(filter_bridges=True, filter_vlans=True):
- """Retuns relevant system NICs (via udev)
-
- Filters out
- - loop
- - bonds
- - vnets
- - bridges
- - sit
- - vlans
-
- >>> "lo" in relevant_ifaces()
- False
-
- Args:
- filter_bridges: If bridges shall be filtered out too
- filter_vlans: If vlans shall be filtered out too
- Returns:
- List of strings, the NIC names
- """
- valid_name = lambda n: not (n == "lo" or
- n.startswith("bond") or
- n.startswith("sit") or
- n.startswith("vnet") or
- n.startswith("tun") or
- n.startswith("wlan") or
- n.startswith("virbr") or
- (filter_vlans and ("." in n)))
-# FIXME!!!
-# valid_props = lambda i, p: (filter_bridges and (p["type"] != "bridge"))
-
- relevant_ifaces = [iface for iface in all_ifaces() if valid_name(iface)]
-# relevant_ifaces = {iface: iface_information(iface) for iface \
-# in relevant_ifaces \
-# if valid_props(iface, iface_information(iface, False))}
-
- irrelevant_names = set(all_ifaces()) - set(relevant_ifaces)
- LOGGER.debug("Irrelevant interfaces: %s" % irrelevant_names)
- LOGGER.debug("Relevant interfaces: %s" % relevant_ifaces)
-
- return relevant_ifaces
-
-
-def node_nics():
- """Returns Node's NIC model.
- This squashes nic, bridge and vlan informations.
-
- All valid NICs of the system are returned, live and cfg informations merged
- into one dict.
- A NIC is "Configured" if itself or a vlan child is a member of a bridge.
- If a NIC is configured, merge the info+cfg of the bridge into the slave.
- If the slave is a vlan NIC set the vlanidof the parent device according to
- this vlan NICs id.
-
- >>> node_nics() != None
- True
- """
- all_ifaces = relevant_ifaces(filter_bridges=False, filter_vlans=False)
- all_infos = dict((i, iface_information(i)) for i in all_ifaces)
- all_cfgs = dict((i, ovirt.node.config.network.iface(i)) for i
- in all_ifaces)
-
- bridges = [nic for nic, info in all_infos.items()
- if info["type"] == "bridge"]
- vlans = [nic for nic, info in all_infos.items()
- if info["type"] == "vlan"]
- nics = [nic for nic, info in all_infos.items()
- if info["name"] not in bridges + vlans]
-
- LOGGER.debug("Bridges: %s" % bridges)
- LOGGER.debug("VLANs: %s" % vlans)
- LOGGER.debug("NICs: %s" % nics)
-
- node_infos = {}
- slaves = []
- # Build dict with all NICs
- for iface in nics:
- LOGGER.debug("Adding physical NIC: %s" % iface)
- info = all_infos[iface]
- info.update(all_cfgs[iface])
- node_infos[iface] = info
- if info["bridge"]:
- LOGGER.debug("Physical NIC '%s' is slave of '%s'" %
- (iface, info["bridge"]))
- slaves.append(iface)
-
- # Merge informations of VLANs into parent
- for iface in vlans:
- info = all_infos[iface]
- info.update(all_cfgs[iface])
- parent = info["vlan_parent"]
- LOGGER.debug("Updating VLANID of '%s': %s" % (parent, info["vlanid"]))
- node_infos[parent]["vlanid"] = info["vlanid"]
- if info["bridge"]:
- LOGGER.debug("VLAN NIC '%s' is slave of '%s'" %
- (iface, info["bridge"]))
- slaves.append(iface)
-
- for slave in slaves:
- info = all_infos[slave]
- info.update(all_cfgs[slave])
- bridge = info["bridge"]
- LOGGER.debug("Found slave for bridge '%s': %s" % (bridge, slave))
- bridge_cfg = all_cfgs[bridge]
- dst = slave
- if info["is_vlan"]:
- dst = info["vlan_parent"]
- for k in ["bootproto", "ipaddr", "netmask", "gateway"]:
- LOGGER.debug("Merging cfg %s from bridge %s into device %s" %
- (k, bridge, slave))
- node_infos[dst][k] = bridge_cfg[k] if k in bridge_cfg else None
-
- LOGGER.debug("Node NICs: %s" % node_infos)
-
- return node_infos
+ return systype
class NIC(base.Base):
- def __init__(self, iface):
- self.iface = iface
+ """Offers an API tp common NIC related functions is also a model for any
+ logical NIC.
+ """
+ ifname = None
+ vendor = None
+ driver = None
+ hwaddr = None
+ typ = None
+ config = None
+
+ def __init__(self, ifname):
super(NIC, self).__init__()
+ self.ifname = ifname
+
+ self._udevinfo = UdevNICInfo(self.ifname)
+ self.vendor = self._udevinfo.vendor
+
+ self._sysfsinfo = SysfsNICInfo(self.ifname)
+ self.driver = self._sysfsinfo.driver
+ self.hwaddr = self._sysfsinfo.hwaddr
+
+ self.config = NicConfig(ifname)
+ self.typ = self._udevinfo.devtype or self._sysfsinfo.systype
def exists(self):
"""If this NIC currently exists in the system
+
+ >>> NIC("lo").exists()
+ True
"""
- return self.iface in all_ifaces()
+ return self.ifname in all_ifaces()
+
+ def is_configured(self):
+ """If there is a configuration for this NIC
+ """
+ return self.config.bootproto is not None
def has_link(self):
"""Determin if L1 is up on a given interface
@@ -306,18 +207,18 @@
True
Args:
- iface: The interface to be checked
+ ifname: The interface to be checked
Returns:
True if L1 (the-link-is-up) is detected (depends on driver support)
"""
if not self.exists():
raise UnknownNicError("Unknown network interface: '%s'" %
- self.iface)
+ self.ifname)
- if is_nm_managed(self.iface):
+ if is_nm_managed(self.ifname):
try:
- device = _nm_client.get_device_by_iface(self.iface)
+ device = _nm_client.get_device_by_iface(self.ifname)
if device:
return device.get_carrier()
except:
@@ -326,11 +227,11 @@
# Fallback
has_carrier = False
try:
- with open("/sys/class/net/%s/carrier" % self.iface) as c:
+ with open("/sys/class/net/%s/carrier" % self.ifname) as c:
content = c.read()
has_carrier = "1" in content
except:
- LOGGER.debug("Carrier down for %s" % self.iface)
+ LOGGER.debug("Carrier down for %s" % self.ifname)
return has_carrier
def ipv4_address(self):
@@ -340,20 +241,20 @@
return self.ip_addresses(["inet6"])["inet6"]
def ip_addresses(self, families=["inet", "inet6"]):
- """Get IP addresses for an iface
+ """Get IP addresses for an ifname
- FIXME NM client.get_device_by_iface(iface).get_ip?_config()
+ FIXME NM _client.get_device_by_iface(ifname).get_ip?_config()
"""
if not self.exists():
raise UnknownNicError("Unknown network interface: '%s'" %
- self.iface)
+ self.ifname)
addresses = dict((f, (None, None)) for f in families)
if False:
- # FIXME to hackish to convert addr - is_nm_managed(iface):
- device = _nm_client.get_device_by_iface(self.iface)
- LOGGER.debug("Got '%s' for '%s'" % (device, self.iface))
+ # FIXME to hackish to convert addr - is_nm_managed(ifname):
+ device = _nm_client.get_device_by_iface(self.ifname)
+ LOGGER.debug("Got '%s' for '%s'" % (device, self.ifname))
if device:
for family, cfgfunc, sf in [("inet", device.get_ip4_config,
socket.AF_INET),
@@ -362,7 +263,7 @@
cfg = cfgfunc()
if not cfg:
LOGGER.debug("No %s configuration for %s" %
- (family, self.iface))
+ (family, self.ifname))
break
addrs = cfg.get_addresses()
addr = addrs[0].get_address() if len(addrs) > 0 else None
@@ -371,7 +272,7 @@
return addresses
# Fallback
- cmd = "ip -o addr show {iface}".format(iface=self.iface)
+ cmd = "ip -o addr show {ifname}".format(ifname=self.ifname)
for line in process.pipe(cmd).split("\n"):
token = re.split("\s+", line)
if re.search("\sinet[6]?\s", line):
@@ -383,32 +284,233 @@
return addresses
- def vlanid(self):
- vlanids = []
- vcfg = "/proc/net/vlan/config"
- pat = re.compile("([0-9]+)\s*\|\s*%s$" % self.iface)
- if os.path.exists(vcfg):
- try:
- with open(vcfg) as f:
- for line in f:
- r = pat.search(line)
- if r:
- vlanids.append(r.groups[0])
- except IOError as e:
- self.logger.warning("Could not read vlan config: %s" %
- e.message)
- if len(vlanids) > 1:
- self.logger.info("Found more than one (expected) vlan: %s" %
- vlanids)
- return vlanids[0] if vlanids else None
+ def has_vlans(self):
+ """If this nic has associated vlan ids
+ """
+ return len(self.vlanids()) > 0
+
+ def is_vlan(self):
+ """if this nic is a vlan nic
+ """
+ vlans = Vlans()
+ return vlans.is_vlan_device(self.ifname)
+
+ def vlanids(self):
+ """Return all vlans of this nic
+ """
+ vlans = Vlans()
+ return vlans.vlans_for_nic(self.ifname)
def identify(self):
"""Flash the lights of this NIC to identify it
"""
- utils.process.call("ethtool --identify %s 10" % self.iface)
+ utils.process.call("ethtool --identify %s 10" % self.ifname)
def __str__(self):
- return "<NIC iface='%s' at %s" % (self.iface, hex(id(self)))
+ return self.build_str(["ifname"])
+
+ def __repr__(self):
+ return self.__str__()
+
+
+class BridgedNIC(NIC):
+ """A class to handle the legacy/default bridge-setup used by Node
+ """
+ bridge_nic = None
+ slave_nic = None
+
+ def __init__(self, snic, bnic):
+ super(BridgedNIC, self).__init__(snic.ifname)
+ self.slave_nic = snic
+ self.bridge_nic = bnic
+ self.config = self.bridge_nic.config
+
+ def exists(self):
+ return self.bridge_nic.exists()
+
+ def is_configured(self):
+ return self.bridge_nic.is_configured()
+
+ def has_link(self):
+ return self.slave_nic.has_link()
+
+ def ipv4_address(self):
+ return self.bridge_nic.ipv4_address()
+
+ def ipv6_address(self):
+ return self.bridge_nic.ipv6_address()
+
+ def ip_addresses(self, families=["inet", "inet6"]):
+ return self.bridge_nic.ip_addresses(families=families)
+
+ def is_vlan(self):
+ return self.slave_nic.is_vlan()
+
+ def has_vlans(self):
+ return self.slave_nic.has_vlans()
+
+ def vlanids(self):
+ return self.slave_nic.vlanids()
+
+ def identify(self):
+ self.slave_nic.identify(self)
+
+ def __str__(self):
+ pairs = {"bridge": self.bridge_nic.ifname,
+ "slave": self.slave_nic.ifname}
+ return self.build_str(["ifname"], additional_pairs=pairs)
+
+
+class TaggedNIC(NIC):
+ """A class to provide easy access to tagged NICs
+ """
+ vlan_nic = None
+ parent_nic = None
+
+ def __init__(self, vnic):
+ parent_ifname, _ = TaggedNIC._parent_and_vlanid_from_name(vnic.ifname)
+ super(TaggedNIC, self).__init__(parent_ifname)
+ self.vlan_nic = vnic
+ self.parent_nic = NIC(parent_ifname)
+
+ @staticmethod
+ def _parent_and_vlanid_from_name(ifname):
+ """Parse parent and vlanid from a ifname
+
+ >>> TaggedNIC._parent_and_vlanid_from_name("ens1.0")
+ ('ens1', '0')
+
+ Args:
+ ifname
+ Returns:
+ A tuple (parent ifname, vlanid)
+ """
+ parts = ifname.split(".")
+ return (".".join(parts[:-1]), parts[-1:][0])
+
+ def exists(self):
+ return self.vlan_nic.exists()
+
+ def is_configured(self):
+ return self.vlan_nic.is_configured()
+
+ def has_link(self):
+ return self.parent_nic.has_link()
+
+ def ipv4_address(self):
+ return self.vlan_nic.ipv4_address()
+
+ def ipv6_address(self):
+ return self.vlan_nic.ipv6_address()
+
+ def ip_addresses(self, families=["inet", "inet6"]):
+ return self.vlan_nic.ip_addresses(families=families)
+
+ def is_vlan(self):
+ return True
+
+ def has_vlans(self):
+ raise RuntimeError("Nested tagging is not allowed. Is it?")
+
+ def vlanids(self):
+ return self.parent_nic.vlanids()
+
+ def identify(self):
+ self.parent_nic.identify(self)
+
+ def __str__(self):
+ pairs = {"vlan": self.vlan_nic.ifname,
+ "parent": self.parent_nic.ifname}
+ return self.build_str(["ifname"], additional_pairs=pairs)
+
+
+class NodeNetwork(base.Base):
+ def all_ifnames(self):
+ return all_ifaces()
+
+ def relevant_ifnames(self, filter_bridges=True, filter_vlans=True):
+ all_ifaces = self.all_ifnames()
+ relevant_ifaces = self._filter_on_ifname(all_ifaces, filter_vlans)
+ irrelevant_names = set(all_ifaces) - set(relevant_ifaces)
+ LOGGER.debug("Irrelevant interfaces: %s" % irrelevant_names)
+ LOGGER.debug("Relevant interfaces: %s" % relevant_ifaces)
+
+ return relevant_ifaces
+
+ def _filter_on_ifname(self, ifnames, filter_vlans=True):
+ """Retuns relevant system NICs (via udev)
+
+ Filters out
+ - loop
+ - bonds
+ - vnets
+ - bridges
+ - sit
+ - vlans
+ - phy (wireless device)
+
+ >>> names = ["bond007", "sit1", "vnet11", "tun0", "wlan1", "virbr0"]
+ >>> names += ["ens1", "eth0", "phy0", "p1p7"]
+ >>> model = NodeNetwork()
+ >>> model._filter_on_ifname(names)
+ ['ens1', 'eth0', 'p1p7']
+
+ Args:
+ filter_bridges: If bridges shall be filtered out too
+ filter_vlans: If vlans shall be filtered out too
+ Returns:
+ List of strings, the NIC names
+ """
+ return [n for n in ifnames if not (n == "lo" or
+ n.startswith("bond") or
+ n.startswith("sit") or
+ n.startswith("vnet") or
+ n.startswith("tun") or
+ n.startswith("wlan") or
+ n.startswith("virbr") or
+ n.startswith("phy") or
+ (filter_vlans and ("." in n)))]
+
+ def build_nic_model(self, ifname):
+ nic = NIC(ifname)
+
+ if nic.config.bridge and nic.config.vlan:
+ nic = BridgedNIC(TaggedNIC(nic),
+ NIC(nic.config.bridge))
+
+ elif nic.config.bridge:
+ nic = BridgedNIC(NIC(nic.ifname),
+ NIC(nic.config.bridge))
+
+ elif nic.config.vlan:
+ nic = TaggedNIC(NIC(nic.ifname))
+
+ return nic
+
+ def nics(self):
+ """
+ >>> model = NodeNetwork()
+ """
+ nics = [NIC(ifname) for ifname
+ in self.relevant_ifnames(filter_vlans=False)]
+
+ bridges = [nic for nic in nics if nic.typ == "bridge"]
+ vlans = [nic for nic in nics if nic.typ == "vlan"]
+ nics = [nic for nic in nics if nic not in bridges + vlans]
+
+ self.logger.debug("Bridges: %s" % bridges)
+ self.logger.debug("VLANs: %s" % vlans)
+ self.logger.debug("NICs: %s" % nics)
+
+ candidates = {}
+
+ for nic in nics + vlans:
+ candidate = self.build_nic_model(nic.ifname)
+ if nic.ifname not in candidates or \
+ type(candidates[nic.ifname]) is NIC:
+ candidates[candidate.ifname] = candidate
+
+ return candidates
class Routes(base.Base):
@@ -458,7 +560,8 @@
def networking_status(iface=None):
status = "Not connected"
- iface = iface or config.network.node_bridge()
+ model = defaults.Network()
+ iface = iface or model.retrieve()["iface"]
addresses = []
if iface:
nic = NIC(iface)
@@ -524,3 +627,52 @@
print "Warning: could not find __res_init in libresolv.so.2"
r = -1
return r
+
+
+class Vlans(base.Base):
+ """A class to offer a convenience api to the vconfig file
+ """
+ cfg = "/proc/net/vlan/config"
+
+ def parse_cfg(self):
+ if not os.path.exists(self.cfg):
+ raise RuntimeError("vlans ain't enabled.")
+
+ vlans = {}
+ try:
+ with open(self.cfg) as f:
+ data_block = False
+ for line in f:
+ if line.startswith("Name-Type"):
+ data_block = True
+ continue
+ if not data_block:
+ continue
+ vdev, _, hdev = [field.strip()
+ for field in line.split("|")]
+ if not hdev in vlans:
+ vlans[hdev] = []
+ vlans[hdev].append(vdev)
+ except IOError as e:
+ self.logger.warning("Could not read vlan config: %s" %
+ e.message)
+
+ return vlans
+
+ def vlans_for_nic(self, ifname):
+ """return the vlans of the nic ifname
+ """
+ return self.parse_cfg().get(ifname, [])
+
+ def nic_for_vlan_device(self, vifname):
+ nic = None
+ for hdev, vifs in self.parse_cfg().items():
+ if vifname in vifs:
+ nic = hdev
+ break
+ return nic
+
+ def is_vlan_device(self, vifname):
+ """Check if ifname is a vlan device
+ """
+ return self.nic_for_vlan_device(vifname) is not None
--
To view, visit http://gerrit.ovirt.org/15411
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I8053ea21af5029190bc16b89ee109d2d7937a05f
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-node
Gerrit-Branch: master
Gerrit-Owner: Fabian Deutsch <fabiand at fedoraproject.org>
More information about the node-patches
mailing list