From: Radek Pazdera rpazdera@redhat.com
This patchset integrates LNST with libvirt and add support for dynamically adding arbitrary netdevices to the test machines in case they are libvirt virtual domains.
To take advantage of this, you need to have libvirt (including virsh) installed on the controller machine.
To indicate that your machine is virtualized on controller, specify 'libvirt_domain=<your_domain_name_here>' attribute to <info> tag in netmachine config.
After that you can mark your dynamic devices by enclosing them in <libvirt_create> tag.
Some other changes were added along with this patchset. Netdevices are now groupped in <netdevices> tag (it's necessary to fix this in your older recipes to use them from now on).
Each netdevice now has a required attribute called 'network'. This can be later used to determine the topology within lnst. You can chose any network name you want as long as its unique within your recipe. The semantics of this attribute is to indicate that all the devices tagged with the same network name are available on a single link segment.
Hope this patchset doesn't break anything else :). In case it does, please let me know.
v2: create tag changed to libvirt_create target_bridge changed to libvirt_bridge dev["dynamic"] changed to dev["create"] fixed copy-paste string error in NetUtils added forgotten exception raise to NetTestController
Radek
Radek Pazdera (11): NetTest: Adding 'network' attribute to netdevice Common: New module NetUtils Utils: Moved get_corespond_local_ip() to NetUtils NetConfigDevNames: Separating scan_netdevs method NetTestController: Adding new exception NetTestSlave: Adding RPC method for dev querying NetTestParse: Adding support for libvirt-integration NetConfig: Adding device rescan to add methods NetUtils: Adding AddressPool class Common: Adding VirtUtils module NetTestController: Implementing libvirt-integration
Common/NetUtils.py | 108 ++++++++++++++++++ Common/Utils.py | 20 +--- Common/VirtUtils.py | 242 ++++++++++++++++++++++++++++++++++++++++ NetConfig/NetConfig.py | 4 + NetConfig/NetConfigDevNames.py | 27 +---- NetTest/NetTestController.py | 103 +++++++++++++++++- NetTest/NetTestParse.py | 39 ++++++- NetTest/NetTestSlave.py | 11 ++ 8 files changed, 506 insertions(+), 48 deletions(-) create mode 100644 Common/NetUtils.py create mode 100644 Common/VirtUtils.py
From: Radek Pazdera rpazdera@redhat.com
This commit introduces networks into LNST netdevices. Up to this point we were unaware of how the devices are connected to each other. The network attribute adds this information directly into the recipe.
So if you later receive a recipe, you can configure your underlying physical network accordingly. This will be also useful with the upcomming virtual networks and interfaces, because LNST will be able to set up the virtual network automatically.
See the following example:
Machine 1: <netdevice hwaddr="52:54:00:11:D5:A8" network="channel-1" phys_id="1" type="eth"/> <netdevice hwaddr="52:54:00:2E:CD:A8" network="channel-2" phys_id="1" type="eth"/>
Machine 2: <netdevice hwaddr="52:54:00:2E:D5:A8" network="channel-1" phys_id="1" type="eth"/> <netdevice hwaddr="52:54:00:2E:D5:A8" network="channel-2" phys_id="1" type="eth"/>
Machine 1 is connected to machine 2 with two networks, channel-1 and channel-2.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- NetTest/NetTestController.py | 16 +++++++++++++++- NetTest/NetTestParse.py | 1 + 2 files changed, 16 insertions(+), 1 deletions(-)
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py index 4e34c90..8e23152 100644 --- a/NetTest/NetTestController.py +++ b/NetTest/NetTestController.py @@ -39,11 +39,14 @@ class NetTestController: self._recipe = {} definitions = {"recipe": self._recipe}
+ self._recipe["networks"] = {} + ntparse = NetTestParse(recipe_path) ntparse.set_recipe(self._recipe) ntparse.set_definitions(definitions)
- ntparse.register_event_handler("netdevice_ready", ignore_event) + ntparse.register_event_handler("netdevice_ready", + self._prepare_device) ntparse.register_event_handler("machine_info_ready", self._prepare_slave) ntparse.register_event_handler("interface_config_ready", @@ -75,6 +78,17 @@ class NetTestController: session.command, status) raise Exception("Session Die.")
+ def _prepare_device(self, machine_id, dev_id): + info = self._get_machineinfo(machine_id) + dev = self._recipe["machines"][machine_id]["netdevices"][dev_id] + + dev_net = dev["network"] + networks = self._recipe["networks"] + if not dev_net in networks: + networks[dev_net] = {"members": []} + + networks[dev_net]["members"].append((machine_id, dev_id)) + def _prepare_interface(self, machine_id, netdev_config_id): rpc = self._get_machinerpc(machine_id) info = self._get_machineinfo(machine_id) diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py index e52f0f3..32d75e1 100644 --- a/NetTest/NetTestParse.py +++ b/NetTest/NetTestParse.py @@ -145,6 +145,7 @@ class NetMachineConfigParse(RecipeParser):
dev = machine["netdevices"][phys_id] = {} dev["type"] = self._get_attribute(node, "type") + dev["network"] = self._get_attribute(node, "network") dev["hwaddr"] = normalize_hwaddr(self._get_attribute(node, "hwaddr"))
if self._has_attribute(node, "name"):
From: Radek Pazdera rpazdera@redhat.com
Created a new module for networking related code and utilities in Common package.
The normalize_hwaddr() function was moved there.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- Common/NetUtils.py | 16 ++++++++++++++++ NetConfig/NetConfigDevNames.py | 4 +--- NetTest/NetTestParse.py | 3 +-- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 Common/NetUtils.py
diff --git a/Common/NetUtils.py b/Common/NetUtils.py new file mode 100644 index 0000000..059ce6e --- /dev/null +++ b/Common/NetUtils.py @@ -0,0 +1,16 @@ +""" +Networking related utilities and common code + +Copyright 2012 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +rpazdera@redhat.com (Radek Pazdera) +""" + +import logging + +def normalize_hwaddr(hwaddr): + return hwaddr.upper().rstrip("\n") diff --git a/NetConfig/NetConfigDevNames.py b/NetConfig/NetConfigDevNames.py index 1220480..7c7f862 100644 --- a/NetConfig/NetConfigDevNames.py +++ b/NetConfig/NetConfigDevNames.py @@ -13,9 +13,7 @@ jpirko@redhat.com (Jiri Pirko) import logging import os from NetConfigCommon import get_option - -def normalize_hwaddr(hwaddr): - return hwaddr.upper().rstrip("\n") +from Common.NetUtils import normalize_hwaddr
class NetConfigDevNames: def __init__(self): diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py index 32d75e1..746342c 100644 --- a/NetTest/NetTestParse.py +++ b/NetTest/NetTestParse.py @@ -16,8 +16,7 @@ import re from Common.XmlProcessing import RecipeParser from Common.XmlProcessing import XmlDomTreeInit from Common.XmlProcessing import XmlProcessingError -from NetConfig.NetConfigDevNames import normalize_hwaddr - +from Common.NetUtils import normalize_hwaddr
class NetTestParse(RecipeParser): def __init__(self, recipe_filepath):
From: Radek Pazdera rpazdera@redhat.com
This commit moves get_corespond_local_ip function from the general utility module to the new network specific one.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- Common/NetUtils.py | 20 ++++++++++++++++++++ Common/Utils.py | 20 ++------------------ NetTest/NetTestController.py | 2 +- 3 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/Common/NetUtils.py b/Common/NetUtils.py index 059ce6e..7e642b6 100644 --- a/Common/NetUtils.py +++ b/Common/NetUtils.py @@ -11,6 +11,26 @@ rpazdera@redhat.com (Radek Pazdera) """
import logging +import re +import socket +import subprocess
def normalize_hwaddr(hwaddr): return hwaddr.upper().rstrip("\n") + +def get_corespond_local_ip(query_ip): + """ + Get ip address in local system which can communicate with query_ip. + + @param query_ip: IP of client which want communicate with autotest machine. + @return: IP address which can communicate with query_ip + """ + query_ip = socket.gethostbyname(query_ip) + ip = subprocess.Popen("ip route get %s" % (query_ip), + shell=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + ip = ip.communicate()[0] + ip = re.search(r"src ([0-9.]*)",ip) + if ip is None: + return ip + return ip.group(1) diff --git a/Common/Utils.py b/Common/Utils.py index 8f39729..f6ba201 100644 --- a/Common/Utils.py +++ b/Common/Utils.py @@ -9,7 +9,8 @@ published by the Free Software Foundation; see COPYING for details. __autor__ = """ jzupka@redhat.com (Jiri Zupka) """ -import logging, time, subprocess, re, socket +import logging +import time
def die_when_parent_die(): try: @@ -51,20 +52,3 @@ def wait_for(func, timeout, first=0.0, step=1.0, text=None):
logging.debug("Timeout elapsed") return None - - -def get_corespond_local_ip(query_ip): - """ - Get ip address in local system which can communicate with quert_ip. - - @param query_ip: IP of client which want communicate with autotest machine. - @return: IP address which can communicate with query_ip - """ - query_ip = socket.gethostbyname(query_ip) - ip = subprocess.Popen("ip route get %s" % (query_ip), - shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - ip = ip.communicate()[0] - ip = re.search(r"src ([0-9.]*)",ip) - if ip is None: - return ip - return ip.group(1) diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py index 8e23152..a997e37 100644 --- a/NetTest/NetTestController.py +++ b/NetTest/NetTestController.py @@ -20,7 +20,7 @@ from pprint import pprint, pformat from Common.XmlRpc import ServerProxy from NetTest.NetTestParse import NetTestParse from Common.SlaveUtils import prepare_client_session -from Common.Utils import get_corespond_local_ip +from Common.NetUtils import get_corespond_local_ip from NetTest.NetTestSlave import DefaultRPCPort from NetTest.NetTestCommand import NetTestCommand, str_command from Common.LoggingServer import LoggingServer
From: Radek Pazdera rpazdera@redhat.com
I separated scan_netdevs method from the class, changed it to regular function and moved it into the Common.NetUtils module, because it will be useful elsewhere in the code, but not in the context of the class and outside of the NetConfig module.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- Common/NetUtils.py | 19 +++++++++++++++++++ NetConfig/NetConfigDevNames.py | 23 +++-------------------- 2 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/Common/NetUtils.py b/Common/NetUtils.py index 7e642b6..42ee6f0 100644 --- a/Common/NetUtils.py +++ b/Common/NetUtils.py @@ -11,6 +11,7 @@ rpazdera@redhat.com (Radek Pazdera) """
import logging +import os import re import socket import subprocess @@ -18,6 +19,24 @@ import subprocess def normalize_hwaddr(hwaddr): return hwaddr.upper().rstrip("\n")
+def scan_netdevs(): + sys_dir = "/sys/class/net" + scan = [] + for root, dirs, files in os.walk(sys_dir): + if "lo" in dirs: + dirs.remove("lo") + for d in dirs: + dev_path = os.path.join(sys_dir, d) + addr_path = os.path.join(dev_path, "address") + if not os.path.isfile(addr_path): + continue + handle = open(addr_path, "rb") + addr = handle.read() + handle.close() + addr = normalize_hwaddr(addr) + scan.append({"name": d, "hwaddr": addr}) + return scan + def get_corespond_local_ip(query_ip): """ Get ip address in local system which can communicate with query_ip. diff --git a/NetConfig/NetConfigDevNames.py b/NetConfig/NetConfigDevNames.py index 7c7f862..0f246d9 100644 --- a/NetConfig/NetConfigDevNames.py +++ b/NetConfig/NetConfigDevNames.py @@ -14,31 +14,14 @@ import logging import os from NetConfigCommon import get_option from Common.NetUtils import normalize_hwaddr +from Common.NetUtils import scan_netdevs
class NetConfigDevNames: def __init__(self): - self._scan = self._scan_netdevs() - - def _scan_netdevs(self): - sys_dir = "/sys/class/net" - scan = [] - for root, dirs, files in os.walk(sys_dir): - if "lo" in dirs: - dirs.remove("lo") - for d in dirs: - dev_path = os.path.join(sys_dir, d) - addr_path = os.path.join(dev_path, "address") - if not os.path.isfile(addr_path): - continue - handle = open(addr_path, "rb") - addr = handle.read() - handle.close() - addr = normalize_hwaddr(addr) - scan.append({"name": d, "hwaddr": addr}) - return scan + self._scan = scan_netdevs()
def rescan_netdevs(self): - self._scan = self._scan_netdevs() + self._scan = scan_netdevs()
def assign_name_by_scan(self, dev_id, netdev): if (not "hwaddr" in netdev or
From: Radek Pazdera rpazdera@redhat.com
This commit adds new exception to NetTestController that will be used to indicate errors in recipe execution.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- NetTest/NetTestController.py | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-)
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py index a997e37..1dcfc69 100644 --- a/NetTest/NetTestController.py +++ b/NetTest/NetTestController.py @@ -25,6 +25,9 @@ from NetTest.NetTestSlave import DefaultRPCPort from NetTest.NetTestCommand import NetTestCommand, str_command from Common.LoggingServer import LoggingServer
+class NetTestError(Exception): + pass + def ignore_event(**kwarg): pass
From: Radek Pazdera rpazdera@redhat.com
This commit adds simple RPC method to the slave that allows the controler to issue a device queries and obtain information about available devices by hwaddr.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- NetTest/NetTestSlave.py | 11 +++++++++++ 1 files changed, 11 insertions(+), 0 deletions(-)
diff --git a/NetTest/NetTestSlave.py b/NetTest/NetTestSlave.py index 12440f7..a7722e4 100644 --- a/NetTest/NetTestSlave.py +++ b/NetTest/NetTestSlave.py @@ -22,6 +22,7 @@ from NetConfig.NetConfig import NetConfig from NetConfig.NetConfigDevice import NetConfigDeviceAllCleanup from NetTest.NetTestCommand import NetTestCommand, CommandException from Common.Utils import die_when_parent_die +from Common.NetUtils import scan_netdevs
DefaultRPCPort = 9999
@@ -46,6 +47,16 @@ class NetTestSlaveXMLRPC: Logs.append_network_hadler(logger_address, port) return True
+ def get_devices_by_hwaddr(self, hwaddr): + name_scan = scan_netdevs() + netdevs = [] + + for entry in name_scan: + if entry["hwaddr"] == hwaddr: + netdevs.append(entry) + + return netdevs + def get_interface_info(self, if_id): if_config = self._netconfig.get_interface_config(if_id) info = {}
From: Radek Pazdera rpazdera@redhat.com
This commit adds necessary changes to the parser in LNST in order to support the libvirt device creation.
Netdevice tags are now grouped under a <netdevices> parent tag within <netmachineconfig>.
On top of that a special mode for defining netdevices (called create) was added. Devices defined in this mode are not present in the machine and will be created on test execution.
This mode is available only on virtual test machines created using libvirt. Here's an exmaple config of such a machine:
<machine id="1"> <netmachineconfig> <info hostname="192.168.122.11" libvirt_domain="RHEL6" rootpass="redhat"/> <netdevices> <libvirt_create> <netdevice network="tnet" phys_id="1" type="eth"/> <netdevice network="tnet" phys_id="2" type="eth"/> </libvirt_create> </netdevices> </netmachineconfig> <netconfig> <interface id="1" phys_id="1" type="eth"> <addresses> <address value="192.168.100.226/24"/> </addresses> </interface> <interface id="2" phys_id="2" type="eth"> <addresses> <address value="192.168.100.240/24"/> </addresses> </interface> </netconfig> </machine>
The dynamic devices are enclosed within the <libvirt_create> tag. Their hwaddr doesn't need to be specified (it will be generated if missing).
Additionally, 'libvirt_bridge' attribute can be added to these devices to override the default LNST internal bridge, that these devices are connected to.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- NetTest/NetTestParse.py | 35 ++++++++++++++++++++++++++++++++--- 1 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py index 746342c..fbe5adc 100644 --- a/NetTest/NetTestParse.py +++ b/NetTest/NetTestParse.py @@ -118,7 +118,7 @@ class NetMachineConfigParse(RecipeParser):
def parse(self, node): scheme = {"info": self._info, - "netdevice": self._netdevice} + "netdevices": self._netdevices} self._process_child_nodes(node, scheme)
def _info(self, node, params): @@ -127,6 +127,10 @@ class NetMachineConfigParse(RecipeParser):
info["hostname"] = self._get_attribute(node, "hostname")
+ if self._has_attribute(node, "libvirt_domain"): + info["libvirt_domain"] = self._get_attribute(node, + "libvirt_domain") + if self._has_attribute(node, "rootpass"): info["rootpass"] = self._get_attribute(node, "rootpass")
@@ -138,18 +142,43 @@ class NetMachineConfigParse(RecipeParser): self._trigger_event("machine_info_ready", {"machine_id": self._machine_id})
+ def _netdevices(self, node, params): + scheme = {"netdevice": self._netdevice, + "libvirt_create": self._libvirt_create} + + new_params = {"create": None} + self._process_child_nodes(node, scheme, new_params) + + def _libvirt_create(self, node, params): + scheme = {"netdevice": self._netdevice} + + new_params = {"create": "libvirt"} + self._process_child_nodes(node, scheme, new_params) + def _netdevice(self, node, params): machine = self._machine phys_id = self._get_attribute(node, "phys_id", int)
dev = machine["netdevices"][phys_id] = {} + dev["create"] = params["create"] dev["type"] = self._get_attribute(node, "type") dev["network"] = self._get_attribute(node, "network") - dev["hwaddr"] = normalize_hwaddr(self._get_attribute(node, "hwaddr"))
- if self._has_attribute(node, "name"): + # hwaddr attribute is optional for dynamic devices, + # but it is required by non-dynamic devices + if dev["create"] == None or self._has_attribute(node, "hwaddr"): + hwaddr = self._get_attribute(node, "hwaddr") + dev["hwaddr"] = normalize_hwaddr(hwaddr) + + # name attribute is only valid when the device is not dynamic + if dev["create"] == None and self._has_attribute(node, "name"): dev["name"] = self._get_attribute(node, "name")
+ # bridge attribute is valid only when the device is dynamic + if dev["create"] == "libvirt" and \ + self._has_attribute(node, "libvirt_bridge"): + dev["libvirt_bridge"] = self._get_attribute(node, "libvirt_bridge") + self._trigger_event("netdevice_ready", {"machine_id": self._machine_id, "dev_id": phys_id})
From: Radek Pazdera rpazdera@redhat.com
Now, when devices can be added dynamically to the slaves, NetConfig is obligated to rescan the existing devices each time a new device is configured.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- NetConfig/NetConfig.py | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/NetConfig/NetConfig.py b/NetConfig/NetConfig.py index 41a99d8..75d5129 100644 --- a/NetConfig/NetConfig.py +++ b/NetConfig/NetConfig.py @@ -65,6 +65,8 @@ class NetConfig: NetConfigDeviceType(dev_type).type_init()
self._config[if_id] = config + + self._devnames.rescan_netdevs() self._devnames.assign_name(if_id, self._config)
def remove_interface_config(self, if_id): @@ -125,6 +127,8 @@ class NetConfig: if "slaves" in params: netdev["slaves"] = params["slaves"] self._config[dev_id] = netdev + + self._devnames.rescan_netdevs() self._devnames.assign_name(dev_id, self._config) return dev_id
From: Radek Pazdera rpazdera@redhat.com
This commit introduces AddressPool class (and its two children - MacPool and IpPool). This class can be used to generate (and keep a track of) different types of network addresses within a defined range.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- Common/NetUtils.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 53 insertions(+), 0 deletions(-)
diff --git a/Common/NetUtils.py b/Common/NetUtils.py index 42ee6f0..8594a23 100644 --- a/Common/NetUtils.py +++ b/Common/NetUtils.py @@ -53,3 +53,56 @@ def get_corespond_local_ip(query_ip): if ip is None: return ip return ip.group(1) + +class AddressPool: + def __init__(self, start, end): + self._next = self._addr_to_byte_string(start) + self._final = self._addr_to_byte_string(end) + + def _inc_byte_string(self, byte_string): + if len(byte_string): + byte_string[-1] += 1 + if byte_string[-1] > 255: + del byte_string[-1] + self._inc_byte_string(byte_string) + byte_string.append(0) + + def _addr_to_byte_string(self, addr): + pass + + def _byte_string_to_addr(self, byte_string): + pass + + def get_addr(self): + if self._next > self._final: + msg = "Pool exhausted, no free addresses available" + raise Exception(msg) + + addr_str = self._byte_string_to_addr(self._next) + self._inc_byte_string(self._next) + + return addr_str + +class MacPool(AddressPool): + def _addr_to_byte_string(self, addr): + bs = [int(byte, 16) for byte in addr.split(":")] + + if len(bs) != 6: + raise Exception("Malformed MAC address") + + return bs + + def _byte_string_to_addr(self, byte_string): + return ':'.join(map(lambda x: "%02x" % x, byte_string)) + +class IpPool(AddressPool): + def _addr_to_byte_string(self, addr): + bs = [int(byte) for byte in addr.split(".")] + + if len(bs) != 4: + raise Exception("Malformed IP address") + + return bs + + def _byte_string_to_addr(self, byte_string): + return '.'.join(map(str, byte_string))
From: Radek Pazdera rpazdera@redhat.com
This commit adds VirtUtils module to Common.
VirtUtils is a set of classes that can be used to manage virtual domains and networks.
VirtDomainCtl can control libvirt domains, VirtNetCtl can control libvirt networks, BridgeCtl can control network bridges.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- Common/VirtUtils.py | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 242 insertions(+), 0 deletions(-) create mode 100644 Common/VirtUtils.py
diff --git a/Common/VirtUtils.py b/Common/VirtUtils.py new file mode 100644 index 0000000..28a3d7e --- /dev/null +++ b/Common/VirtUtils.py @@ -0,0 +1,242 @@ +""" +Utilities for manipulating virtualization host, its guests and +connections between them + +Copyright 2012 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +rpazdera@redhat.com (Radek Pazdera) +""" + +import os +import re +import copy +import time +import logging +from tempfile import NamedTemporaryFile +from xml.dom import minidom +from ExecCmd import exec_cmd, ExecCmdFail +from Common.NetUtils import normalize_hwaddr, scan_netdevs + +class VirtUtilsError(Exception): + pass + +def _ip(cmd): + try: + exec_cmd("ip %s" % cmd) + except ExecCmdFail as err: + raise VirtUtilsError("ip command error: %s" % err) + +def _brctl(cmd): + try: + exec_cmd("brctl %s" % cmd) + except ExecCmdFail as err: + raise VirtUtilsError("brctl error: %s" % err) + +def _virsh(cmd): + try: + exec_cmd("virsh %s" % cmd) + except ExecCmdFail as err: + raise VirtUtilsError("virsh error: %s" % err) + + +class VirtDomainCtl: + _name = None + + def __init__(self, domain_name): + self._name = domain_name + + def start(self): + _virsh("start %s" % self._name) + + def stop(self): + _virsh("destroy %s" % self._name) + + def restart(self): + _virsh("reboot %s" % self._name) + + def attach_interface(self, hwaddr, net_name, net_type="bridge"): + _virsh("attach-interface %s %s %s --mac %s" % \ + (self._name, net_type, net_name, hwaddr)) + self.ifup(hwaddr) + + def detach_interface(self, hwaddr, net_type="bridge"): + _virsh("detach-interface %s %s %s" % (self._name, net_type, hwaddr)) + + def ifup(self, hwaddr): + _virsh("domif-setlink %s %s up" % (self._name, hwaddr)) + + def ifdown(self, hwaddr): + _virsh("domif-setlink %s %s down" % (self._name, hwaddr)) + +class NetCtl(object): + _name = None + + def __init__(self, name): + self._name = name + + def get_name(self): + return self._name + + def init(self): + pass + + def cleanup(self): + pass + +class VirtNetCtl(NetCtl): + _addr = None + _mask = None + + _dhcp_from = None + _dhcp_to = None + _static_mappings = [] + _lease_file = None + + def setup_dhcp_server(self, addr, mask, range_start, range_end, + static_map=None): + self._addr = addr + self._mask = mask + self._dhcp_from = range_start + self._dhcp_to = range_end + self._lease_file = "/var/lib/libvirt/dnsmasq/%s.leases" % self._name + if static_map: + self._static_mappings = static_map + + def get_dhcp_mapping(self, hwaddr, timeout=30): + wait = timeout + while True: + addr = self._get_map(hwaddr) + if addr or not wait: + break + + time.sleep(1) + wait -= 1 + + return addr + + def _get_map(self, hwaddr): + + try: + leases_file = open(self._lease_file, "r") + except IOError as err: + raise VirtUtilsError("Unable to resolve IP mapping (%s)" % err) + + addr = None + normalized_hwaddr = normalize_hwaddr(hwaddr) + for entry in leases_file: + match = re.match(r"\d+\s+([0-9a-f:]+)\s+([0-9.]+)", entry) + if match: + entry_hwaddr = normalize_hwaddr(match.group(1)) + entry_addr = match.group(2) + if entry_hwaddr == normalized_hwaddr: + addr = entry_addr + break + + leases_file.close() + return addr + + + def init(self): + tmp_file = NamedTemporaryFile(delete=False) + doc = self._get_net_xml_dom() + + doc.writexml(tmp_file) + tmp_file.close() + + _virsh("net-create %s" % tmp_file.name) + os.unlink(tmp_file.name) + + def cleanup(self): + _virsh("net-destroy %s" % self._name) + exec_cmd("rm -f %s" % self._lease_file) + + def _get_net_xml_dom(self): + doc = minidom.Document() + + net = doc.createElement("network") + doc.appendChild(net) + + name = doc.createElement("name") + name_text = doc.createTextNode(self._name) + name.appendChild(name_text) + net.appendChild(name) + + if self._addr: + ip = doc.createElement("ip") + ip.setAttribute("address", self._addr) + ip.setAttribute("netmask", self._mask) + net.appendChild(ip) + + dhcp = doc.createElement("dhcp") + ip.appendChild(dhcp) + + dhcp_range = doc.createElement("range") + dhcp_range.setAttribute("start", self._dhcp_from) + dhcp_range.setAttribute("end", self._dhcp_to) + dhcp.appendChild(dhcp_range) + + for mapping in self._static_mappings: + hwaddr, addr = mapping + host = doc.createElement("host") + host.setAttribute("mac", hwaddr) + host.setAttribute("ip", addr) + dhcp.appendChild(host) + + return doc + +class BridgeCtl(NetCtl): + _remove = False + + def __init__(self, name=None): + if not name: + name = self._generate_name() + + self._check_name(name) + self._name = name + + @staticmethod + def _check_name(name): + if len(name) > 16: + msg = "Bridge name '%s' longer than 16 characters" % name + raise VirtUtilsError(msg) + + @staticmethod + def _generate_name(): + devs = scan_netdevs() + + index = 0 + while True: + name = "lnstbr%d" % index + index += 1 + unique = True + for dev in devs: + if name == dev["name"]: + unique = False + break + + if unique: + return name + + def _exists(self): + devs = scan_netdevs() + for dev in devs: + if self._name == dev["name"]: + return True + + return False + + def init(self): + if not self._exists(): + _brctl("addbr %s" % self._name) + self._remove = True + + _ip("link set %s up" % self._name) + + def cleanup(self): + if self._remove: + _ip("link set %s down" % self._name) + _brctl("delbr %s" % self._name)
From: Radek Pazdera rpazdera@redhat.com
This commit adds the necessary changes to NetTestController that implements integration with libvirt.
Signed-off-by: Radek Pazdera rpazdera@redhat.com --- NetTest/NetTestController.py | 92 +++++++++++++++++++++++++++++++++++++++-- 1 files changed, 87 insertions(+), 5 deletions(-)
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py index 1dcfc69..296150d 100644 --- a/NetTest/NetTestController.py +++ b/NetTest/NetTestController.py @@ -20,10 +20,14 @@ from pprint import pprint, pformat from Common.XmlRpc import ServerProxy from NetTest.NetTestParse import NetTestParse from Common.SlaveUtils import prepare_client_session -from Common.NetUtils import get_corespond_local_ip +from Common.NetUtils import get_corespond_local_ip, MacPool from NetTest.NetTestSlave import DefaultRPCPort from NetTest.NetTestCommand import NetTestCommand, str_command from Common.LoggingServer import LoggingServer +from Common.VirtUtils import VirtNetCtl, VirtDomainCtl, BridgeCtl +from Common.Utils import wait_for + +MAC_POOL_RANGE = {"start": "52:54:01:00:00:01", "end": "52:54:01:FF:FF:FF"}
class NetTestError(Exception): pass @@ -43,6 +47,8 @@ class NetTestController: definitions = {"recipe": self._recipe}
self._recipe["networks"] = {} + self._mac_pool = MacPool(MAC_POOL_RANGE["start"], + MAC_POOL_RANGE["end"])
ntparse = NetTestParse(recipe_path) ntparse.set_recipe(self._recipe) @@ -83,14 +89,71 @@ class NetTestController:
def _prepare_device(self, machine_id, dev_id): info = self._get_machineinfo(machine_id) + rpc = self._get_machinerpc(machine_id) dev = self._recipe["machines"][machine_id]["netdevices"][dev_id]
- dev_net = dev["network"] + dev_net_name = dev["network"] networks = self._recipe["networks"] - if not dev_net in networks: - networks[dev_net] = {"members": []} + if not dev_net_name in networks: + networks[dev_net_name] = {"members": []} + + dev_net = networks[dev_net_name] + dev_net["members"].append((machine_id, dev_id)) + + if dev["create"] == "libvirt": + if not "virt_domain_ctl" in info: + msg = "Cannot create device. " \ + "Machine '%d' is not virtual." % (machine_id) + raise NetTestError(msg) + + if not "hwaddr" in dev: + dev["hwaddr"] = self._mac_pool.get_addr() + + if "target_bridge" in dev: + brctl = BridgeCtl(dev["target_bridge"]) + else: + if "default_bridge" in dev_net: + brctl = dev_net["default_bridge"] + else: + brctl = BridgeCtl() + dev_net["default_bridge"] = brctl + + br_name = brctl.get_name() + brctl.init() + + logging.info("Creating netdevice %d (%s) on machine %d", + dev_id, dev["hwaddr"], machine_id) + + domain_ctl = info["virt_domain_ctl"] + domain_ctl.attach_interface(dev["hwaddr"], br_name)
- networks[dev_net]["members"].append((machine_id, dev_id)) + ready_check_func = lambda: self._device_ready(machine_id, dev_id) + ready = wait_for(ready_check_func, timeout=10) + + if not ready: + msg = "Netdevice initialization failed." \ + "Unable to create device %d (%s) on machine %d" \ + % (dev_id, dev["hwaddr"], machine_id) + raise NetTestError(msg) + + phys_devs = rpc.get_devices_by_hwaddr(dev["hwaddr"]) + if len(phys_devs) == 1: + pass + elif len(phys_devs) < 1: + msg = "Device %d not found on machine %d" \ + % (dev_id, machine_id) + raise NetTestError(msg) + elif len(phys_devs) > 1: + msg = "Multiple netdevices with same address %s on machine %d" \ + % (dev["hwaddr"], machine_id) + raise NetTestError(msg) + + def _device_ready(self, machine_id, dev_id): + rpc = self._get_machinerpc(machine_id) + dev = self._recipe["machines"][machine_id]["netdevices"][dev_id] + + devs = rpc.get_devices_by_hwaddr(dev["hwaddr"]) + return len(devs) > 0
def _prepare_interface(self, machine_id, netdev_config_id): rpc = self._get_machinerpc(machine_id) @@ -122,6 +185,10 @@ class NetTestController: logging.info("Preparing machine #%d", machine_id) info = self._get_machineinfo(machine_id)
+ if "libvirt_domain" in info: + domain_ctl = VirtDomainCtl(info["libvirt_domain"]) + info["virt_domain_ctl"] = domain_ctl + if self._remoteexec and not "session" in info: self._init_slave_session(machine_id)
@@ -184,6 +251,21 @@ class NetTestController: for if_id in reversed(info["configured_interfaces"]): rpc.deconfigure_interface(if_id)
+ # detach dynamically created devices + machine = self._recipe["machines"][machine_id] + for dev_id, dev in machine["netdevices"].iteritems(): + if dev["create"] == "libvirt": + logging.info("Removing netdevice %d (%s) from machine %d", + dev_id, dev["hwaddr"], machine_id) + domain_ctl = info["virt_domain_ctl"] + domain_ctl.detach_interface(dev["hwaddr"]) + + # remove dynamically created bridges + networks = self._recipe["networks"] + for net in networks.itervalues(): + if "default_bridge" in net: + net["default_bridge"].cleanup() + def _disconnect_slaves(self): for machine_id in self._recipe["machines"]: info = self._get_machineinfo(machine_id)
Mon, Aug 13, 2012 at 12:01:41PM CEST, rpazdera@redhat.com wrote:
From: Radek Pazdera rpazdera@redhat.com
This patchset integrates LNST with libvirt and add support for dynamically adding arbitrary netdevices to the test machines in case they are libvirt virtual domains.
To take advantage of this, you need to have libvirt (including virsh) installed on the controller machine.
To indicate that your machine is virtualized on controller, specify 'libvirt_domain=<your_domain_name_here>' attribute to <info> tag in netmachine config.
After that you can mark your dynamic devices by enclosing them in <libvirt_create> tag.
Some other changes were added along with this patchset. Netdevices are now groupped in <netdevices> tag (it's necessary to fix this in your older recipes to use them from now on).
Each netdevice now has a required attribute called 'network'. This can be later used to determine the topology within lnst. You can chose any network name you want as long as its unique within your recipe. The semantics of this attribute is to indicate that all the devices tagged with the same network name are available on a single link segment.
Hope this patchset doesn't break anything else :). In case it does, please let me know.
v2: create tag changed to libvirt_create target_bridge changed to libvirt_bridge dev["dynamic"] changed to dev["create"] fixed copy-paste string error in NetUtils added forgotten exception raise to NetTestController
Radek
Radek Pazdera (11): NetTest: Adding 'network' attribute to netdevice Common: New module NetUtils Utils: Moved get_corespond_local_ip() to NetUtils NetConfigDevNames: Separating scan_netdevs method NetTestController: Adding new exception NetTestSlave: Adding RPC method for dev querying NetTestParse: Adding support for libvirt-integration NetConfig: Adding device rescan to add methods NetUtils: Adding AddressPool class Common: Adding VirtUtils module NetTestController: Implementing libvirt-integration
Common/NetUtils.py | 108 ++++++++++++++++++ Common/Utils.py | 20 +--- Common/VirtUtils.py | 242 ++++++++++++++++++++++++++++++++++++++++ NetConfig/NetConfig.py | 4 + NetConfig/NetConfigDevNames.py | 27 +---- NetTest/NetTestController.py | 103 +++++++++++++++++- NetTest/NetTestParse.py | 39 ++++++- NetTest/NetTestSlave.py | 11 ++ 8 files changed, 506 insertions(+), 48 deletions(-) create mode 100644 Common/NetUtils.py create mode 100644 Common/VirtUtils.py
Applied. Thanks!
-- 1.7.7.6
LNST-developers mailing list LNST-developers@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/lnst-developers
lnst-developers@lists.fedorahosted.org