HA testcase containerized Compass support
[yardstick.git] / yardstick / benchmark / scenarios / networking / vnf_generic.py
index 447c550..594edea 100644 (file)
 
 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
@@ -60,13 +68,9 @@ class SshManager(object):
         returns -> ssh connection ready to be used
         """
         try:
-            ssh_port = self.node.get("ssh_port", ssh.DEFAULT_PORT)
-            self.conn = ssh.SSH(user=self.node["user"],
-                                host=self.node["ip"],
-                                password=self.node["password"],
-                                port=ssh_port)
+            self.conn = ssh.SSH.from_node(self.node)
             self.conn.wait()
-        except (SSHError) as error:
+        except SSHError as error:
             LOG.info("connect failed to %s, due to %s", self.node["ip"], error)
         # self.conn defaults to None
         return self.conn
@@ -76,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  """
@@ -88,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
@@ -118,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):
@@ -127,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)
@@ -197,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.
@@ -212,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
@@ -244,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
@@ -268,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)