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 traffic generation definitions which implements rfc2544 """
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 stl.trex_stl_lib.trex_stl_client import STLClient
28 from stl.trex_stl_lib.trex_stl_client import LoggerApi
29 from stl.trex_stl_lib.trex_stl_exceptions import STLError
31 LOGGING = logging.getLogger(__name__)
36 TREX_ASYNC_PORT = 4501
39 class TrexTrafficGenRFC(GenericTrafficGen):
41 This class handles mapping traffic profile and generating
42 traffic for rfc2544 testcase.
45 def __init__(self, vnfd):
46 super(TrexTrafficGenRFC, self).__init__(vnfd)
48 self._terminated = multiprocessing.Value('i', 0)
49 self._queue = multiprocessing.Queue()
50 self._terminated = multiprocessing.Value('i', 0)
51 self._traffic_process = None
52 self._vpci_ascending = None
53 self.tc_file_name = None
57 mgmt_interface = self.vnfd["mgmt-interface"]
58 ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT)
59 self.connection = ssh.SSH(mgmt_interface["user"], mgmt_interface["ip"],
60 password=mgmt_interface["password"],
62 self.connection.wait()
65 def _split_mac_address_into_list(cls, mac):
66 octets = mac.split(':')
67 for i, elem in enumerate(octets):
68 octets[i] = "0x" + str(elem)
71 def _generate_trex_cfg(self, vnfd):
74 :param vnfd: vnfd.yaml
75 :return: trex_cfg.yaml file
84 trex_cfg["port_limit"] = len(vnfd["vdu"][0]["external-interface"])
85 trex_cfg["version"] = '2'
91 ext_intf = vnfd["vdu"][0]["external-interface"]
92 for interface in ext_intf:
93 virt_intf = interface["virtual-interface"]
94 vpci.append(virt_intf["vpci"])
97 self._split_mac_address_into_list(virt_intf["local_mac"])
101 self._split_mac_address_into_list(virt_intf["dst_mac"])
102 if virt_intf["dst_mac"]:
103 trex_cfg["port_info"].append(port.copy())
105 trex_cfg["interfaces"] = vpci
106 cfg_file.append(trex_cfg)
108 with open('/tmp/trex_cfg.yaml', 'w') as outfile:
109 outfile.write(yaml.safe_dump(cfg_file, default_flow_style=False))
110 self.connection.put('/tmp/trex_cfg.yaml', '/etc')
112 self._vpci_ascending = sorted(vpci)
114 def scale(self, flavor=""):
115 ''' scale vnfbased on flavor input '''
116 super(TrexTrafficGenRFC, self).scale(flavor)
118 def instantiate(self, scenario_cfg, context_cfg):
119 self._generate_trex_cfg(self.vnfd)
120 self.tc_file_name = '{0}.yaml'.format(scenario_cfg['tc'])
121 trex = os.path.join(self.bin_path, "trex")
123 self.connection.execute("ls {} >/dev/null 2>&1".format(trex))
125 self.connection.put(trex, trex, True)
127 LOGGING.debug("Starting TRex server...")
129 multiprocessing.Process(target=self._start_server)
132 LOGGING.info("Waiting for TG Server to start.. ")
136 self.connection.execute("lsof -i:%s" % TREX_SYNC_PORT)[0]
138 LOGGING.info("TG server is up and running.")
139 return _tg_server.exitcode
140 if not _tg_server.is_alive():
141 raise RuntimeError("Traffic Generator process died.")
143 def listen_traffic(self, traffic_profile):
146 def _get_logical_if_name(self, vpci):
147 ext_intf = self.vnfd["vdu"][0]["external-interface"]
148 for interface in range(len(self.vnfd["vdu"][0]["external-interface"])):
149 virtual_intf = ext_intf[interface]["virtual-interface"]
150 if virtual_intf["vpci"] == vpci:
151 return ext_intf[interface]["name"]
153 def run_traffic(self, traffic_profile,
154 client_started=multiprocessing.Value('i', 0)):
156 self._traffic_process = \
157 multiprocessing.Process(target=self._traffic_runner,
158 args=(traffic_profile, self._queue,
159 client_started, self._terminated))
160 self._traffic_process.start()
161 # Wait for traffic process to start
162 while client_started.value == 0:
165 return self._traffic_process.is_alive()
167 def _start_server(self):
168 mgmt_interface = self.vnfd["mgmt-interface"]
169 ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT)
170 _server = ssh.SSH(mgmt_interface["user"], mgmt_interface["ip"],
171 password=mgmt_interface["password"],
175 _server.execute("fuser -n tcp %s %s -k > /dev/null 2>&1" %
176 (TREX_SYNC_PORT, TREX_ASYNC_PORT))
177 _server.execute("pkill -9 rex > /dev/null 2>&1")
179 trex_path = os.path.join(self.bin_path, "trex/scripts")
180 path = get_nsb_option("trex_path", trex_path)
181 trex_cmd = "cd " + path + "; sudo ./t-rex-64 -i > /dev/null 2>&1"
183 _server.execute(trex_cmd)
185 def _connect_client(self, client=None):
187 client = STLClient(username=self.vnfd["mgmt-interface"]["user"],
188 server=self.vnfd["mgmt-interface"]["ip"],
189 verbose_level=LoggerApi.VERBOSE_QUIET)
195 LOGGING.info("Unable to connect to Trex. Attempt %s", idx)
196 time.sleep(WAIT_TIME)
200 def _get_rfc_tolerance(cls, tc_yaml):
201 tolerance = '0.8 - 1.0'
202 if 'tc_options' in tc_yaml['scenarios'][0]:
203 tc_options = tc_yaml['scenarios'][0]['tc_options']
204 if 'rfc2544' in tc_options:
206 tc_options['rfc2544'].get('allowed_drop_rate', '0.8 - 1.0')
208 tolerance = tolerance.split('-')
209 min_tol = float(tolerance[0])
210 if len(tolerance) == 2:
211 max_tol = float(tolerance[1])
213 max_tol = float(tolerance[0])
215 return [min_tol, max_tol]
217 def _traffic_runner(self, traffic_profile, queue,
218 client_started, terminated):
219 LOGGING.info("Starting TRex client...")
222 with open(self.tc_file_name) as tc_file:
223 tc_yaml = yaml.load(tc_file.read())
225 tolerance = self._get_rfc_tolerance(tc_yaml)
227 # fixme: fix passing correct trex config file,
228 # instead of searching the default path
229 self.my_ports = [0, 1]
230 self.client = self._connect_client()
231 self.client.reset(ports=self.my_ports)
232 self.client.remove_all_streams(self.my_ports) # remove all streams
233 while not terminated.value:
234 traffic_profile.execute(self)
235 client_started.value = 1
237 self.client.stop(self.my_ports)
238 time.sleep(WAIT_TIME)
239 last_res = self.client.get_stats(self.my_ports)
241 for vpci_idx in range(len(self._vpci_ascending)):
243 self._get_logical_if_name(self._vpci_ascending[vpci_idx])
244 # fixme: VNFDs KPIs values needs to be mapped to TRex structure
245 if not isinstance(last_res, dict):
250 {"rx_throughput_fps":
251 float(last_res.get(vpci_idx, {}).get("rx_pps", 0.0)),
253 float(last_res.get(vpci_idx, {}).get("tx_pps", 0.0)),
254 "rx_throughput_mbps":
255 float(last_res.get(vpci_idx, {}).get("rx_bps", 0.0)),
256 "tx_throughput_mbps":
257 float(last_res.get(vpci_idx, {}).get("tx_bps", 0.0)),
259 last_res.get(vpci_idx, {}).get("ipackets", 0),
261 last_res.get(vpci_idx, {}).get("opackets", 0)}
264 traffic_profile.get_drop_percentage(self, samples,
265 tolerance[0], tolerance[1])
267 self.client.stop(self.my_ports)
268 self.client.disconnect()
271 def collect_kpi(self):
272 if not self._queue.empty():
273 result = self._queue.get()
274 self._result.update(result)
275 LOGGING.debug("trex collect Kpis %s", self._result)
279 self._terminated.value = 1 # stop Trex clinet
281 self.connection.execute("fuser -n tcp %s %s -k > /dev/null 2>&1" %
282 (TREX_SYNC_PORT, TREX_ASYNC_PORT))
284 if self._traffic_process:
285 self._traffic_process.terminate()