diff options
Diffstat (limited to 'yardstick')
-rw-r--r-- | yardstick/benchmark/contexts/standalone/__init__.py | 211 | ||||
-rw-r--r-- | yardstick/benchmark/contexts/standalone/model.py | 493 | ||||
-rw-r--r-- | yardstick/benchmark/contexts/standalone/ovs_dpdk.py | 383 | ||||
-rw-r--r-- | yardstick/benchmark/contexts/standalone/ovsdpdk.py | 369 | ||||
-rw-r--r-- | yardstick/benchmark/contexts/standalone/sriov.py | 624 | ||||
-rw-r--r-- | yardstick/network_services/utils.py | 57 |
6 files changed, 1160 insertions, 977 deletions
diff --git a/yardstick/benchmark/contexts/standalone/__init__.py b/yardstick/benchmark/contexts/standalone/__init__.py index f0ef1d560..e69de29bb 100644 --- a/yardstick/benchmark/contexts/standalone/__init__.py +++ b/yardstick/benchmark/contexts/standalone/__init__.py @@ -1,211 +0,0 @@ -# Copyright (c) 2016-2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""This module handle non managed standalone virtualization node.""" - -from __future__ import absolute_import -import logging -import os -import errno -import collections -import time - -from yardstick.benchmark.contexts.base import Context -from yardstick.common.constants import YARDSTICK_ROOT_PATH -from yardstick.common.utils import import_modules_from_package, itersubclasses -from yardstick.common.yaml_loader import yaml_load - -LOG = logging.getLogger(__name__) - - -class StandaloneContext(Context): - """ This class handles standalone nodes - VM running on Non-Managed NFVi - Configuration: vswitch, ovs, ovs-dpdk, sr-iov, linuxbridge - """ - - __context_type__ = "Standalone" - - def __init__(self): - self.name = None - self.file_path = None - self.nodes = [] - self.networks = {} - self.nfvi_node = [] - self.nfvi_obj = None - self.attrs = {} - super(StandaloneContext, self).__init__() - - def read_config_file(self): - """Read from config file""" - - with open(self.file_path) as stream: - LOG.info("Parsing pod file: %s", self.file_path) - cfg = yaml_load(stream) - return cfg - - def get_nfvi_obj(self): - print("{0}".format(self.nfvi_node[0]['role'])) - context_type = self.get_context_impl(self.nfvi_node[0]['role']) - nfvi_obj = context_type() - nfvi_obj.__init__() - nfvi_obj.parse_pod_and_get_data(self.file_path) - return nfvi_obj - - def init(self, attrs): - """initializes itself from the supplied arguments""" - - self.name = attrs["name"] - self.file_path = file_path = attrs.get("file", "pod.yaml") - - try: - cfg = self.read_config_file() - except IOError as io_error: - if io_error.errno != errno.ENOENT: - raise - self.file_path = os.path.join(YARDSTICK_ROOT_PATH, file_path) - cfg = self.read_config_file() - - self.vm_deploy = attrs.get("vm_deploy", True) - self.nodes.extend([node for node in cfg["nodes"] - if str(node["role"]) != "Sriov" and - str(node["role"]) != "Ovsdpdk"]) - for node in cfg["nodes"]: - if str(node["role"]) == "Sriov": - self.nfvi_node.extend([node for node in cfg["nodes"] - if str(node["role"]) == "Sriov"]) - if str(node["role"]) == "Ovsdpdk": - self.nfvi_node.extend([node for node in cfg["nodes"] - if str(node["role"]) == "Ovsdpdk"]) - LOG.info("{0}".format(node["role"])) - else: - LOG.debug("Node role is other than SRIOV and OVS") - self.nfvi_obj = self.get_nfvi_obj() - self.attrs = attrs - # add optional static network definition - self.networks.update(cfg.get("networks", {})) - self.nfvi_obj = self.get_nfvi_obj() - LOG.debug("Nodes: %r", self.nodes) - LOG.debug("NFVi Node: %r", self.nfvi_node) - LOG.debug("Networks: %r", self.networks) - - def deploy(self): - """don't need to deploy""" - - # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. - if not self.vm_deploy: - return - - # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. - self.nfvi_obj.ssh_remote_machine() - if self.nfvi_obj.first_run is True: - self.nfvi_obj.install_req_libs() - - nic_details = self.nfvi_obj.get_nic_details() - print("{0}".format(nic_details)) - - if self.nfvi_node[0]["role"] == "Sriov": - self.nfvi_obj.setup_sriov_context( - self.nfvi_obj.sriov[0]['phy_ports'], - nic_details, - self.nfvi_obj.sriov[0]['phy_driver']) - if self.nfvi_node[0]["role"] == "Ovsdpdk": - self.nfvi_obj.setup_ovs(self.nfvi_obj.ovs[0]["phy_ports"]) - self.nfvi_obj.start_ovs_serverswitch() - time.sleep(5) - self.nfvi_obj.setup_ovs_bridge() - self.nfvi_obj.add_oflows() - self.nfvi_obj.setup_ovs_context( - self.nfvi_obj.ovs[0]['phy_ports'], - nic_details, - self.nfvi_obj.ovs[0]['phy_driver']) - pass - - def undeploy(self): - """don't need to undeploy""" - - if not self.vm_deploy: - return - # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config. - # self.nfvi_obj = self.get_nfvi_obj() - self.nfvi_obj.ssh_remote_machine() - self.nfvi_obj.destroy_vm() - pass - - def _get_server(self, attr_name): - """lookup server info by name from context - - Keyword arguments: - attr_name -- A name for a server listed in nodes config file - """ - node_name, name = self.split_name(attr_name) - if name is None or self.name != name: - return None - - matching_nodes = (n for n in self.nodes if n["name"] == node_name) - try: - # A clone is created in order to avoid affecting the - # original one. - node = dict(next(matching_nodes)) - except StopIteration: - return None - - try: - duplicate = next(matching_nodes) - except StopIteration: - pass - else: - raise ValueError("Duplicate nodes!!! Nodes: %s %s", - (node, duplicate)) - - node["name"] = attr_name - return node - - def _get_network(self, attr_name): - if not isinstance(attr_name, collections.Mapping): - network = self.networks.get(attr_name) - - else: - # Don't generalize too much Just support vld_id - vld_id = attr_name.get('vld_id', {}) - # for standalone context networks are dicts - iter1 = (n for n in self.networks.values() if n.get('vld_id') == vld_id) - network = next(iter1, None) - - if network is None: - return None - - result = { - # name is required - "name": network["name"], - "vld_id": network.get("vld_id"), - "segmentation_id": network.get("segmentation_id"), - "network_type": network.get("network_type"), - "physical_network": network.get("physical_network"), - } - return result - - def get_context_impl(self, nfvi_type): - """ Find the implementing class from vnf_model["vnf"]["name"] field - - :param vnf_model: dictionary containing a parsed vnfd - :return: subclass of GenericVNF - """ - import_modules_from_package( - "yardstick.benchmark.contexts") - expected_name = nfvi_type - impl = [c for c in itersubclasses(StandaloneContext) - if c.__name__ == expected_name] - try: - return next(iter(impl)) - except StopIteration: - raise ValueError("No implementation for %s", expected_name) diff --git a/yardstick/benchmark/contexts/standalone/model.py b/yardstick/benchmark/contexts/standalone/model.py new file mode 100644 index 000000000..4491660e0 --- /dev/null +++ b/yardstick/benchmark/contexts/standalone/model.py @@ -0,0 +1,493 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import os +import re +import time +import glob +import uuid +import random +import logging +import itertools +import errno + +from netaddr import IPNetwork +import xml.etree.ElementTree as ET + +from yardstick import ssh +from yardstick.common.constants import YARDSTICK_ROOT_PATH +from yardstick.common.yaml_loader import yaml_load +from yardstick.network_services.utils import PciAddress +from yardstick.common.utils import write_file + +LOG = logging.getLogger(__name__) + +VM_TEMPLATE = """ +<domain type="kvm"> + <name>{vm_name}</name> + <uuid>{random_uuid}</uuid> + <memory unit="MB">{memory}</memory> + <currentMemory unit="MB">{memory}</currentMemory> + <memoryBacking> + <hugepages /> + </memoryBacking> + <vcpu placement="static">{vcpu}</vcpu> + <os> + <type arch="x86_64" machine="pc-i440fx-utopic">hvm</type> + <boot dev="hd" /> + </os> + <features> + <acpi /> + <apic /> + <pae /> + </features> + <cpu mode='host-passthrough'> + <topology cores="{cpu}" sockets="{socket}" threads="{threads}" /> + <numa> + <cell id='0' cpus='{numa_cpus}' memory='{memory}' unit='MB' memAccess='shared'/> + </numa> + </cpu> + <clock offset="utc"> + <timer name="rtc" tickpolicy="catchup" /> + <timer name="pit" tickpolicy="delay" /> + <timer name="hpet" present="no" /> + </clock> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>restart</on_crash> + <devices> + <emulator>/usr/bin/kvm-spice</emulator> + <disk device="disk" type="file"> + <driver name="qemu" type="qcow2" /> + <source file="{vm_image}"/> + <target bus="virtio" dev="vda" /> + </disk> + <graphics autoport="yes" listen="0.0.0.0" port="-1" type="vnc" /> + <interface type="bridge"> + <mac address='{mac_addr}'/> + <source bridge="br-int" /> + <model type='virtio'/> + </interface> + </devices> +</domain> +""" +WAIT_FOR_BOOT = 30 + + +class Libvirt(object): + """ This class handles all the libvirt updates to lauch VM + """ + + @staticmethod + def check_if_vm_exists_and_delete(vm_name, connection): + cmd_template = "virsh list --name | grep -i %s" + status = connection.execute(cmd_template % vm_name)[0] + if status == 0: + LOG.info("VM '%s' is already present.. destroying" % vm_name) + connection.execute("virsh destroy %s" % vm_name) + + @staticmethod + def virsh_create_vm(connection, cfg): + err = connection.execute("virsh create %s" % cfg)[0] + LOG.info("VM create status: %s" % (err)) + + @staticmethod + def virsh_destroy_vm(vm_name, connection): + connection.execute("virsh destroy %s" % vm_name) + + @staticmethod + def add_interface_address(interface, pci_address): + vm_pci = ET.SubElement(interface, 'address') + vm_pci.set('type', 'pci') + vm_pci.set('domain', '0x%s' % pci_address.domain) + vm_pci.set('bus', '0x%s' % pci_address.bus) + vm_pci.set('slot', '0x%s' % pci_address.slot) + vm_pci.set('function', '0x%s' % pci_address.function) + return vm_pci + + @classmethod + def add_ovs_interface(cls, vpath, port_num, vpci, vports_mac, xml): + vhost_path = '{0}/var/run/openvswitch/dpdkvhostuser{1}' + root = ET.parse(xml) + pci_address = PciAddress.parse_address(vpci.strip(), multi_line=True) + device = root.find('devices') + + interface = ET.SubElement(device, 'interface') + interface.set('type', 'vhostuser') + mac = ET.SubElement(interface, 'mac') + mac.set('address', vports_mac) + + source = ET.SubElement(interface, 'source') + source.set('type', 'unix') + source.set('path', vhost_path.format(vpath, port_num)) + source.set('mode', 'client') + + model = ET.SubElement(interface, 'model') + model.set('type', 'virtio') + + driver = ET.SubElement(interface, 'driver') + driver.set('queues', '4') + + host = ET.SubElement(driver, 'host') + host.set('mrg_rxbuf', 'off') + + cls.add_interface_address(interface, pci_address) + + root.write(xml) + + @classmethod + def add_sriov_interfaces(cls, vm_pci, vf_pci, vfmac, xml): + root = ET.parse(xml) + pci_address = PciAddress.parse_address(vf_pci.strip(), multi_line=True) + device = root.find('devices') + + interface = ET.SubElement(device, 'interface') + interface.set('managed', 'yes') + interface.set('type', 'hostdev') + + mac = ET.SubElement(interface, 'mac') + mac.set('address', vfmac) + source = ET.SubElement(interface, 'source') + + addr = ET.SubElement(source, "address") + addr.set('domain', "0x0") + addr.set('bus', "{0}".format(pci_address.bus)) + addr.set('function', "{0}".format(pci_address.function)) + addr.set('slot', "0x{0}".format(pci_address.slot)) + addr.set('type', "pci") + + pci_vm_address = PciAddress.parse_address(vm_pci.strip(), multi_line=True) + cls.add_interface_address(interface, pci_vm_address) + + root.write(xml) + + @staticmethod + def create_snapshot_qemu(connection, index, vm_image): + # build snapshot image + image = "/var/lib/libvirt/images/%s.qcow2" % index + connection.execute("rm %s" % image) + qemu_template = "qemu-img create -f qcow2 -o backing_file=%s %s" + connection.execute(qemu_template % (vm_image, image)) + + return image + + @classmethod + def build_vm_xml(cls, connection, flavor, cfg, vm_name, index): + memory = flavor.get('ram', '4096') + extra_spec = flavor.get('extra_specs', {}) + cpu = extra_spec.get('hw:cpu_cores', '2') + socket = extra_spec.get('hw:cpu_sockets', '1') + threads = extra_spec.get('hw:cpu_threads', '2') + vcpu = int(cpu) * int(threads) + numa_cpus = '0-%s' % (vcpu - 1) + + mac = StandaloneContextHelper.get_mac_address(0x00) + image = cls.create_snapshot_qemu(connection, index, + flavor.get("images", None)) + vm_xml = VM_TEMPLATE.format( + vm_name=vm_name, + random_uuid=uuid.uuid4(), + mac_addr=mac, + memory=memory, vcpu=vcpu, cpu=cpu, + numa_cpus=numa_cpus, + socket=socket, threads=threads, + vm_image=image) + + write_file(cfg, vm_xml) + + return [vcpu, mac] + + @staticmethod + def split_cpu_list(cpu_list): + if not cpu_list: + return [] + + ranges = cpu_list.split(',') + bounds = ([int(b) for b in r.split('-')] for r in ranges) + range_objects = \ + (range(bound[0], bound[1] + 1 if len(bound) == 2 + else bound[0] + 1) for bound in bounds) + + return sorted(itertools.chain.from_iterable(range_objects)) + + @classmethod + def get_numa_nodes(cls): + nodes_sysfs = glob.iglob("/sys/devices/system/node/node*") + nodes = {} + for node_sysfs in nodes_sysfs: + num = os.path.basename(node_sysfs).replace("node", "") + with open(os.path.join(node_sysfs, "cpulist")) as cpulist_file: + cpulist = cpulist_file.read().strip() + nodes[num] = cls.split_cpu_list(cpulist) + LOG.info("nodes: {0}".format(nodes)) + return nodes + + @staticmethod + def update_interrupts_hugepages_perf(connection): + connection.execute("echo 1 > /sys/module/kvm/parameters/allow_unsafe_assigned_interrupts") + connection.execute("echo never > /sys/kernel/mm/transparent_hugepage/enabled") + + @classmethod + def pin_vcpu_for_perf(cls, connection, vm_name, cpu): + nodes = cls.get_numa_nodes() + num_nodes = len(nodes) + vcpi_pin_template = "virsh vcpupin {0} {1} {2}" + for i in range(0, int(cpu)): + core = nodes[str(num_nodes - 1)][i % len(nodes[str(num_nodes - 1)])] + connection.execute(vcpi_pin_template.format(vm_name, i, core)) + cls.update_interrupts_hugepages_perf(connection) + + +class StandaloneContextHelper(object): + """ This class handles all the common code for standalone + """ + def __init__(self): + self.file_path = None + super(StandaloneContextHelper, self).__init__() + + @staticmethod + def install_req_libs(connection, extra_pkgs=[]): + pkgs = ["qemu-kvm", "libvirt-bin", "bridge-utils", "numactl", "fping"] + pkgs.extend(extra_pkgs) + cmd_template = "dpkg-query -W --showformat='${Status}\\n' \"%s\"|grep 'ok installed'" + for pkg in pkgs: + if connection.execute(cmd_template % pkg)[0]: + connection.execute("apt-get update") + connection.execute("apt-get -y install %s" % pkg) + else: + # all installed + return + + @staticmethod + def get_kernel_module(connection, pci, driver): + if not driver: + out = connection.execute("lspci -k -s %s" % pci)[1] + driver = out.split("Kernel modules:").pop().strip() + return driver + + @classmethod + def get_nic_details(cls, connection, networks, dpdk_nic_bind): + for key, ports in networks.items(): + if key == "mgmt": + continue + + phy_ports = ports['phy_port'] + phy_driver = ports.get('phy_driver', None) + driver = cls.get_kernel_module(connection, phy_ports, phy_driver) + + # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe + bind_cmd = "{dpdk_nic_bind} --force -b {driver} {port}" + lshw_cmd = "lshw -c network -businfo | grep '{port}'" + link_show_cmd = "ip -s link show {interface}" + + cmd = bind_cmd.format(dpdk_nic_bind=dpdk_nic_bind, + driver=driver, port=ports['phy_port']) + connection.execute(cmd) + + out = connection.execute(lshw_cmd.format(port=phy_ports))[1] + interface = out.split()[1] + + connection.execute(link_show_cmd.format(interface=interface)) + + ports.update({ + 'interface': str(interface), + 'driver': driver + }) + LOG.info("{0}".format(networks)) + + return networks + + @staticmethod + def get_virtual_devices(connection, pci): + cmd = "cat /sys/bus/pci/devices/{0}/virtfn0/uevent" + output = connection.execute(cmd.format(pci))[1] + + pattern = "PCI_SLOT_NAME=({})".format(PciAddress.PCI_PATTERN_STR) + m = re.search(pattern, output, re.MULTILINE) + + pf_vfs = {} + if m: + pf_vfs = {pci: m.group(1).rstrip()} + + LOG.info("pf_vfs:\n%s", pf_vfs) + + return pf_vfs + + def read_config_file(self): + """Read from config file""" + + with open(self.file_path) as stream: + LOG.info("Parsing pod file: %s", self.file_path) + cfg = yaml_load(stream) + return cfg + + def parse_pod_file(self, file_path, nfvi_role='Sriov'): + self.file_path = file_path + nodes = [] + nfvi_host = [] + try: + cfg = self.read_config_file() + except IOError as io_error: + if io_error.errno != errno.ENOENT: + raise + self.file_path = os.path.join(YARDSTICK_ROOT_PATH, file_path) + cfg = self.read_config_file() + + nodes.extend([node for node in cfg["nodes"] if str(node["role"]) != nfvi_role]) + nfvi_host.extend([node for node in cfg["nodes"] if str(node["role"]) == nfvi_role]) + if not nfvi_host: + raise("Node role is other than SRIOV") + + host_mgmt = {'user': nfvi_host[0]['user'], + 'ip': str(IPNetwork(nfvi_host[0]['ip']).ip), + 'password': nfvi_host[0]['password'], + 'ssh_port': nfvi_host[0].get('ssh_port', 22), + 'key_filename': nfvi_host[0].get('key_filename')} + + return [nodes, nfvi_host, host_mgmt] + + @staticmethod + def get_mac_address(end=0x7f): + mac = [0x52, 0x54, 0x00, + random.randint(0x00, end), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + mac_address = ':'.join('%02x' % x for x in mac) + return mac_address + + @staticmethod + def get_mgmt_ip(connection, mac, cidr, node): + mgmtip = None + times = 10 + while not mgmtip and times: + connection.execute("fping -c 1 -g %s > /dev/null 2>&1" % cidr) + out = connection.execute("ip neighbor | grep '%s'" % mac)[1] + LOG.info("fping -c 1 -g %s > /dev/null 2>&1" % cidr) + if out.strip(): + mgmtip = str(out.split(" ")[0]).strip() + client = ssh.SSH.from_node(node, overrides={"ip": mgmtip}) + client.wait() + break + + time.sleep(WAIT_FOR_BOOT) # FixMe: How to find if VM is booted? + times = times - 1 + return mgmtip + + @classmethod + def wait_for_vnfs_to_start(cls, connection, servers, nodes): + for node in nodes: + vnf = servers[node["name"]] + mgmtip = vnf["network_ports"]["mgmt"]["cidr"] + ip = cls.get_mgmt_ip(connection, node["mac"], mgmtip, node) + if ip: + node["ip"] = ip + return nodes + + +class Server(object): + """ This class handles geting vnf nodes + """ + + @staticmethod + def build_vnf_interfaces(vnf, ports): + interfaces = {} + index = 0 + + for key, vfs in vnf["network_ports"].items(): + if key == "mgmt": + mgmtip = str(IPNetwork(vfs['cidr']).ip) + continue + + vf = ports[vfs[0]] + ip = IPNetwork(vf['cidr']) + interfaces.update({ + key: { + 'vpci': vf['vpci'], + 'driver': "%svf" % vf['driver'], + 'local_mac': vf['mac'], + 'dpdk_port_num': index, + 'local_ip': str(ip.ip), + 'netmask': str(ip.netmask) + }, + }) + index = index + 1 + + return mgmtip, interfaces + + @classmethod + def generate_vnf_instance(cls, flavor, ports, ip, key, vnf, mac): + mgmtip, interfaces = cls.build_vnf_interfaces(vnf, ports) + + result = { + "ip": mgmtip, + "mac": mac, + "host": ip, + "user": flavor.get('user', 'root'), + "interfaces": interfaces, + "routing_table": [], + # empty IPv6 routing table + "nd_route_tbl": [], + "name": key, "role": key + } + + try: + result['key_filename'] = flavor['key_filename'] + except KeyError: + pass + + try: + result['password'] = flavor['password'] + except KeyError: + pass + LOG.info(result) + return result + + +class OvsDeploy(object): + """ This class handles deploy of ovs dpdk + Configuration: ovs_dpdk + """ + + OVS_DEPLOY_SCRIPT = "ovs_deploy.bash" + + def __init__(self, connection, bin_path, ovs_properties): + self.connection = connection + self.bin_path = bin_path + self.ovs_properties = ovs_properties + + def prerequisite(self): + pkgs = ["git", "build-essential", "pkg-config", "automake", + "autotools-dev", "libltdl-dev", "cmake", "libnuma-dev", + "libpcap-dev"] + StandaloneContextHelper.install_req_libs(self.connection, pkgs) + + def ovs_deploy(self): + ovs_deploy = os.path.join(YARDSTICK_ROOT_PATH, + "yardstick/resources/scripts/install/", + self.OVS_DEPLOY_SCRIPT) + if os.path.isfile(ovs_deploy): + self.prerequisite() + remote_ovs_deploy = os.path.join(self.bin_path, self.OVS_DEPLOY_SCRIPT) + LOG.info(remote_ovs_deploy) + self.connection.put(ovs_deploy, remote_ovs_deploy) + + http_proxy = os.environ.get('http_proxy', '') + ovs_details = self.ovs_properties.get("version", {}) + ovs = ovs_details.get("ovs", "2.6.0") + dpdk = ovs_details.get("dpdk", "16.11.1") + + cmd = "sudo -E %s --ovs='%s' --dpdk='%s' -p='%s'" % (remote_ovs_deploy, + ovs, dpdk, http_proxy) + self.connection.execute(cmd) diff --git a/yardstick/benchmark/contexts/standalone/ovs_dpdk.py b/yardstick/benchmark/contexts/standalone/ovs_dpdk.py new file mode 100644 index 000000000..833c3fb80 --- /dev/null +++ b/yardstick/benchmark/contexts/standalone/ovs_dpdk.py @@ -0,0 +1,383 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import os +import logging +import collections +import time + +from collections import OrderedDict + +from yardstick import ssh +from yardstick.network_services.utils import get_nsb_option +from yardstick.network_services.utils import provision_tool +from yardstick.benchmark.contexts.base import Context +from yardstick.benchmark.contexts.standalone.model import Libvirt +from yardstick.benchmark.contexts.standalone.model import StandaloneContextHelper +from yardstick.benchmark.contexts.standalone.model import Server +from yardstick.benchmark.contexts.standalone.model import OvsDeploy +from yardstick.network_services.utils import PciAddress + +LOG = logging.getLogger(__name__) + + +class OvsDpdkContext(Context): + """ This class handles OVS standalone nodes - VM running on Non-Managed NFVi + Configuration: ovs_dpdk + """ + + __context_type__ = "StandaloneOvsDpdk" + + SUPPORTED_OVS_TO_DPDK_MAP = { + '2.6.0': '16.07.1', + '2.6.1': '16.07.2', + '2.7.0': '16.11.1', + '2.7.1': '16.11.2', + '2.7.2': '16.11.3', + '2.8.0': '17.05.2' + } + + DEFAULT_OVS = '2.6.0' + + PKILL_TEMPLATE = "pkill %s %s" + + def __init__(self): + self.file_path = None + self.sriov = [] + self.first_run = True + self.dpdk_nic_bind = "" + self.vm_names = [] + self.name = None + self.nfvi_host = [] + self.nodes = [] + self.networks = {} + self.attrs = {} + self.vm_flavor = None + self.servers = None + self.helper = StandaloneContextHelper() + self.vnf_node = Server() + self.ovs_properties = {} + self.wait_for_vswitchd = 10 + super(OvsDpdkContext, self).__init__() + + def init(self, attrs): + """initializes itself from the supplied arguments""" + + self.name = attrs["name"] + self.file_path = attrs.get("file", "pod.yaml") + + self.nodes, self.nfvi_host, self.host_mgmt = \ + self.helper.parse_pod_file(self.file_path, 'OvsDpdk') + + self.attrs = attrs + self.vm_flavor = attrs.get('flavor', {}) + self.servers = attrs.get('servers', {}) + self.vm_deploy = attrs.get("vm_deploy", True) + self.ovs_properties = attrs.get('ovs_properties', {}) + # add optional static network definition + self.networks = attrs.get("networks", {}) + + LOG.debug("Nodes: %r", self.nodes) + LOG.debug("NFVi Node: %r", self.nfvi_host) + LOG.debug("Networks: %r", self.networks) + + def setup_ovs(self): + vpath = self.ovs_properties.get("vpath", "/usr/local") + xargs_kill_cmd = self.PKILL_TEMPLATE % ('-9', 'ovs') + + create_from = os.path.join(vpath, 'etc/openvswitch/conf.db') + create_to = os.path.join(vpath, 'share/openvswitch/vswitch.ovsschema') + + cmd_list = [ + "chmod 0666 /dev/vfio/*", + "chmod a+x /dev/vfio", + "pkill -9 ovs", + xargs_kill_cmd, + "killall -r 'ovs*'", + "mkdir -p {0}/etc/openvswitch".format(vpath), + "mkdir -p {0}/var/run/openvswitch".format(vpath), + "rm {0}/etc/openvswitch/conf.db".format(vpath), + "ovsdb-tool create {0} {1}".format(create_from, create_to), + "modprobe vfio-pci", + "chmod a+x /dev/vfio", + "chmod 0666 /dev/vfio/*", + ] + for cmd in cmd_list: + self.connection.execute(cmd) + bind_cmd = "{dpdk_nic_bind} --force -b {driver} {port}" + phy_driver = "vfio-pci" + for key, port in self.networks.items(): + vpci = port.get("phy_port") + self.connection.execute(bind_cmd.format(dpdk_nic_bind=self.dpdk_nic_bind, + driver=phy_driver, port=vpci)) + + def start_ovs_serverswitch(self): + vpath = self.ovs_properties.get("vpath") + pmd_nums = int(self.ovs_properties.get("pmd_threads", 2)) + ovs_sock_path = '/var/run/openvswitch/db.sock' + log_path = '/var/log/openvswitch/ovs-vswitchd.log' + + pmd_mask = hex(sum(2 ** num for num in range(pmd_nums)) << 1) + socket0 = self.ovs_properties.get("ram", {}).get("socket_0", "2048") + socket1 = self.ovs_properties.get("ram", {}).get("socket_1", "2048") + + ovs_other_config = "ovs-vsctl {0}set Open_vSwitch . other_config:{1}" + detach_cmd = "ovs-vswitchd unix:{0}{1} --pidfile --detach --log-file={2}" + + cmd_list = [ + "mkdir -p /usr/local/var/run/openvswitch", + "ovsdb-server --remote=punix:/{0}/{1} --pidfile --detach".format(vpath, + ovs_sock_path), + ovs_other_config.format("--no-wait ", "dpdk-init=true"), + ovs_other_config.format("--no-wait ", "dpdk-socket-mem='%s,%s'" % (socket0, socket1)), + detach_cmd.format(vpath, ovs_sock_path, log_path), + ovs_other_config.format("", "pmd-cpu-mask=%s" % pmd_mask), + ] + + for cmd in cmd_list: + LOG.info(cmd) + self.connection.execute(cmd) + time.sleep(self.wait_for_vswitchd) + + def setup_ovs_bridge_add_flows(self): + dpdk_args = "" + dpdk_list = [] + vpath = self.ovs_properties.get("vpath", "/usr/local") + version = self.ovs_properties.get('version', {}) + ovs_ver = [int(x) for x in version.get('ovs', self.DEFAULT_OVS).split('.')] + ovs_add_port = \ + "ovs-vsctl add-port {br} {port} -- set Interface {port} type={type_}{dpdk_args}" + ovs_add_queue = "ovs-vsctl set Interface {port} options:n_rxq={queue}" + chmod_vpath = "chmod 0777 {0}/var/run/openvswitch/dpdkvhostuser*" + + cmd_dpdk_list = [ + "ovs-vsctl del-br br0", + "rm -rf /usr/local/var/run/openvswitch/dpdkvhostuser*", + "ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev", + ] + + ordered_network = OrderedDict(self.networks) + for index, (key, vnf) in enumerate(ordered_network.items()): + if ovs_ver >= [2, 7, 0]: + dpdk_args = " options:dpdk-devargs=%s" % vnf.get("phy_port") + dpdk_list.append(ovs_add_port.format(br='br0', port='dpdk%s' % vnf.get("port_num", 0), + type_='dpdk', dpdk_args=dpdk_args)) + dpdk_list.append(ovs_add_queue.format(port='dpdk%s' % vnf.get("port_num", 0), + queue=self.ovs_properties.get("queues", 4))) + + # Sorting the array to make sure we execute dpdk0... in the order + list.sort(dpdk_list) + cmd_dpdk_list.extend(dpdk_list) + + # Need to do two for loop to maintain the dpdk/vhost ports. + for index, _ in enumerate(ordered_network): + cmd_dpdk_list.append(ovs_add_port.format(br='br0', port='dpdkvhostuser%s' % index, + type_='dpdkvhostuser', dpdk_args="")) + + for cmd in cmd_dpdk_list: + LOG.info(cmd) + self.connection.execute(cmd) + + # Fixme: add flows code + ovs_flow = "ovs-ofctl add-flow br0 in_port=%s,action=output:%s" + + network_count = len(ordered_network) + 1 + for in_port, out_port in zip(range(1, network_count), + range(network_count, network_count * 2)): + self.connection.execute(ovs_flow % (in_port, out_port)) + self.connection.execute(ovs_flow % (out_port, in_port)) + + self.connection.execute(chmod_vpath.format(vpath)) + + def cleanup_ovs_dpdk_env(self): + self.connection.execute("ovs-vsctl del-br br0") + self.connection.execute("pkill -9 ovs") + + def check_ovs_dpdk_env(self): + self.cleanup_ovs_dpdk_env() + + version = self.ovs_properties.get("version", {}) + ovs_ver = version.get("ovs", self.DEFAULT_OVS) + dpdk_ver = version.get("dpdk", "16.07.2").split('.') + + supported_version = self.SUPPORTED_OVS_TO_DPDK_MAP.get(ovs_ver, None) + if supported_version is None or supported_version.split('.')[:2] != dpdk_ver[:2]: + raise Exception("Unsupported ovs '{}'. Please check the config...".format(ovs_ver)) + + status = self.connection.execute("ovs-vsctl -V | grep -i '%s'" % ovs_ver)[0] + if status: + deploy = OvsDeploy(self.connection, + get_nsb_option("bin_path"), + self.ovs_properties) + deploy.ovs_deploy() + + def deploy(self): + """don't need to deploy""" + + # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. + if not self.vm_deploy: + return + + self.connection = ssh.SSH.from_node(self.host_mgmt) + self.dpdk_nic_bind = provision_tool( + self.connection, + os.path.join(get_nsb_option("bin_path"), "dpdk-devbind.py")) + + # Check dpdk/ovs version, if not present install + self.check_ovs_dpdk_env() + # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. + StandaloneContextHelper.install_req_libs(self.connection) + self.networks = StandaloneContextHelper.get_nic_details(self.connection, + self.networks, + self.dpdk_nic_bind) + + self.setup_ovs() + self.start_ovs_serverswitch() + self.setup_ovs_bridge_add_flows() + self.nodes = self.setup_ovs_dpdk_context() + LOG.debug("Waiting for VM to come up...") + self.nodes = StandaloneContextHelper.wait_for_vnfs_to_start(self.connection, + self.servers, + self.nodes) + + def undeploy(self): + + if not self.vm_deploy: + return + + # Cleanup the ovs installation... + self.cleanup_ovs_dpdk_env() + + # Bind nics back to kernel + bind_cmd = "{dpdk_nic_bind} --force -b {driver} {port}" + for key, port in self.networks.items(): + vpci = port.get("phy_port") + phy_driver = port.get("driver") + self.connection.execute(bind_cmd.format(dpdk_nic_bind=self.dpdk_nic_bind, + driver=phy_driver, port=vpci)) + + # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config. + for vm in self.vm_names: + Libvirt.check_if_vm_exists_and_delete(vm, self.connection) + + def _get_server(self, attr_name): + """lookup server info by name from context + + Keyword arguments: + attr_name -- A name for a server listed in nodes config file + """ + node_name, name = self.split_name(attr_name) + if name is None or self.name != name: + return None + + matching_nodes = (n for n in self.nodes if n["name"] == node_name) + try: + # A clone is created in order to avoid affecting the + # original one. + node = dict(next(matching_nodes)) + except StopIteration: + return None + + try: + duplicate = next(matching_nodes) + except StopIteration: + pass + else: + raise ValueError("Duplicate nodes!!! Nodes: %s %s", + (node, duplicate)) + + node["name"] = attr_name + return node + + def _get_network(self, attr_name): + if not isinstance(attr_name, collections.Mapping): + network = self.networks.get(attr_name) + + else: + # Don't generalize too much Just support vld_id + vld_id = attr_name.get('vld_id', {}) + # for standalone context networks are dicts + iter1 = (n for n in self.networks.values() if n.get('vld_id') == vld_id) + network = next(iter1, None) + + if network is None: + return None + + result = { + # name is required + "name": network["name"], + "vld_id": network.get("vld_id"), + "segmentation_id": network.get("segmentation_id"), + "network_type": network.get("network_type"), + "physical_network": network.get("physical_network"), + } + return result + + def configure_nics_for_ovs_dpdk(self): + portlist = OrderedDict(self.networks) + for key, ports in portlist.items(): + mac = StandaloneContextHelper.get_mac_address() + portlist[key].update({'mac': mac}) + self.networks = portlist + LOG.info("Ports %s" % self.networks) + + def _enable_interfaces(self, index, vfs, cfg): + vpath = self.ovs_properties.get("vpath", "/usr/local") + vf = self.networks[vfs[0]] + port_num = vf.get('port_num', 0) + vpci = PciAddress.parse_address(vf['vpci'].strip(), multi_line=True) + # Generate the vpci for the interfaces + slot = index + port_num + 10 + vf['vpci'] = \ + "{}:{}:{:02x}.{}".format(vpci.domain, vpci.bus, slot, vpci.function) + Libvirt.add_ovs_interface(vpath, port_num, vf['vpci'], vf['mac'], str(cfg)) + + def setup_ovs_dpdk_context(self): + nodes = [] + + self.configure_nics_for_ovs_dpdk() + + for index, (key, vnf) in enumerate(OrderedDict(self.servers).items()): + cfg = '/tmp/vm_ovs_%d.xml' % index + vm_name = "vm_%d" % index + + # 1. Check and delete VM if already exists + Libvirt.check_if_vm_exists_and_delete(vm_name, self.connection) + + vcpu, mac = Libvirt.build_vm_xml(self.connection, self.vm_flavor, cfg, vm_name, index) + # 2: Cleanup already available VMs + for idx, (vkey, vfs) in enumerate(OrderedDict(vnf["network_ports"]).items()): + if vkey == "mgmt": + continue + self._enable_interfaces(index, vfs, cfg) + + # copy xml to target... + self.connection.put(cfg, cfg) + + # FIXME: launch through libvirt + LOG.info("virsh create ...") + Libvirt.virsh_create_vm(self.connection, cfg) + + # 5: Tunning for better performace + Libvirt.pin_vcpu_for_perf(self.connection, vm_name, vcpu) + self.vm_names.append(vm_name) + + # build vnf node details + nodes.append(self.vnf_node.generate_vnf_instance(self.vm_flavor, + self.networks, + self.host_mgmt.get('ip'), + key, vnf, mac)) + + return nodes diff --git a/yardstick/benchmark/contexts/standalone/ovsdpdk.py b/yardstick/benchmark/contexts/standalone/ovsdpdk.py deleted file mode 100644 index cf5529d89..000000000 --- a/yardstick/benchmark/contexts/standalone/ovsdpdk.py +++ /dev/null @@ -1,369 +0,0 @@ -# Copyright (c) 2016-2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -import os -import yaml -import time -import glob -import itertools -import logging -from yardstick import ssh -from yardstick.benchmark.contexts.standalone import StandaloneContext - -BIN_PATH = "/opt/isb_bin/" -DPDK_NIC_BIND = "dpdk_nic_bind.py" - -log = logging.getLogger(__name__) - -VM_TEMPLATE = """ -<domain type='kvm'> - <name>vm1</name> - <uuid>18230c0c-635d-4c50-b2dc-a213d30acb34</uuid> - <memory unit='KiB'>20971520</memory> - <currentMemory unit="KiB">20971520</currentMemory> - <memoryBacking> - <hugepages/> - </memoryBacking> - <vcpu placement='static'>20</vcpu> - <os> - <type arch='x86_64' machine='pc'>hvm</type> - <boot dev='hd'/> - </os> - <features> - <acpi/> - <apic/> - </features> - <cpu match="exact" mode='host-model'> - <model fallback='allow'/> - <topology sockets='1' cores='10' threads='2'/> - </cpu> - <on_poweroff>destroy</on_poweroff> - <on_reboot>restart</on_reboot> - <on_crash>destroy</on_crash> - <devices> - <emulator>/usr/bin/qemu-system-x86_64</emulator> - <disk type='file' device='disk'> - <driver name='qemu' type='qcow2' cache='none'/> - <source file="{vm_image}"/> - <target dev='vda' bus='virtio'/> - <address bus="0x00" domain="0x0000" - function="0x0" slot="0x04" type="pci" /> - </disk> - <!--disk type='dir' device='disk'> - <driver name='qemu' type='fat'/> - <source dir='/opt/isb_bin/dpdk'/> - <target dev='vdb' bus='virtio'/> - <readonly/> - </disk--> - <interface type="bridge"> - <mac address="00:00:00:ab:cd:ef" /> - <source bridge="br-int" /> - </interface> - <interface type='vhostuser'> - <mac address='00:00:00:00:00:01'/> - <source type='unix' path='/usr/local/var/run/openvswitch/dpdkvhostuser0' mode='client'/> - <model type='virtio'/> - <driver queues='4'> - <host mrg_rxbuf='off'/> - </driver> - </interface> - <interface type='vhostuser'> - <mac address='00:00:00:00:00:02'/> - <source type='unix' path='/usr/local/var/run/openvswitch/dpdkvhostuser1' mode='client'/> - <model type='virtio'/> - <driver queues='4'> - <host mrg_rxbuf='off'/> - </driver> - </interface> - <serial type='pty'> - <target port='0'/> - </serial> - <console type='pty'> - <target type='serial' port='0'/> - </console> - <graphics autoport="yes" listen="0.0.0.0" port="1" type="vnc" /> - </devices> -</domain> -""" - - -class Ovsdpdk(StandaloneContext): - def __init__(self): - self.name = None - self.file_path = None - self.nodes = [] - self.vm_deploy = False - self.ovs = [] - self.first_run = True - self.dpdk_nic_bind = BIN_PATH + DPDK_NIC_BIND - self.user = "" - self.ssh_ip = "" - self.passwd = "" - self.ssh_port = "" - self.auth_type = "" - - def init(self): - '''initializes itself''' - log.debug("In init") - self.parse_pod_and_get_data() - - def parse_pod_and_get_data(self, file_path): - self.file_path = file_path - print("parsing pod file: {0}".format(self.file_path)) - try: - with open(self.file_path) as stream: - cfg = yaml.load(stream) - except IOError: - print("File {0} does not exist".format(self.file_path)) - raise - - self.ovs.extend([node for node in cfg["nodes"] - if node["role"] == "Ovsdpdk"]) - self.user = self.ovs[0]['user'] - self.ssh_ip = self.ovs[0]['ip'] - if self.ovs[0]['auth_type'] == "password": - self.passwd = self.ovs[0]['password'] - else: - self.ssh_port = self.ovs[0]['ssh_port'] - self.key_filename = self.ovs[0]['key_filename'] - - def ssh_remote_machine(self): - if self.ovs[0]['auth_type'] == "password": - self.connection = ssh.SSH( - self.user, - self.ssh_ip, - password=self.passwd) - self.connection.wait() - else: - if self.ssh_port is not None: - ssh_port = self.ssh_port - else: - ssh_port = ssh.DEFAULT_PORT - self.connection = ssh.SSH( - self.user, - self.ssh_ip, - port=ssh_port, - key_filename=self.key_filename) - self.connection.wait() - - def get_nic_details(self): - nic_details = {} - nic_details['interface'] = {} - nic_details['pci'] = self.ovs[0]['phy_ports'] - nic_details['phy_driver'] = self.ovs[0]['phy_driver'] - nic_details['vports_mac'] = self.ovs[0]['vports_mac'] - # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe - for i, _ in enumerate(nic_details['pci']): - err, out, _ = self.connection.execute( - "{dpdk_nic_bind} --force -b {driver} {port}".format( - dpdk_nic_bind=self.dpdk_nic_bind, - driver=self.ovs[0]['phy_driver'], - port=self.ovs[0]['phy_ports'][i])) - err, out, _ = self.connection.execute( - "lshw -c network -businfo | grep '{port}'".format( - port=self.ovs[0]['phy_ports'][i])) - a = out.split()[1] - err, out, _ = self.connection.execute( - "ip -s link show {interface}".format( - interface=out.split()[1])) - nic_details['interface'][i] = str(a) - print("{0}".format(nic_details)) - return nic_details - - def install_req_libs(self): - if self.first_run: - err, out, _ = self.connection.execute("apt-get update") - print("{0}".format(out)) - err, out, _ = self.connection.execute( - "apt-get -y install qemu-kvm libvirt-bin") - print("{0}".format(out)) - err, out, _ = self.connection.execute( - "apt-get -y install libvirt-dev bridge-utils numactl") - print("{0}".format(out)) - self.first_run = False - - def setup_ovs(self, vpcis): - self.connection.execute("/usr/bin/chmod 0666 /dev/vfio/*") - self.connection.execute("/usr/bin/chmod a+x /dev/vfio") - self.connection.execute("pkill -9 ovs") - self.connection.execute("ps -ef | grep ovs | grep -v grep | " - "awk '{print $2}' | xargs -r kill -9") - self.connection.execute("killall -r 'ovs*'") - self.connection.execute( - "mkdir -p {0}/etc/openvswitch".format(self.ovs[0]["vpath"])) - self.connection.execute( - "mkdir -p {0}/var/run/openvswitch".format(self.ovs[0]["vpath"])) - self.connection.execute( - "rm {0}/etc/openvswitch/conf.db".format(self.ovs[0]["vpath"])) - self.connection.execute( - "ovsdb-tool create {0}/etc/openvswitch/conf.db " - "{0}/share/openvswitch/" - "vswitch.ovsschema".format(self.ovs[0]["vpath"])) - self.connection.execute("modprobe vfio-pci") - self.connection.execute("chmod a+x /dev/vfio") - self.connection.execute("chmod 0666 /dev/vfio/*") - for vpci in vpcis: - self.connection.execute( - "/opt/isb_bin/dpdk_nic_bind.py " - "--bind=vfio-pci {0}".format(vpci)) - - def start_ovs_serverswitch(self): - self.connection.execute("mkdir -p /usr/local/var/run/openvswitch") - self.connection.execute( - "ovsdb-server --remote=punix:" - "/usr/local/var/run/openvswitch/db.sock --pidfile --detach") - self.connection.execute( - "ovs-vsctl --no-wait set " - "Open_vSwitch . other_config:dpdk-init=true") - self.connection.execute( - "ovs-vsctl --no-wait set " - "Open_vSwitch . other_config:dpdk-lcore-mask=0x3") - self.connection.execute( - "ovs-vsctl --no-wait set " - "Open_vSwitch . other_config:dpdk-socket-mem='2048,0'") - self.connection.execute( - "ovs-vswitchd unix:{0}/" - "var/run/openvswitch/db.sock --pidfile --detach " - "--log-file=/var/log/openvswitch/" - "ovs-vswitchd.log".format( - self.ovs[0]["vpath"])) - self.connection.execute( - "ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=2C") - - def setup_ovs_bridge(self): - self.connection.execute("ovs-vsctl del-br br0") - self.connection.execute( - "rm -rf /usr/local/var/run/openvswitch/dpdkvhostuser*") - self.connection.execute( - "ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev") - self.connection.execute( - "ovs-vsctl add-port br0 dpdk0 -- set Interface dpdk0 type=dpdk") - self.connection.execute( - "ovs-vsctl add-port br0 dpdk1 -- set Interface dpdk1 type=dpdk") - self.connection.execute( - "ovs-vsctl add-port br0 dpdkvhostuser0 -- set Interface " - "dpdkvhostuser0 type=dpdkvhostuser") - self.connection.execute("ovs-vsctl add-port br0 dpdkvhostuser1 " - "-- set Interface dpdkvhostuser1 " - "type=dpdkvhostuser") - self.connection.execute( - "chmod 0777 {0}/var/run/" - "openvswitch/dpdkvhostuser*".format(self.ovs[0]["vpath"])) - - def add_oflows(self): - self.connection.execute("ovs-ofctl del-flows br0") - for flow in self.ovs[0]["flow"]: - self.connection.execute(flow) - self.connection.execute("ovs-ofctl dump-flows br0") - self.connection.execute( - "ovs-vsctl set Interface dpdk0 options:n_rxq=4") - self.connection.execute( - "ovs-vsctl set Interface dpdk1 options:n_rxq=4") - - def setup_ovs_context(self, pcis, nic_details, host_driver): - - ''' 1: Setup vm_ovs.xml to launch VM.''' - cfg_ovs = '/tmp/vm_ovs.xml' - vm_ovs_xml = VM_TEMPLATE.format(vm_image=self.ovs[0]["images"]) - with open(cfg_ovs, 'w') as f: - f.write(vm_ovs_xml) - - ''' 2: Create and start the VM''' - self.connection.put(cfg_ovs, cfg_ovs) - time.sleep(10) - err, out = self.check_output("virsh list --name | grep -i vm1") - if out == "vm1": - print("VM is already present") - else: - ''' FIXME: launch through libvirt''' - print("virsh create ...") - err, out, _ = self.connection.execute( - "virsh create /tmp/vm_ovs.xml") - time.sleep(10) - print("err : {0}".format(err)) - print("{0}".format(_)) - print("out : {0}".format(out)) - - ''' 3: Tuning for better performace.''' - self.pin_vcpu(pcis) - self.connection.execute( - "echo 1 > /sys/module/kvm/parameters/" - "allow_unsafe_assigned_interrupts") - self.connection.execute( - "echo never > /sys/kernel/mm/transparent_hugepage/enabled") - print("After tuning performance ...") - - ''' This is roughly compatible with check_output function in subprocess - module which is only available in python 2.7.''' - def check_output(self, cmd, stderr=None): - '''Run a command and capture its output''' - err, out, _ = self.connection.execute(cmd) - return err, out - - def read_from_file(self, filename): - data = "" - with open(filename, 'r') as the_file: - data = the_file.read() - return data - - def write_to_file(self, filename, content): - with open(filename, 'w') as the_file: - the_file.write(content) - - def pin_vcpu(self, pcis): - nodes = self.get_numa_nodes() - print("{0}".format(nodes)) - num_nodes = len(nodes) - for i in range(0, 10): - self.connection.execute( - "virsh vcpupin vm1 {0} {1}".format( - i, nodes[str(num_nodes - 1)][i % len(nodes[str(num_nodes - 1)])])) - - def get_numa_nodes(self): - nodes_sysfs = glob.iglob("/sys/devices/system/node/node*") - nodes = {} - for node_sysfs in nodes_sysfs: - num = os.path.basename(node_sysfs).replace("node", "") - with open(os.path.join(node_sysfs, "cpulist")) as cpulist_file: - cpulist = cpulist_file.read().strip() - print("cpulist: {0}".format(cpulist)) - nodes[num] = self.split_cpu_list(cpulist) - print("nodes: {0}".format(nodes)) - return nodes - - def split_cpu_list(self, cpu_list): - if cpu_list: - ranges = cpu_list.split(',') - bounds = ([int(b) for b in r.split('-')] for r in ranges) - range_objects =\ - (range(bound[0], bound[1] + 1 if len(bound) == 2 - else bound[0] + 1) for bound in bounds) - - return sorted(itertools.chain.from_iterable(range_objects)) - else: - return [] - - def destroy_vm(self): - host_driver = self.ovs[0]['phy_driver'] - err, out = self.check_output("virsh list --name | grep -i vm1") - print("{0}".format(out)) - if err == 0: - self.connection.execute("virsh shutdown vm1") - self.connection.execute("virsh destroy vm1") - self.check_output("rmmod {0}".format(host_driver))[1].splitlines() - self.check_output("modprobe {0}".format(host_driver))[ - 1].splitlines() - else: - print("error : ", err) diff --git a/yardstick/benchmark/contexts/standalone/sriov.py b/yardstick/benchmark/contexts/standalone/sriov.py index fe27d2579..55d7057a9 100644 --- a/yardstick/benchmark/contexts/standalone/sriov.py +++ b/yardstick/benchmark/contexts/standalone/sriov.py @@ -14,418 +14,248 @@ from __future__ import absolute_import import os -import yaml -import re -import time -import glob -import uuid -import random import logging -import itertools -import xml.etree.ElementTree as ET +import collections +from collections import OrderedDict + from yardstick import ssh from yardstick.network_services.utils import get_nsb_option from yardstick.network_services.utils import provision_tool -from yardstick.benchmark.contexts.standalone import StandaloneContext - -log = logging.getLogger(__name__) - -VM_TEMPLATE = """ -<domain type="kvm"> - <name>vm1</name> - <uuid>{random_uuid}</uuid> - <memory unit="KiB">102400</memory> - <currentMemory unit="KiB">102400</currentMemory> - <memoryBacking> - <hugepages /> - </memoryBacking> - <vcpu placement="static">20</vcpu> - <os> - <type arch="x86_64" machine="pc-i440fx-utopic">hvm</type> - <boot dev="hd" /> - </os> - <features> - <acpi /> - <apic /> - <pae /> - </features> - <cpu match="exact" mode="custom"> - <model fallback="allow">SandyBridge</model> - <topology cores="10" sockets="1" threads="2" /> - </cpu> - <clock offset="utc"> - <timer name="rtc" tickpolicy="catchup" /> - <timer name="pit" tickpolicy="delay" /> - <timer name="hpet" present="no" /> - </clock> - <on_poweroff>destroy</on_poweroff> - <on_reboot>restart</on_reboot> - <on_crash>restart</on_crash> - <devices> - <emulator>/usr/bin/kvm-spice</emulator> - <disk device="disk" type="file"> - <driver name="qemu" type="qcow2" /> - <source file="{vm_image}"/> - <target bus="virtio" dev="vda" /> - <address bus="0x00" domain="0x0000" -function="0x0" slot="0x04" type="pci" /> - </disk> - <controller index="0" model="ich9-ehci1" type="usb"> - <address bus="0x00" domain="0x0000" -function="0x7" slot="0x05" type="pci" /> - </controller> - <controller index="0" model="ich9-uhci1" type="usb"> - <master startport="0" /> - <address bus="0x00" domain="0x0000" function="0x0" -multifunction="on" slot="0x05" type="pci" /> - </controller> - <controller index="0" model="ich9-uhci2" type="usb"> - <master startport="2" /> - <address bus="0x00" domain="0x0000" -function="0x1" slot="0x05" type="pci" /> - </controller> - <controller index="0" model="ich9-uhci3" type="usb"> - <master startport="4" /> - <address bus="0x00" domain="0x0000" -function="0x2" slot="0x05" type="pci" /> - </controller> - <controller index="0" model="pci-root" type="pci" /> - <serial type="pty"> - <target port="0" /> - </serial> - <console type="pty"> - <target port="0" type="serial" /> - </console> - <input bus="usb" type="tablet" /> - <input bus="ps2" type="mouse" /> - <input bus="ps2" type="keyboard" /> - <graphics autoport="yes" listen="0.0.0.0" port="-1" type="vnc" /> - <video> - <model heads="1" type="cirrus" vram="16384" /> - <address bus="0x00" domain="0x0000" -function="0x0" slot="0x02" type="pci" /> - </video> - <memballoon model="virtio"> - <address bus="0x00" domain="0x0000" -function="0x0" slot="0x06" type="pci" /> - </memballoon> - <interface type="bridge"> - <mac address="{mac_addr}" /> - <source bridge="virbr0" /> - </interface> - </devices> -</domain> -""" - - -class Sriov(StandaloneContext): +from yardstick.benchmark.contexts.base import Context +from yardstick.benchmark.contexts.standalone.model import Libvirt +from yardstick.benchmark.contexts.standalone.model import StandaloneContextHelper +from yardstick.benchmark.contexts.standalone.model import Server +from yardstick.network_services.utils import PciAddress + +LOG = logging.getLogger(__name__) + + +class SriovContext(Context): + """ This class handles SRIOV standalone nodes - VM running on Non-Managed NFVi + Configuration: sr-iov + """ + + __context_type__ = "StandaloneSriov" + def __init__(self): - self.name = None self.file_path = None - self.nodes = [] - self.vm_deploy = False self.sriov = [] self.first_run = True self.dpdk_nic_bind = "" - self.user = "" - self.ssh_ip = "" - self.passwd = "" - self.ssh_port = "" - self.auth_type = "" - - def init(self): - log.debug("In init") - self.parse_pod_and_get_data(self.file_path) - - def parse_pod_and_get_data(self, file_path): - self.file_path = file_path - log.debug("parsing pod file: {0}".format(self.file_path)) - try: - with open(self.file_path) as stream: - cfg = yaml.load(stream) - except IOError: - log.error("File {0} does not exist".format(self.file_path)) - raise - - self.sriov.extend([node for node in cfg["nodes"] - if node["role"] == "Sriov"]) - self.user = self.sriov[0]['user'] - self.ssh_ip = self.sriov[0]['ip'] - if self.sriov[0]['auth_type'] == "password": - self.passwd = self.sriov[0]['password'] - else: - self.ssh_port = self.sriov[0]['ssh_port'] - self.key_filename = self.sriov[0]['key_filename'] - - def ssh_remote_machine(self): - if self.sriov[0]['auth_type'] == "password": - self.connection = ssh.SSH( - self.user, - self.ssh_ip, - password=self.passwd) - self.connection.wait() - else: - if self.ssh_port is not None: - ssh_port = self.ssh_port - else: - ssh_port = ssh.DEFAULT_PORT - self.connection = ssh.SSH( - self.user, - self.ssh_ip, - port=ssh_port, - key_filename=self.key_filename) - self.connection.wait() + self.vm_names = [] + self.name = None + self.nfvi_host = [] + self.nodes = [] + self.networks = {} + self.attrs = {} + self.vm_flavor = None + self.servers = None + self.helper = StandaloneContextHelper() + self.vnf_node = Server() + self.drivers = [] + super(SriovContext, self).__init__() + + def init(self, attrs): + """initializes itself from the supplied arguments""" + + self.name = attrs["name"] + self.file_path = attrs.get("file", "pod.yaml") + + self.nodes, self.nfvi_host, self.host_mgmt = \ + self.helper.parse_pod_file(self.file_path, 'Sriov') + + self.attrs = attrs + self.vm_flavor = attrs.get('flavor', {}) + self.servers = attrs.get('servers', {}) + self.vm_deploy = attrs.get("vm_deploy", True) + # add optional static network definition + self.networks = attrs.get("networks", {}) + + LOG.debug("Nodes: %r", self.nodes) + LOG.debug("NFVi Node: %r", self.nfvi_host) + LOG.debug("Networks: %r", self.networks) + + def deploy(self): + """don't need to deploy""" + + # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. + if not self.vm_deploy: + return + + self.connection = ssh.SSH.from_node(self.host_mgmt) self.dpdk_nic_bind = provision_tool( self.connection, os.path.join(get_nsb_option("bin_path"), "dpdk_nic_bind.py")) - def get_nic_details(self): - nic_details = {} - nic_details = { - 'interface': {}, - 'pci': self.sriov[0]['phy_ports'], - 'phy_driver': self.sriov[0]['phy_driver'], - 'vf_macs': self.sriov[0]['vf_macs'] - } - # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe - for i, _ in enumerate(nic_details['pci']): - err, out, _ = self.connection.execute( - "{dpdk_nic_bind} --force -b {driver} {port}".format( - dpdk_nic_bind=self.dpdk_nic_bind, - driver=self.sriov[0]['phy_driver'], - port=self.sriov[0]['phy_ports'][i])) - err, out, _ = self.connection.execute( - "lshw -c network -businfo | grep '{port}'".format( - port=self.sriov[0]['phy_ports'][i])) - a = out.split()[1] - err, out, _ = self.connection.execute( - "ip -s link show {interface}".format( - interface=out.split()[1])) - nic_details['interface'][i] = str(a) - log.info("{0}".format(nic_details)) - return nic_details - - def install_req_libs(self): - if self.first_run: - log.info("Installing required libraries...") - err, out, _ = self.connection.execute("apt-get update") - log.debug("{0}".format(out)) - err, out, _ = self.connection.execute( - "apt-get -y install qemu-kvm libvirt-bin") - log.debug("{0}".format(out)) - err, out, _ = self.connection.execute( - "apt-get -y install libvirt-dev bridge-utils numactl") - log.debug("{0}".format(out)) - self.first_run = False - - def configure_nics_for_sriov(self, host_driver, nic_details): - vf_pci = [[], []] - self.connection.execute( - "rmmod {0}".format(host_driver))[1].splitlines() - self.connection.execute( - "modprobe {0} num_vfs=1".format(host_driver))[1].splitlines() - nic_details['vf_pci'] = {} - for i in range(len(nic_details['pci'])): - self.connection.execute( - "echo 1 > /sys/bus/pci/devices/{0}/sriov_numvfs".format( - nic_details['pci'][i])) - err, out, _ = self.connection.execute( - "ip link set {interface} vf 0 mac {mac}".format( - interface=nic_details['interface'][i], - mac=nic_details['vf_macs'][i])) - time.sleep(3) - vf_pci[i] = self.get_vf_datas( - 'vf_pci', - nic_details['pci'][i], - nic_details['vf_macs'][i]) - nic_details['vf_pci'][i] = vf_pci[i] - log.debug("NIC DETAILS : {0}".format(nic_details)) - return nic_details - - def setup_sriov_context(self, pcis, nic_details, host_driver): - blacklist = "/etc/modprobe.d/blacklist.conf" - - # 1 : Blacklist the vf driver in /etc/modprobe.d/blacklist.conf - vfnic = "{0}vf".format(host_driver) - lines = self.read_from_file(blacklist) - if vfnic not in lines: - vfblacklist = "blacklist {vfnic}".format(vfnic=vfnic) - self.connection.execute( - "echo {vfblacklist} >> {blacklist}".format( - vfblacklist=vfblacklist, - blacklist=blacklist)) - - # 2 : modprobe host_driver with num_vfs - nic_details = self.configure_nics_for_sriov(host_driver, nic_details) - - # 3: Setup vm_sriov.xml to launch VM - cfg_sriov = '/tmp/vm_sriov.xml' - mac = [0x00, 0x24, 0x81, - random.randint(0x00, 0x7f), - random.randint(0x00, 0xff), - random.randint(0x00, 0xff)] - mac_address = ':'.join(map(lambda x: "%02x" % x, mac)) - vm_sriov_xml = VM_TEMPLATE.format( - random_uuid=uuid.uuid4(), - mac_addr=mac_address, - vm_image=self.sriov[0]["images"]) - with open(cfg_sriov, 'w') as f: - f.write(vm_sriov_xml) - - vf = nic_details['vf_pci'] - for index in range(len(nic_details['vf_pci'])): - self.add_sriov_interface( - index, - vf[index]['vf_pci'], - mac_address, - "/tmp/vm_sriov.xml") - self.connection.execute( - "ifconfig {interface} up".format( - interface=nic_details['interface'][index])) - - # 4: Create and start the VM - self.connection.put(cfg_sriov, cfg_sriov) - time.sleep(10) - err, out = self.check_output("virsh list --name | grep -i vm1") + # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. + StandaloneContextHelper.install_req_libs(self.connection) + self.networks = StandaloneContextHelper.get_nic_details(self.connection, + self.networks, + self.dpdk_nic_bind) + self.nodes = self.setup_sriov_context() + + LOG.debug("Waiting for VM to come up...") + self.nodes = StandaloneContextHelper.wait_for_vnfs_to_start(self.connection, + self.servers, + self.nodes) + + def undeploy(self): + """don't need to undeploy""" + + if not self.vm_deploy: + return + + # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config. + for vm in self.vm_names: + Libvirt.check_if_vm_exists_and_delete(vm, self.connection) + + # Bind nics back to kernel + for key, ports in self.networks.items(): + # enable VFs for given... + build_vfs = "echo 0 > /sys/bus/pci/devices/{0}/sriov_numvfs" + self.connection.execute(build_vfs.format(ports.get('phy_port'))) + + def _get_server(self, attr_name): + """lookup server info by name from context + + Keyword arguments: + attr_name -- A name for a server listed in nodes config file + """ + node_name, name = self.split_name(attr_name) + if name is None or self.name != name: + return None + + matching_nodes = (n for n in self.nodes if n["name"] == node_name) try: - if out == "vm1": - log.info("VM is already present") - else: - # FIXME: launch through libvirt - log.info("virsh create ...") - err, out, _ = self.connection.execute( - "virsh create /tmp/vm_sriov.xml") - time.sleep(10) - log.error("err : {0}".format(err)) - log.error("{0}".format(_)) - log.debug("out : {0}".format(out)) - except ValueError: - raise - - # 5: Tunning for better performace - self.pin_vcpu(pcis) - self.connection.execute( - "echo 1 > /sys/module/kvm/parameters/" - "allow_unsafe_assigned_interrupts") - self.connection.execute( - "echo never > /sys/kernel/mm/transparent_hugepage/enabled") - - def add_sriov_interface(self, index, vf_pci, vfmac, xml): - root = ET.parse(xml) - pattern = "0000:(\d+):(\d+).(\d+)" - m = re.search(pattern, vf_pci, re.MULTILINE) - device = root.find('devices') - - interface = ET.SubElement(device, 'interface') - interface.set('managed', 'yes') - interface.set('type', 'hostdev') - - mac = ET.SubElement(interface, 'mac') - mac.set('address', vfmac) - source = ET.SubElement(interface, 'source') - - addr = ET.SubElement(source, "address") - addr.set('domain', "0x0") - addr.set('bus', "{0}".format(m.group(1))) - addr.set('function', "{0}".format(m.group(3))) - addr.set('slot', "{0}".format(m.group(2))) - addr.set('type', "pci") - - vf_pci = ET.SubElement(interface, 'address') - vf_pci.set('type', 'pci') - vf_pci.set('domain', '0x0000') - vf_pci.set('bus', '0x00') - vf_pci.set('slot', '0x0{0}'.format(index + 7)) - vf_pci.set('function', '0x00') - - root.write(xml) - - # This is roughly compatible with check_output function in subprocess - # module which is only available in python 2.7 - def check_output(self, cmd, stderr=None): - # Run a command and capture its output - err, out, _ = self.connection.execute(cmd) - return err, out - - def get_virtual_devices(self, pci): - pf_vfs = {} - err, extra_info = self.check_output( - "cat /sys/bus/pci/devices/{0}/virtfn0/uevent".format(pci)) - pattern = "PCI_SLOT_NAME=(?P<name>[0-9:.\s.]+)" - m = re.search(pattern, extra_info, re.MULTILINE) - - if m: - pf_vfs.update({pci: str(m.group(1).rstrip())}) - log.info("pf_vfs : {0}".format(pf_vfs)) - return pf_vfs - - def get_vf_datas(self, key, value, vfmac): - vfret = {} - pattern = "0000:(\d+):(\d+).(\d+)" - - vfret["mac"] = vfmac - vfs = self.get_virtual_devices(value) - log.info("vfs: {0}".format(vfs)) - for k, v in vfs.items(): - m = re.search(pattern, k, re.MULTILINE) - m1 = re.search(pattern, value, re.MULTILINE) - if m.group(1) == m1.group(1): - vfret["vf_pci"] = str(v) - break + # A clone is created in order to avoid affecting the + # original one. + node = dict(next(matching_nodes)) + except StopIteration: + return None - return vfret - - def read_from_file(self, filename): - data = "" - with open(filename, 'r') as the_file: - data = the_file.read() - return data - - def write_to_file(self, filename, content): - with open(filename, 'w') as the_file: - the_file.write(content) - - def pin_vcpu(self, pcis): - nodes = self.get_numa_nodes() - log.info("{0}".format(nodes)) - num_nodes = len(nodes) - for i in range(0, 10): - self.connection.execute( - "virsh vcpupin vm1 {0} {1}".format( - i, nodes[str(num_nodes - 1)][i % len(nodes[str(num_nodes - 1)])])) - - def get_numa_nodes(self): - nodes_sysfs = glob.iglob("/sys/devices/system/node/node*") - nodes = {} - for node_sysfs in nodes_sysfs: - num = os.path.basename(node_sysfs).replace("node", "") - with open(os.path.join(node_sysfs, "cpulist")) as cpulist_file: - cpulist = cpulist_file.read().strip() - nodes[num] = self.split_cpu_list(cpulist) - log.info("nodes: {0}".format(nodes)) - return nodes + try: + duplicate = next(matching_nodes) + except StopIteration: + pass + else: + raise ValueError("Duplicate nodes!!! Nodes: %s %s", + (node, duplicate)) - def split_cpu_list(self, cpu_list): - if cpu_list: - ranges = cpu_list.split(',') - bounds = ([int(b) for b in r.split('-')] for r in ranges) - range_objects =\ - (range(bound[0], bound[1] + 1 if len(bound) == 2 - else bound[0] + 1) for bound in bounds) + node["name"] = attr_name + return node + + def _get_network(self, attr_name): + if not isinstance(attr_name, collections.Mapping): + network = self.networks.get(attr_name) - return sorted(itertools.chain.from_iterable(range_objects)) - else: - return [] - - def destroy_vm(self): - host_driver = self.sriov[0]["phy_driver"] - err, out = self.check_output("virsh list --name | grep -i vm1") - log.info("{0}".format(out)) - if err == 0: - self.connection.execute("virsh shutdown vm1") - self.connection.execute("virsh destroy vm1") - self.check_output("rmmod {0}".format(host_driver))[1].splitlines() - self.check_output("modprobe {0}".format(host_driver))[ - 1].splitlines() else: - log.error("error : {0}".format(err)) + # Don't generalize too much Just support vld_id + vld_id = attr_name.get('vld_id', {}) + # for standalone context networks are dicts + iter1 = (n for n in self.networks.values() if n.get('vld_id') == vld_id) + network = next(iter1, None) + + if network is None: + return None + + result = { + # name is required + "name": network["name"], + "vld_id": network.get("vld_id"), + "segmentation_id": network.get("segmentation_id"), + "network_type": network.get("network_type"), + "physical_network": network.get("physical_network"), + } + return result + + def configure_nics_for_sriov(self): + vf_cmd = "ip link set {0} vf 0 mac {1}" + for key, ports in self.networks.items(): + vf_pci = [] + host_driver = ports.get('driver') + if host_driver not in self.drivers: + self.connection.execute("rmmod %svf" % host_driver) + self.drivers.append(host_driver) + + # enable VFs for given... + build_vfs = "echo 1 > /sys/bus/pci/devices/{0}/sriov_numvfs" + self.connection.execute(build_vfs.format(ports.get('phy_port'))) + + # configure VFs... + mac = StandaloneContextHelper.get_mac_address() + interface = ports.get('interface') + if interface is not None: + self.connection.execute(vf_cmd.format(interface, mac)) + + vf_pci = self.get_vf_data('vf_pci', ports.get('phy_port'), mac, interface) + ports.update({ + 'vf_pci': vf_pci, + 'mac': mac + }) + + LOG.info("Ports %s" % self.networks) + + def _enable_interfaces(self, index, idx, vfs, cfg): + vf = self.networks[vfs[0]] + vpci = PciAddress.parse_address(vf['vpci'].strip(), multi_line=True) + # Generate the vpci for the interfaces + slot = index + idx + 10 + vf['vpci'] = \ + "{}:{}:{:02x}.{}".format(vpci.domain, vpci.bus, slot, vpci.function) + Libvirt.add_sriov_interfaces( + vf['vpci'], vf['vf_pci']['vf_pci'], vf['mac'], str(cfg)) + self.connection.execute("ifconfig %s up" % vf['interface']) + + def setup_sriov_context(self): + nodes = [] + + # 1 : modprobe host_driver with num_vfs + self.configure_nics_for_sriov() + + for index, (key, vnf) in enumerate(OrderedDict(self.servers).items()): + cfg = '/tmp/vm_sriov_%s.xml' % str(index) + vm_name = "vm_%s" % str(index) + + # 1. Check and delete VM if already exists + Libvirt.check_if_vm_exists_and_delete(vm_name, self.connection) + + vcpu, mac = Libvirt.build_vm_xml(self.connection, self.vm_flavor, cfg, vm_name, index) + # 2: Cleanup already available VMs + for idx, (vkey, vfs) in enumerate(OrderedDict(vnf["network_ports"]).items()): + if vkey == "mgmt": + continue + self._enable_interfaces(index, idx, vfs, cfg) + + # copy xml to target... + self.connection.put(cfg, cfg) + + # FIXME: launch through libvirt + LOG.info("virsh create ...") + Libvirt.virsh_create_vm(self.connection, cfg) + + # 5: Tunning for better performace + Libvirt.pin_vcpu_for_perf(self.connection, vm_name, vcpu) + self.vm_names.append(vm_name) + + # build vnf node details + nodes.append(self.vnf_node.generate_vnf_instance(self.vm_flavor, + self.networks, + self.host_mgmt.get('ip'), + key, vnf, mac)) + + return nodes + + def get_vf_data(self, key, value, vfmac, pfif): + vf_data = { + "mac": vfmac, + "pf_if": pfif + } + vfs = StandaloneContextHelper.get_virtual_devices(self.connection, value) + for k, v in vfs.items(): + m = PciAddress.parse_address(k.strip(), multi_line=True) + m1 = PciAddress.parse_address(value.strip(), multi_line=True) + if m.bus == m1.bus: + vf_data.update({"vf_pci": str(v)}) + break + + return vf_data diff --git a/yardstick/network_services/utils.py b/yardstick/network_services/utils.py index d52e27c15..eac3c814f 100644 --- a/yardstick/network_services/utils.py +++ b/yardstick/network_services/utils.py @@ -16,6 +16,7 @@ from __future__ import absolute_import import logging import os +import re from oslo_config import cfg from oslo_config.cfg import NoSuchOptError @@ -38,6 +39,59 @@ OPTS = [ CONF.register_opts(OPTS, group="nsb") +HEXADECIMAL = "[0-9a-zA-Z]" + + +class PciAddress(object): + + PCI_PATTERN_STR = HEXADECIMAL.join([ + "(", + "{4}):(", # domain (4 bytes) + "{2}):(", # bus (2 bytes) + "{2}).(", # function (2 bytes) + ")", # slot (1 byte) + ]) + + PCI_PATTERN = re.compile(PCI_PATTERN_STR) + + @classmethod + def parse_address(cls, text, multi_line=False): + if multi_line: + text = text.replace(os.linesep, '') + match = cls.PCI_PATTERN.search(text) + return cls(match.group(0)) + + def __init__(self, address): + super(PciAddress, self).__init__() + match = self.PCI_PATTERN.match(address) + if not match: + raise ValueError('Invalid PCI address: {}'.format(address)) + self.address = address + self.match = match + + def __repr__(self): + return self.address + + @property + def domain(self): + return self.match.group(1) + + @property + def bus(self): + return self.match.group(2) + + @property + def slot(self): + return self.match.group(3) + + @property + def function(self): + return self.match.group(4) + + def values(self): + return [self.match.group(n) for n in range(1, 5)] + + def get_nsb_option(option, default=None): """return requested option for yardstick.conf""" @@ -55,6 +109,8 @@ def provision_tool(connection, tool_path, tool_file=None): :return - Tool path """ + if not tool_path: + tool_path = get_nsb_option('tool_path') if tool_file: tool_path = os.path.join(tool_path, tool_file) bin_path = get_nsb_option("bin_path") @@ -64,6 +120,7 @@ def provision_tool(connection, tool_path, tool_file=None): logging.warning("%s not found on %s, will try to copy from localhost", tool_path, connection.host) + bin_path = get_nsb_option("bin_path") connection.execute('mkdir -p "%s"' % bin_path) connection.put(tool_path, tool_path) return tool_path |