diff options
author | Deepak S <deepak.s@linux.intel.com> | 2016-12-30 09:23:49 -0800 |
---|---|---|
committer | Deepak S <deepak.s@linux.intel.com> | 2017-01-19 08:28:49 +0530 |
commit | 8b15550fd60efc464aa19589cc0ea638ded20f3d (patch) | |
tree | b2254ab2de360c9c4bfeca09a01bea27f64a7771 | |
parent | c1d6fd53c49a2cc5c0a7eab82e15dcf1a08f4e32 (diff) |
Adding ping based sample VNF appliance
This patch defines
- Generic VNF APIs to test Network service
--> instantiate
--> collect_kpi
--> run_traffic
--> listen_traffic
--> terminate
- vnf Descriptor to map the physical NFVi topology of the Test unit.
JIRA: YARDSTICK-491
Change-Id: I6b7e09972fc536977b65d8a19d635a220815e5f3
Signed-off-by: Deepak S <deepak.s@linux.intel.com>
4 files changed, 588 insertions, 0 deletions
diff --git a/tests/unit/network_services/traffic_profile/test_fixed.py b/tests/unit/network_services/traffic_profile/test_fixed.py new file mode 100644 index 000000000..f4500dd38 --- /dev/null +++ b/tests/unit/network_services/traffic_profile/test_fixed.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python + +# 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 unittest +import mock + +from yardstick.network_services.traffic_profile.base import TrafficProfile +from yardstick.network_services.traffic_profile.fixed import FixedProfile + + +class TestFixedProfile(unittest.TestCase): + TRAFFIC_PROFILE = { + "schema": "isb:traffic_profile:0.1", + "name": "fixed", + "description": "Fixed traffic profile to run UDP traffic", + "traffic_profile": { + "traffic_type": "FixedTraffic", + "frame_rate": 100, # pps + "flow_number": 10, + "frame_size": 64}} + + VNFD = {'vnfd:vnfd-catalog': + {'vnfd': + [{'short-name': 'VpeVnf', + 'vdu': + [{'routing_table': + [{'network': '152.16.100.20', + 'netmask': '255.255.255.0', + 'gateway': '152.16.100.20', + 'if': 'xe0'}, + {'network': '152.16.40.20', + 'netmask': '255.255.255.0', + 'gateway': '152.16.40.20', + 'if': 'xe1'}], + 'description': 'VPE approximation using DPDK', + 'name': 'vpevnf-baremetal', + 'nd_route_tbl': + [{'network': '0064:ff9b:0:0:0:0:9810:6414', + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:6414', + 'if': 'xe0'}, + {'network': '0064:ff9b:0:0:0:0:9810:2814', + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:2814', + 'if': 'xe1'}], + 'id': 'vpevnf-baremetal', + 'external-interface': + [{'virtual-interface': + {'dst_mac': '00:00:00:00:00:04', + 'vpci': '0000:05:00.0', + 'local_ip': '152.16.100.19', + 'type': 'PCI-PASSTHROUGH', + 'netmask': '255.255.255.0', + 'dpdk_port_num': '0', + 'bandwidth': '10 Gbps', + 'dst_ip': '152.16.100.20', + 'local_mac': '00:00:00:00:00:01'}, + 'vnfd-connection-point-ref': 'xe0', + 'name': 'xe0'}, + {'virtual-interface': + {'dst_mac': '00:00:00:00:00:03', + 'vpci': '0000:05:00.1', + 'local_ip': '152.16.40.19', + 'type': 'PCI-PASSTHROUGH', + 'netmask': '255.255.255.0', + 'dpdk_port_num': '1', + 'bandwidth': '10 Gbps', + 'dst_ip': '152.16.40.20', + 'local_mac': '00:00:00:00:00:02'}, + 'vnfd-connection-point-ref': 'xe1', + 'name': 'xe1'}]}], + 'description': 'Vpe approximation using DPDK', + 'mgmt-interface': + {'vdu-id': 'vpevnf-baremetal', + 'host': '1.1.1.1', + 'password': 'r00t', + 'user': 'root', + 'ip': '1.1.1.1'}, + 'benchmark': + {'kpi': ['packets_in', 'packets_fwd', 'packets_dropped']}, + 'connection-point': [{'type': 'VPORT', 'name': 'xe0'}, + {'type': 'VPORT', 'name': 'xe1'}], + 'id': 'VpeApproxVnf', 'name': 'VPEVnfSsh'}]}} + + def test___init__(self): + fixed_profile = \ + FixedProfile(TrafficProfile) + self.assertIsNotNone(fixed_profile) + + def test_execute(self): + traffic_generator = mock.Mock(autospec=TrafficProfile) + traffic_generator.my_ports = [0, 1] + traffic_generator.vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + traffic_generator.client = \ + mock.Mock(return_value=True) + fixed_profile = FixedProfile(self.TRAFFIC_PROFILE) + fixed_profile.params = self.TRAFFIC_PROFILE + fixed_profile.first_run = True + self.assertEqual(None, fixed_profile.execute(traffic_generator)) diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py new file mode 100644 index 000000000..4f0855543 --- /dev/null +++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python + +# 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 unittest +import mock +from multiprocessing import Queue + +from yardstick.network_services.vnf_generic.vnf.tg_ping import \ + PingParser, PingTrafficGen +from yardstick.network_services.traffic_profile.base import TrafficProfile + + +class TestPingParser(unittest.TestCase): + def test___init__(self): + q_out = Queue() + ping_parser = PingParser(q_out) + self.assertIsNotNone(ping_parser.queue) + + def test_clear(self): + sample_out = """ +64 bytes from 10.102.22.93: icmp_seq=3 ttl=64 time=0.296 ms + """ + q_out = Queue() + ping_parser = PingParser(q_out) + ping_parser.write(sample_out) + ping_parser.clear() + self.assertEqual(True, q_out.empty()) + + def test_close(self): + q_out = Queue() + ping_parser = PingParser(q_out) + self.assertIsNone(ping_parser.close()) + + def test_write(self): + sample_out = """ +64 bytes from 10.102.22.93: icmp_seq=3 ttl=64 time=0.296 ms + """ + q_out = Queue() + ping_parser = PingParser(q_out) + ping_parser.write(sample_out) + + self.assertEqual({"packets_received": 3.0, "rtt": 0.296}, q_out.get()) + + +class TestPingTrafficGen(unittest.TestCase): + VNFD = {'vnfd:vnfd-catalog': + {'vnfd': + [{'short-name': 'VpeVnf', + 'vdu': + [{'routing_table': + [{'network': '152.16.100.20', + 'netmask': '255.255.255.0', + 'gateway': '152.16.100.20', + 'if': 'xe0'}, + {'network': '152.16.40.20', + 'netmask': '255.255.255.0', + 'gateway': '152.16.40.20', + 'if': 'xe1'}], + 'description': 'VPE approximation using DPDK', + 'name': 'vpevnf-baremetal', + 'nd_route_tbl': + [{'network': '0064:ff9b:0:0:0:0:9810:6414', + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:6414', + 'if': 'xe0'}, + {'network': '0064:ff9b:0:0:0:0:9810:2814', + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:2814', + 'if': 'xe1'}], + 'id': 'vpevnf-baremetal', + 'external-interface': + [{'virtual-interface': + {'dst_mac': '00:00:00:00:00:04', + 'vpci': '0000:05:00.0', + 'local_ip': '152.16.100.19', + 'type': 'PCI-PASSTHROUGH', + 'netmask': '255.255.255.0', + 'dpdk_port_num': '0', + 'bandwidth': '10 Gbps', + 'driver': "i40e", + 'dst_ip': '152.16.100.20', + 'local_iface_name': 'xe0', + 'local_mac': '00:00:00:00:00:02'}, + 'vnfd-connection-point-ref': 'xe0', + 'name': 'xe0'}, + {'virtual-interface': + {'dst_mac': '00:00:00:00:00:03', + 'vpci': '0000:05:00.1', + 'local_ip': '152.16.40.19', + 'type': 'PCI-PASSTHROUGH', + 'driver': "i40e", + 'netmask': '255.255.255.0', + 'dpdk_port_num': '1', + 'bandwidth': '10 Gbps', + 'dst_ip': '152.16.40.20', + 'local_iface_name': 'xe1', + 'local_mac': '00:00:00:00:00:01'}, + 'vnfd-connection-point-ref': 'xe1', + 'name': 'xe1'}]}], + 'description': 'Vpe approximation using DPDK', + 'mgmt-interface': + {'vdu-id': 'vpevnf-baremetal', + 'host': '1.1.1.1', + 'password': 'r00t', + 'user': 'root', + 'ip': '1.1.1.1'}, + 'benchmark': + {'kpi': ['packets_in', 'packets_fwd', 'packets_dropped']}, + 'connection-point': [{'type': 'VPORT', 'name': 'xe0'}, + {'type': 'VPORT', 'name': 'xe1'}], + 'id': 'VpeApproxVnf', 'name': 'VPEVnfSsh'}]}} + + TRAFFIC_PROFILE = { + "schema": "isb:traffic_profile:0.1", + "name": "fixed", + "description": "Fixed traffic profile to run UDP traffic", + "traffic_profile": { + "traffic_type": "FixedTraffic", + "frame_rate": 100, # pps + "flow_number": 10, + "frame_size": 64}} + + def test___init__(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ping_traffic_gen = PingTrafficGen(vnfd) + self.assertEqual(ping_traffic_gen._queue, None) + + def test_collect_kpi(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + ping_traffic_gen = PingTrafficGen(vnfd) + ping_traffic_gen._queue = Queue() + ping_traffic_gen._queue.put({}) + ping_traffic_gen.collect_kpi() + self.assertEqual({}, ping_traffic_gen._result) + + def test_instantiate(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ping_traffic_gen = PingTrafficGen(vnfd) + self.assertEqual(None, ping_traffic_gen.instantiate({}, {})) + + def test_listen_traffic(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ping_traffic_gen = PingTrafficGen(vnfd) + self.assertEqual(None, ping_traffic_gen.listen_traffic({})) + + def test_run_traffic(self): + mock_traffic_profile = mock.Mock(autospec=TrafficProfile) + mock_traffic_profile.get_traffic_definition.return_value = "64" + mock_traffic_profile.params = self.TRAFFIC_PROFILE + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + self.sut = PingTrafficGen(vnfd) + self.sut.connection = mock.Mock() + self.sut.connection.run = mock.Mock() + self.sut._traffic_runner = mock.Mock(return_value=0) + self.assertEqual( + False, self.sut.run_traffic(mock_traffic_profile)) + + def test_run_traffic_process(self): + mock_traffic_profile = mock.Mock(autospec=TrafficProfile) + mock_traffic_profile.get_traffic_definition.return_value = "64" + mock_traffic_profile.params = self.TRAFFIC_PROFILE + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + self.sut = PingTrafficGen(vnfd) + self.sut.connection = mock.Mock() + self.sut.connection.run = mock.Mock() + q = Queue() + self.sut._traffic_runner(mock_traffic_profile, q) + self.sut.connection.run.assert_called_with( + "ping -s 64 152.16.100.20", + stdout=q, keep_stdin_open=True, pty=True) + + def test_scale(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + flavor = "" + ping_traffic_gen = PingTrafficGen(vnfd) + self.assertRaises(NotImplementedError, ping_traffic_gen.scale, flavor) + + def test_terminate(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ping_traffic_gen = PingTrafficGen(vnfd) + self.assertEqual(None, ping_traffic_gen.terminate()) diff --git a/yardstick/network_services/traffic_profile/fixed.py b/yardstick/network_services/traffic_profile/fixed.py new file mode 100644 index 000000000..a456c2bd7 --- /dev/null +++ b/yardstick/network_services/traffic_profile/fixed.py @@ -0,0 +1,60 @@ +# 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. +""" Fixed traffic profile definitions """ + +from __future__ import absolute_import + +from yardstick.network_services.traffic_profile.base import TrafficProfile +from stl.trex_stl_lib.trex_stl_streams import STLTXCont +from stl.trex_stl_lib.trex_stl_client import STLStream +from stl.trex_stl_lib.trex_stl_packet_builder_scapy import STLPktBuilder +from stl.trex_stl_lib import api as Pkt + + +class FixedProfile(TrafficProfile): + """ + This profile adds a single stream at the beginning of the traffic session + """ + def __init__(self, tp_config): + super(FixedProfile, self).__init__(tp_config) + self.first_run = True + + def execute(self, traffic_generator): + if self.first_run: + for index, ports in enumerate(traffic_generator.my_ports): + ext_intf = \ + traffic_generator.vnfd["vdu"][0]["external-interface"] + virtual_interface = ext_intf[index]["virtual-interface"] + src_ip = virtual_interface["local_ip"] + dst_ip = virtual_interface["dst_ip"] + + traffic_generator.client.add_streams( + self._create_stream(src_ip, dst_ip), + ports=[ports]) + + traffic_generator.client.start(ports=traffic_generator.my_ports) + self.first_run = False + + def _create_stream(self, src_ip, dst_ip): + base_frame = \ + Pkt.Ether() / Pkt.IP(src=src_ip, dst=dst_ip) / Pkt.UDP(dport=12, + sport=1025) + + frame_size = self.params["traffic_profile"]["frame_size"] + pad_size = max(0, frame_size - len(base_frame)) + frame = base_frame / ("x" * max(0, pad_size)) + + frame_rate = self.params["traffic_profile"]["frame_rate"] + return STLStream(packet=STLPktBuilder(pkt=frame), + mode=STLTXCont(pps=frame_rate)) diff --git a/yardstick/network_services/vnf_generic/vnf/tg_ping.py b/yardstick/network_services/vnf_generic/vnf/tg_ping.py new file mode 100644 index 000000000..2844a5c01 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/tg_ping.py @@ -0,0 +1,167 @@ +# 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. +""" PING acts as traffic generation and vnf definitions based on IETS Spec """ + +from __future__ import absolute_import +from __future__ import print_function +import logging +import multiprocessing +import re +import time +import os + +from yardstick import ssh +from yardstick.network_services.vnf_generic.vnf.base import GenericTrafficGen +from yardstick.network_services.utils import provision_tool + +LOG = logging.getLogger(__name__) + + +class PingParser(object): + """ Class providing file-like API for talking with SSH connection """ + + def __init__(self, q_out): + self.queue = q_out + self.closed = False + + def write(self, chunk): + """ 64 bytes from 10.102.22.93: icmp_seq=1 ttl=64 time=0.296 ms """ + match = re.search(r"icmp_seq=(\d+).*time=([0-9.]+)", chunk) + LOG.debug("Parser called on %s", chunk) + if match: + # IMPORTANT: in order for the data to be properly taken + # in by InfluxDB, it needs to be converted to numeric types + self.queue.put({"packets_received": float(match.group(1)), + "rtt": float(match.group(2))}) + + def close(self): + ''' close the ssh connection ''' + pass + + def clear(self): + ''' clear queue till Empty ''' + while self.queue.qsize() > 0: + self.queue.get() + + +class PingTrafficGen(GenericTrafficGen): + """ + This traffic generator can ping a single IP with pingsize + and target given in traffic profile + """ + + def __init__(self, vnfd): + super(PingTrafficGen, self).__init__(vnfd) + self._result = {} + self._parser = None + self._queue = None + self._traffic_process = None + + mgmt_interface = vnfd["mgmt-interface"] + ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT) + LOG.debug("Connecting to %s", mgmt_interface["ip"]) + + self.connection = ssh.SSH(mgmt_interface["user"], mgmt_interface["ip"], + password=mgmt_interface["password"], + port=ssh_port) + self.connection.wait() + + def _bind_device_kernel(self, connection): + dpdk_nic_bind = \ + provision_tool(self.connection, + os.path.join(self.bin_path, "dpdk_nic_bind.py")) + + drivers = {intf["virtual-interface"]["vpci"]: + intf["virtual-interface"]["driver"] + for intf in self.vnfd["vdu"][0]["external-interface"]} + + commands = \ + ['"{0}" --force -b "{1}" "{2}"'.format(dpdk_nic_bind, value, key) + for key, value in drivers.items()] + for command in commands: + connection.execute(command) + + for index, out in enumerate(self.vnfd["vdu"][0]["external-interface"]): + vpci = out["virtual-interface"]["vpci"] + net = "find /sys/class/net -lname '*{}*' -printf '%f'".format(vpci) + out = connection.execute(net)[1] + ifname = out.split('/')[-1].strip('\n') + self.vnfd["vdu"][0]["external-interface"][index][ + "virtual-interface"]["local_iface_name"] = ifname + + def scale(self, flavor=""): + ''' scale vnfbased on flavor input ''' + super(PingTrafficGen, self).scale(flavor) + + def instantiate(self, scenario_cfg, context_cfg): + self._result = {"packets_received": 0, "rtt": 0} + self._bind_device_kernel(self.connection) + + def run_traffic(self, traffic_profile): + self._queue = multiprocessing.Queue() + self._parser = PingParser(self._queue) + self._traffic_process = \ + multiprocessing.Process(target=self._traffic_runner, + args=(traffic_profile, self._parser)) + self._traffic_process.start() + # Wait for traffic process to start + time.sleep(4) + return self._traffic_process.is_alive() + + def listen_traffic(self, traffic_profile): + """ Not needed for ping + + :param traffic_profile: + :return: + """ + pass + + def _traffic_runner(self, traffic_profile, filewrapper): + + mgmt_interface = self.vnfd["mgmt-interface"] + ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT) + self.connection = ssh.SSH(mgmt_interface["user"], mgmt_interface["ip"], + password=mgmt_interface["password"], + port=ssh_port) + self.connection.wait() + external_interface = self.vnfd["vdu"][0]["external-interface"] + virtual_interface = external_interface[0]["virtual-interface"] + target_ip = virtual_interface["dst_ip"].split('/')[0] + local_ip = virtual_interface["local_ip"].split('/')[0] + local_if_name = \ + virtual_interface["local_iface_name"].split('/')[0] + packet_size = traffic_profile.params["traffic_profile"]["frame_size"] + + run_cmd = [] + + run_cmd.append("ip addr flush %s" % local_if_name) + run_cmd.append("ip addr add %s/24 dev %s" % (local_ip, local_if_name)) + run_cmd.append("ip link set %s up" % local_if_name) + + for cmd in run_cmd: + self.connection.execute(cmd) + + ping_cmd = ("ping -s %s %s" % (packet_size, target_ip)) + self.connection.run(ping_cmd, stdout=filewrapper, + keep_stdin_open=True, pty=True) + + def collect_kpi(self): + if not self._queue.empty(): + kpi = self._queue.get() + self._result.update(kpi) + return self._result + + def terminate(self): + if self._traffic_process is not None: + self._traffic_process.terminate() |