aboutsummaryrefslogtreecommitdiffstats
path: root/yardstick/benchmark/scenarios/networking
diff options
context:
space:
mode:
authorEdward MacGillivray <edward.s.macgillivray@intel.com>2017-06-12 11:06:45 -0700
committerRoss Brattain <ross.b.brattain@intel.com>2017-06-20 13:19:25 +0000
commit653902770572c780777d1dc7a371794b670585b1 (patch)
tree6fcb520a711836eb10c2dd4759666d3b39858150 /yardstick/benchmark/scenarios/networking
parent37921fcd232cd2fbba9f45ef9fa5d8c912f54af6 (diff)
Acquire NSB specific data from Heat.
First we add mac_address, subnet_cidr to Heat template outputs Then we convert those into a form for NSB and add vld_id. NSB also requires PCI Bus ID, kernel driver and dpdk_port_num. We get this by ssh-ing into instance and dumping sysfs We also need to fix allow for ssh key auth, and implement relative path file loading so NSB can find all its YAML files JIRA: YARDSTICK-580 Change history: don't hide heat create tracebacks we need tracebacks for debug vnf_generic: add task_path to scenario so we can load relative paths for vnf_generic we want to be able to load yaml relative to the task path For example: traffic_profile: ../../traffic_profiles/fixed.yaml topology: ping_tg_topology.yaml # TODO: look in relative path where the tc.yaml is found These need to be relative to samples/vnf_samples/nsut/ping/tc_ping_heat_context.yaml Add a scenario["task_path"] entry heat: log actual exception vnf_generic: replace list with set and iterate over values() some general refactors to remove redundact lookups and type conversions heat: provide mac_address, device_id and network_id from outputs We may need more information to dynamically determine test topology. Towards this end return more info in the heat template. We can return mac_address, device_id and network_id. Once we have this info we can add it to the context_cfg as an interfaces dict. add sample vnf ping multi-network test this test requires 3 network, one for mgmt and the other two for NSB traffic tests We have to make sure we don't use DPDK on mgmt interface because DPDK unbinds the driver heat: convert networks to OrderedDict so we can lookups networks as well as iterate over them in consisitent order heat: and vld_id to networks for vnf_generic vnf_generic uses vld_id Virtual Link Descriptor ID to identify interfaces Add the key to the networks dict and store in Networks object implement relative path file loading in vnf_generic in multiple places we need to load a file relative to the task path, so add open_relative_file_path and modify load_vnf_model to include the scenario_cfg parameter so we have access to task_path DRAFT: heat timeout support Heat stack in CI job failed due to some Nova issue. But then apparently yardstick kept running and took 180mins to timeout https://build.opnfv.org/ci/view/bottlenecks/job/bottlenecks-compass-posca_stress_ping-baremetal-daily-master/16/console We can add a Heat create timeout and fail faster if there is an error. The question is how long should we wait for a Heat stack to deploy. We can set a default and allow override in the heat context config, if users make complicated stacks heat: get netmask and gateway from heat outputs we have do some tricky business with finding the subnet cidr and converting it into netmask vnf_generic: get vpci, driver and dpdk_port_num use a big old find command to dump all the sysfs netdev info nicely. This was re-used from autotest FCoE tests. r"""find /sys/devices/pci* -type d -name net -exec sh -c '{ grep -sH ^ \ +$1/ifindex $1/address $1/operstate $1/device/vendor $1/device/device \ +$1/device/subsystem_vendor $1/device/subsystem_device ; \ +printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ +' sh \{\}/* \; This finds all PCI devices that are network devices, then dumps all the relevant info using /bin/sh. Then we parse this into a 'netdevs' dict inside the node_dict and also convert into VNF fields we need. vnf_generic: set node name for kpis node is a dict, so we have to use node_name vnfdgen: we CANNOT use TaskTemplate.render because it does not allow for missing variables, we need to allow password for key_filename to be undefined remove default ssh password hack, once rendering is fixed add new example tc_external_ping_heat_context Change-Id: If1fe0c1a2ab0a5be17e40790a66f28f706fa44d6 Signed-off-by: Ross Brattain <ross.b.brattain@intel.com> Signed-off-by: Edward MacGillivray <edward.s.macgillivray@intel.com>
Diffstat (limited to 'yardstick/benchmark/scenarios/networking')
-rw-r--r--yardstick/benchmark/scenarios/networking/vnf_generic.py130
1 files changed, 108 insertions, 22 deletions
diff --git a/yardstick/benchmark/scenarios/networking/vnf_generic.py b/yardstick/benchmark/scenarios/networking/vnf_generic.py
index be179631e..594edeaa8 100644
--- a/yardstick/benchmark/scenarios/networking/vnf_generic.py
+++ b/yardstick/benchmark/scenarios/networking/vnf_generic.py
@@ -15,6 +15,14 @@
from __future__ import absolute_import
import logging
+
+import errno
+import os
+
+import re
+from operator import itemgetter
+from collections import defaultdict
+
import yaml
from yardstick.benchmark.scenarios import base
@@ -72,6 +80,15 @@ class SshManager(object):
self.conn.close()
+def open_relative_file(path, task_path):
+ try:
+ return open(path)
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ return open(os.path.join(task_path, path))
+ raise
+
+
class NetworkServiceTestCase(base.Scenario):
"""Class handles Generic framework to do pre-deployment VNF &
Network service testing """
@@ -84,8 +101,11 @@ class NetworkServiceTestCase(base.Scenario):
self.context_cfg = context_cfg
# fixme: create schema to validate all fields have been provided
- with open(scenario_cfg["topology"]) as stream:
- self.topology = yaml.load(stream)["nsd:nsd-catalog"]["nsd"][0]
+ with open_relative_file(scenario_cfg["topology"],
+ scenario_cfg['task_path']) as stream:
+ topology_yaml = yaml.load(stream)
+
+ self.topology = topology_yaml["nsd:nsd-catalog"]["nsd"][0]
self.vnfs = []
self.collector = None
self.traffic_profile = None
@@ -114,7 +134,8 @@ class NetworkServiceTestCase(base.Scenario):
private = {}
public = {}
try:
- with open(scenario_cfg["traffic_profile"]) as infile:
+ with open_relative_file(scenario_cfg["traffic_profile"],
+ scenario_cfg["task_path"]) as infile:
traffic_profile_tpl = infile.read()
except (KeyError, IOError, OSError):
@@ -123,8 +144,6 @@ class NetworkServiceTestCase(base.Scenario):
return [traffic_profile_tpl, private, public]
def _fill_traffic_profile(self, scenario_cfg, context_cfg):
- traffic_profile = {}
-
flow = self._get_traffic_flow(scenario_cfg)
imix = self._get_traffic_imix(scenario_cfg)
@@ -193,6 +212,26 @@ class NetworkServiceTestCase(base.Scenario):
list_idx = self._find_list_index_from_vnf_idx(topology, vnf_idx)
nodes[node].update(topology["constituent-vnfd"][list_idx])
+ @staticmethod
+ def _sort_dpdk_port_num(netdevs):
+ # dpdk_port_num is PCI BUS ID ordering, lowest first
+ s = sorted(netdevs.values(), key=itemgetter('pci_bus_id'))
+ for dpdk_port_num, netdev in enumerate(s, 1):
+ netdev['dpdk_port_num'] = dpdk_port_num
+
+ @classmethod
+ def _probe_missing_values(cls, netdevs, network, missing):
+ mac = network['local_mac']
+ for netdev in netdevs.values():
+ if netdev['address'].lower() == mac.lower():
+ network['driver'] = netdev['driver']
+ network['vpci'] = netdev['pci_bus_id']
+ network['dpdk_port_num'] = netdev['dpdk_port_num']
+ network['ifindex'] = netdev['ifindex']
+
+ TOPOLOGY_REQUIRED_KEYS = frozenset({
+ "vpci", "local_ip", "netmask", "local_mac", "driver", "dpdk_port_num"})
+
def map_topology_to_infrastructure(self, context_cfg, topology):
""" This method should verify if the available resources defined in pod.yaml
match the topology.yaml file.
@@ -208,21 +247,66 @@ class NetworkServiceTestCase(base.Scenario):
exit_status = conn.execute(cmd)[0]
if exit_status != 0:
raise IncorrectSetup("Node's %s lacks ip tool." % node)
-
- for interface in node_dict["interfaces"]:
- network = node_dict["interfaces"][interface]
- keys = ["vpci", "local_ip", "netmask",
- "local_mac", "driver", "dpdk_port_num"]
- missing = set(keys).difference(network)
+ exit_status, stdout, _ = conn.execute(
+ self.FIND_NETDEVICE_STRING)
+ if exit_status != 0:
+ raise IncorrectSetup(
+ "Cannot find netdev info in sysfs" % node)
+ netdevs = node_dict['netdevs'] = self.parse_netdev_info(
+ stdout)
+ self._sort_dpdk_port_num(netdevs)
+
+ for network in node_dict["interfaces"].values():
+ missing = self.TOPOLOGY_REQUIRED_KEYS.difference(network)
if missing:
- raise IncorrectConfig("Require interface fields '%s' "
- "not found, topology file "
- "corrupted" % ', '.join(missing))
+ try:
+ self._probe_missing_values(netdevs, network,
+ missing)
+ except KeyError:
+ pass
+ else:
+ missing = self.TOPOLOGY_REQUIRED_KEYS.difference(
+ network)
+ if missing:
+ raise IncorrectConfig(
+ "Require interface fields '%s' "
+ "not found, topology file "
+ "corrupted" % ', '.join(missing))
# 3. Use topology file to find connections & resolve dest address
self._resolve_topology(context_cfg, topology)
self._update_context_with_topology(context_cfg, topology)
+ FIND_NETDEVICE_STRING = r"""find /sys/devices/pci* -type d -name net -exec sh -c '{ grep -sH ^ \
+$1/ifindex $1/address $1/operstate $1/device/vendor $1/device/device \
+$1/device/subsystem_vendor $1/device/subsystem_device ; \
+printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \
+' sh \{\}/* \;
+"""
+ BASE_ADAPTER_RE = re.compile(
+ '^/sys/devices/(.*)/net/([^/]*)/([^:]*):(.*)$', re.M)
+
+ @classmethod
+ def parse_netdev_info(cls, stdout):
+ network_devices = defaultdict(dict)
+ matches = cls.BASE_ADAPTER_RE.findall(stdout)
+ for bus_path, interface_name, name, value in matches:
+ dirname, bus_id = os.path.split(bus_path)
+ if 'virtio' in bus_id:
+ # for some stupid reason VMs include virtio1/
+ # in PCI device path
+ bus_id = os.path.basename(dirname)
+ # remove extra 'device/' from 'device/vendor,
+ # device/subsystem_vendor', etc.
+ if 'device/' in name:
+ name = name.split('/')[1]
+ network_devices[interface_name][name] = value
+ network_devices[interface_name][
+ 'interface_name'] = interface_name
+ network_devices[interface_name]['pci_bus_id'] = bus_id
+ # convert back to regular dict
+ return dict(network_devices)
+
@classmethod
def get_vnf_impl(cls, vnf_model):
""" Find the implementing class from vnf_model["vnf"]["name"] field
@@ -240,21 +324,24 @@ class NetworkServiceTestCase(base.Scenario):
except StopIteration:
raise IncorrectConfig("No implementation for %s", expected_name)
- def load_vnf_models(self, context_cfg):
+ def load_vnf_models(self, scenario_cfg, context_cfg):
""" Create VNF objects based on YAML descriptors
+ :param scenario_cfg:
+ :type scenario_cfg:
:param context_cfg:
:return:
"""
vnfs = []
- for node in context_cfg["nodes"]:
- LOG.debug(context_cfg["nodes"][node])
- with open(context_cfg["nodes"][node]["VNF model"]) as stream:
+ for node_name, node in context_cfg["nodes"].items():
+ LOG.debug(node)
+ with open_relative_file(node["VNF model"],
+ scenario_cfg['task_path']) as stream:
vnf_model = stream.read()
- vnfd = vnfdgen.generate_vnfd(vnf_model, context_cfg["nodes"][node])
+ vnfd = vnfdgen.generate_vnfd(vnf_model, node)
vnf_impl = self.get_vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0])
vnf_instance = vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0])
- vnf_instance.name = node
+ vnf_instance.name = node_name
vnfs.append(vnf_instance)
return vnfs
@@ -264,11 +351,10 @@ class NetworkServiceTestCase(base.Scenario):
:return:
"""
-
# 1. Verify if infrastructure mapping can meet topology
self.map_topology_to_infrastructure(self.context_cfg, self.topology)
# 1a. Load VNF models
- self.vnfs = self.load_vnf_models(self.context_cfg)
+ self.vnfs = self.load_vnf_models(self.scenario_cfg, self.context_cfg)
# 1b. Fill traffic profile with information from topology
self.traffic_profile = self._fill_traffic_profile(self.scenario_cfg,
self.context_cfg)