import errno
import ipaddress
+
+import copy
import os
import sys
import re
from itertools import chain
import six
+import yaml
from collections import defaultdict
from yardstick.benchmark.scenarios import base
+from yardstick.common.constants import LOG_DIR
+from yardstick.common.process import terminate_children
from yardstick.common.utils import import_modules_from_package, itersubclasses
from yardstick.common.yaml_loader import yaml_load
from yardstick.network_services.collector.subscriber import Collector
def _get_ip_flow_range(self, ip_start_range):
+ # IP range is specified as 'x.x.x.x-y.y.y.y'
+ if isinstance(ip_start_range, six.string_types):
+ return ip_start_range
+
node_name, range_or_interface = next(iter(ip_start_range.items()), (None, '0.0.0.0'))
- if node_name is not None:
+ if node_name is None:
+ # we are manually specifying the range
+ ip_addr_range = range_or_interface
+ else:
node = self.context_cfg["nodes"].get(node_name, {})
try:
# the ip_range is the interface name
LOG.warning("Only single IP in range %s", ipaddr)
# fall back to single IP range
ip_addr_range = ip
- else:
- # we are manually specifying the range
- ip_addr_range = range_or_interface
return ip_addr_range
def _get_traffic_flow(self):
flow = {}
try:
# TODO: should be .0 or .1 so we can use list
- # but this also roughly matches private_0, public_0
+ # but this also roughly matches uplink_0, downlink_0
fflow = self.scenario_cfg["options"]["flow"]
for index, src in enumerate(fflow.get("src_ip", [])):
flow["src_ip_{}".format(index)] = self._get_ip_flow_range(src)
for index, publicip in enumerate(fflow.get("public_ip", [])):
flow["public_ip_{}".format(index)] = publicip
+ for index, src_port in enumerate(fflow.get("src_port", [])):
+ flow["src_port_{}".format(index)] = src_port
+
+ for index, dst_port in enumerate(fflow.get("dst_port", [])):
+ flow["dst_port_{}".format(index)] = dst_port
+
flow["count"] = fflow["count"]
except KeyError:
flow = {}
traffic_map_data = {
'flow': self._get_traffic_flow(),
'imix': self._get_traffic_imix(),
- 'private': {},
- 'public': {},
+ TrafficProfile.UPLINK: {},
+ TrafficProfile.DOWNLINK: {},
}
traffic_vnfd = vnfdgen.generate_vnfd(traffic_mapping, traffic_map_data)
# check for xe0, xe1
intf = nodes[name]["interfaces"][if_name]
except KeyError:
- # if not xe0, then maybe vld_id, private_0, public_0
+ # if not xe0, then maybe vld_id, uplink_0, downlink_0
# pop it and re-insert with the correct name from topology
intf = nodes[name]["interfaces"].pop(vld_id)
nodes[name]["interfaces"][if_name] = intf
'ifindex': netdev['ifindex'],
})
+ def _generate_pod_yaml(self):
+ context_yaml = os.path.join(LOG_DIR, "pod-{}.yaml".format(self.scenario_cfg['task_id']))
+ # convert OrderedDict to a list
+ # pod.yaml nodes is a list
+ nodes = [self._serialize_node(node) for node in self.context_cfg["nodes"].values()]
+ pod_dict = {
+ "nodes": nodes,
+ "networks": self.context_cfg["networks"]
+ }
+ with open(context_yaml, "w") as context_out:
+ yaml.safe_dump(pod_dict, context_out, default_flow_style=False,
+ explicit_start=True)
+
+ @staticmethod
+ def _serialize_node(node):
+ new_node = copy.deepcopy(node)
+ # name field is required
+ # remove context suffix
+ new_node["name"] = node['name'].split('.')[0]
+ try:
+ new_node["pkey"] = ssh.convert_key_to_str(node["pkey"])
+ except KeyError:
+ pass
+ return new_node
+
TOPOLOGY_REQUIRED_KEYS = frozenset({
"vpci", "local_ip", "netmask", "local_mac", "driver"})
"Require interface fields '%s' not found, topology file "
"corrupted" % ', '.join(missing))
+ # we have to generate pod.yaml here so we have vpci and driver
+ self._generate_pod_yaml()
# 3. Use topology file to find connections & resolve dest address
self._resolve_topology()
self._update_context_with_topology()
# we assume OrderedDict for consistenct in instantiation
for node_name, node in context_cfg["nodes"].items():
LOG.debug(node)
- file_name = node["VNF model"]
+ try:
+ file_name = node["VNF model"]
+ except KeyError:
+ LOG.debug("no model for %s, skipping", node_name)
+ continue
file_path = scenario_cfg['task_path']
with open_relative_file(file_name, file_path) as stream:
vnf_model = stream.read()
vnf.instantiate(self.scenario_cfg, self.context_cfg)
LOG.info("Waiting for %s to instantiate", vnf.name)
vnf.wait_for_instantiate()
- except RuntimeError:
+ except:
+ LOG.exception("")
for vnf in self.vnfs:
vnf.terminate()
raise
traffic_gen.listen_traffic(self.traffic_profile)
# register collector with yardstick for KPI collection.
- self.collector = Collector(self.vnfs, self.traffic_profile)
+ self.collector = Collector(self.vnfs, self.context_cfg["nodes"], self.traffic_profile)
self.collector.start()
# Start the actual traffic
:return: None
"""
- for vnf in self.vnfs:
- # Result example:
- # {"VNF1: { "tput" : [1000, 999] }, "VNF2": { "latency": 100 }}
- LOG.debug("collect KPI for %s", vnf.name)
- result.update(self.collector.get_kpi(vnf))
+ # this is the only method that is check from the runner
+ # so if we have any fatal error it must be raised via these methods
+ # otherwise we will not terminate
+
+ result.update(self.collector.get_kpi())
def teardown(self):
""" Stop the collector and terminate VNF & TG instance
:return
"""
- self.collector.stop()
- for vnf in self.vnfs:
- LOG.info("Stopping %s", vnf.name)
- vnf.terminate()
+ try:
+ try:
+ self.collector.stop()
+ for vnf in self.vnfs:
+ LOG.info("Stopping %s", vnf.name)
+ vnf.terminate()
+ LOG.debug("all VNFs terminated: %s", ", ".join(vnf.name for vnf in self.vnfs))
+ finally:
+ terminate_children()
+ except Exception:
+ # catch any exception in teardown and convert to simple exception
+ # never pass exceptions back to multiprocessing, because some exceptions can
+ # be unpicklable
+ # https://bugs.python.org/issue9400
+ LOG.exception("")
+ raise RuntimeError("Error in teardown")