1 # Copyright (c) 2016-2017 Intel Corporation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """ Trex acts as traffic generation and vnf definitions based on IETS Spec """
16 from __future__ import absolute_import
17 from __future__ import print_function
18 import multiprocessing
24 from yardstick import ssh
25 from yardstick.network_services.vnf_generic.vnf.base import GenericTrafficGen
26 from yardstick.network_services.utils import get_nsb_option
27 from yardstick.network_services.utils import provision_tool
28 from stl.trex_stl_lib.trex_stl_client import STLClient
29 from stl.trex_stl_lib.trex_stl_client import LoggerApi
30 from stl.trex_stl_lib.trex_stl_exceptions import STLError
32 LOG = logging.getLogger(__name__)
35 TREX_ASYNC_PORT = 4501
38 class TrexTrafficGen(GenericTrafficGen):
40 This class handles mapping traffic profile and generating
41 traffic for given testcase
44 def __init__(self, vnfd):
45 super(TrexTrafficGen, self).__init__(vnfd)
47 self._queue = multiprocessing.Queue()
48 self._terminated = multiprocessing.Value('i', 0)
49 self._traffic_process = None
50 self._vpci_ascending = None
53 self.client_started = multiprocessing.Value('i', 0)
55 mgmt_interface = self.vnfd["mgmt-interface"]
56 ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT)
57 self.connection = ssh.SSH(mgmt_interface["user"],
59 password=mgmt_interface["password"],
61 self.connection.wait()
64 def _split_mac_address_into_list(cls, mac):
65 octets = mac.split(':')
66 for i, elem in enumerate(octets):
67 octets[i] = "0x" + str(elem)
70 def _generate_trex_cfg(self, vnfd):
73 :param vnfd: vnfd.yaml
74 :return: trex_cfg.yaml file
83 trex_cfg["port_limit"] = len(vnfd["vdu"][0]["external-interface"])
84 trex_cfg["version"] = '2'
90 for interface in range(len(vnfd["vdu"][0]["external-interface"])):
91 ext_intrf = vnfd["vdu"][0]["external-interface"]
92 virtual_interface = ext_intrf[interface]["virtual-interface"]
93 vpci.append(virtual_interface["vpci"])
95 port["src_mac"] = self._split_mac_address_into_list(
96 virtual_interface["local_mac"])
97 port["dest_mac"] = self._split_mac_address_into_list(
98 virtual_interface["dst_mac"])
100 trex_cfg["port_info"].append(port.copy())
102 trex_cfg["interfaces"] = vpci
103 cfg_file.append(trex_cfg)
105 with open('/tmp/trex_cfg.yaml', 'w') as outfile:
106 outfile.write(yaml.safe_dump(cfg_file, default_flow_style=False))
107 self.connection.put('/tmp/trex_cfg.yaml', '/etc')
109 self._vpci_ascending = sorted(vpci)
112 def __setup_hugepages(cls, connection):
115 "awk '/Hugepagesize/ { print $2$3 }' < /proc/meminfo")[1]
116 hugepages = hugepages.rstrip()
119 '/sys/kernel/mm/hugepages/hugepages-%s/nr_hugepages' % hugepages
120 connection.execute("awk -F: '{ print $1 }' < %s" % memory_path)
122 pages = 16384 if hugepages.rstrip() == "2048kB" else 16
123 connection.execute("echo %s > %s" % (pages, memory_path))
125 def setup_vnf_environment(self, connection):
126 ''' setup dpdk environment needed for vnf to run '''
128 self.__setup_hugepages(connection)
129 connection.execute("modprobe uio && modprobe igb_uio")
131 exit_status = connection.execute("lsmod | grep -i igb_uio")[0]
135 dpdk = os.path.join(self.bin_path, "dpdk-16.07")
137 provision_tool(self.connection,
138 os.path.join(self.bin_path, "nsb_setup.sh"))
139 status = connection.execute("ls {} >/dev/null 2>&1".format(dpdk))[0]
141 connection.execute("bash %s dpdk >/dev/null 2>&1" % dpdk_setup)
143 def scale(self, flavor=""):
144 ''' scale vnfbased on flavor input '''
145 super(TrexTrafficGen, self).scale(flavor)
147 def instantiate(self, scenario_cfg, context_cfg):
148 self._generate_trex_cfg(self.vnfd)
149 self.setup_vnf_environment(self.connection)
151 trex = os.path.join(self.bin_path, "trex")
153 self.connection.execute("ls {} >/dev/null 2>&1".format(trex))[0]
155 LOG.info("Copying trex to destination...")
156 self.connection.put("/root/.bash_profile", "/root/.bash_profile")
157 self.connection.put(trex, trex, True)
158 ko_src = os.path.join(trex, "scripts/ko/src/")
159 self.connection.execute("cd %s && make && make install" % ko_src)
161 LOG.info("Starting TRex server...")
163 multiprocessing.Process(target=self._start_server)
166 if not _tg_process.is_alive():
167 raise RuntimeError("Traffic Generator process died.")
168 LOG.info("Waiting for TG Server to start.. ")
171 self.connection.execute("lsof -i:%s" % TREX_SYNC_PORT)[0]
173 LOG.info("TG server is up and running.")
174 return _tg_process.exitcode
176 def listen_traffic(self, traffic_profile):
179 def _get_logical_if_name(self, vpci):
180 ext_intf = self.vnfd["vdu"][0]["external-interface"]
181 for interface in range(len(self.vnfd["vdu"][0]["external-interface"])):
182 virtual_intf = ext_intf[interface]["virtual-interface"]
183 if virtual_intf["vpci"] == vpci:
184 return ext_intf[interface]["name"]
186 def run_traffic(self, traffic_profile):
187 self._traffic_process = \
188 multiprocessing.Process(target=self._traffic_runner,
189 args=(traffic_profile, self._queue,
192 self._traffic_process.start()
193 # Wait for traffic process to start
194 while self.client_started.value == 0:
197 return self._traffic_process.is_alive()
199 def _start_server(self):
200 mgmt_interface = self.vnfd["mgmt-interface"]
201 ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT)
202 _server = ssh.SSH(mgmt_interface["user"], mgmt_interface["ip"],
203 password=mgmt_interface["password"],
207 _server.execute("fuser -n tcp %s %s -k > /dev/null 2>&1" %
208 (TREX_SYNC_PORT, TREX_ASYNC_PORT))
210 trex_path = os.path.join(self.bin_path, "trex/scripts")
211 path = get_nsb_option("trex_path", trex_path)
212 trex_cmd = "cd " + path + "; sudo ./t-rex-64 -i > /dev/null 2>&1"
214 _server.execute(trex_cmd)
216 def _connect_client(self, client=None):
218 client = STLClient(username=self.vnfd["mgmt-interface"]["user"],
219 server=self.vnfd["mgmt-interface"]["ip"],
220 verbose_level=LoggerApi.VERBOSE_QUIET)
221 # try to connect with 5s intervals, 30s max
227 LOG.info("Unable to connect to Trex Server.. Attempt %s", idx)
231 def _traffic_runner(self, traffic_profile, queue,
232 client_started, terminated):
233 LOG.info("Starting TRex client...")
235 self.my_ports = [0, 1]
236 self.client = self._connect_client()
237 self.client.reset(ports=self.my_ports)
239 self.client.remove_all_streams(self.my_ports) # remove all streams
241 while not terminated.value:
242 traffic_profile.execute(self)
243 client_started.value = 1
244 last_res = self.client.get_stats(self.my_ports)
245 if not isinstance(last_res, dict): # added for mock unit test
250 for vpci_idx in range(len(self._vpci_ascending)):
252 self._get_logical_if_name(self._vpci_ascending[vpci_idx])
253 # fixme: VNFDs KPIs values needs to be mapped to TRex structure
254 xe_value = last_res.get(vpci_idx, {})
256 {"rx_throughput_fps": float(xe_value.get("rx_pps", 0.0)),
257 "tx_throughput_fps": float(xe_value.get("tx_pps", 0.0)),
258 "rx_throughput_mbps": float(xe_value.get("rx_bps", 0.0)),
259 "tx_throughput_mbps": float(xe_value.get("tx_bps", 0.0)),
260 "in_packets": xe_value.get("ipackets", 0),
261 "out_packets": xe_value.get("opackets", 0)}
264 self.client.disconnect()
267 def collect_kpi(self):
268 if not self._queue.empty():
269 self._result.update(self._queue.get())
270 LOG.debug("trex collect Kpis %s", self._result)
274 self.connection.execute("fuser -n tcp %s %s -k > /dev/null 2>&1" %
275 (TREX_SYNC_PORT, TREX_ASYNC_PORT))
276 self.traffic_finished = True
277 if self._traffic_process:
278 self._traffic_process.terminate()