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.
19 from yardstick.common import utils
20 from yardstick.network_services.libs.ixia_libs.ixnet import ixnet_api
21 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNFTrafficGen
22 from yardstick.network_services.vnf_generic.vnf.sample_vnf import ClientResourceHelper
23 from yardstick.network_services.vnf_generic.vnf.sample_vnf import Rfc2544ResourceHelper
26 LOG = logging.getLogger(__name__)
28 WAIT_AFTER_CFG_LOAD = 10
30 WAIT_PROTOCOLS_STARTED = 360
33 class IxiaBasicScenario(object):
34 def __init__(self, client, context_cfg, ixia_cfg):
37 self.context_cfg = context_cfg
38 self.ixia_cfg = ixia_cfg
40 self._uplink_vports = None
41 self._downlink_vports = None
43 def apply_config(self):
46 def create_traffic_model(self, traffic_profile=None):
47 # pylint: disable=unused-argument
48 vports = self.client.get_vports()
49 self._uplink_vports = vports[::2]
50 self._downlink_vports = vports[1::2]
51 self.client.create_traffic_model(self._uplink_vports,
52 self._downlink_vports)
54 def run_protocols(self):
57 def stop_protocols(self):
61 class IxiaPppoeClientScenario(object):
62 def __init__(self, client, context_cfg, ixia_cfg):
66 self._uplink_vports = None
67 self._downlink_vports = None
69 self._access_topologies = []
70 self._core_topologies = []
72 self._context_cfg = context_cfg
73 self._ixia_cfg = ixia_cfg
75 self.device_groups = []
77 def apply_config(self):
78 vports = self.client.get_vports()
79 self._uplink_vports = vports[::2]
80 self._downlink_vports = vports[1::2]
81 self._fill_ixia_config()
82 self._apply_access_network_config()
83 self._apply_core_network_config()
85 def create_traffic_model(self, traffic_profile):
86 endpoints_id_pairs = self._get_endpoints_src_dst_id_pairs(
87 traffic_profile.full_profile)
88 endpoints_obj_pairs = \
89 self._get_endpoints_src_dst_obj_pairs(endpoints_id_pairs)
90 uplink_endpoints = endpoints_obj_pairs[::2]
91 downlink_endpoints = endpoints_obj_pairs[1::2]
92 self.client.create_ipv4_traffic_model(uplink_endpoints,
95 def run_protocols(self):
96 LOG.info('PPPoE Scenario - Start Protocols')
97 self.client.start_protocols()
98 utils.wait_until_true(
99 lambda: self.client.is_protocols_running(self.protocols),
100 timeout=WAIT_PROTOCOLS_STARTED, sleep=2)
102 def stop_protocols(self):
103 LOG.info('PPPoE Scenario - Stop Protocols')
104 self.client.stop_protocols()
106 def _get_intf_addr(self, intf):
107 """Retrieve interface IP address and mask
109 :param intf: could be the string which represents IP address
110 with mask (e.g 192.168.10.2/24) or a dictionary with the host
111 name and the port (e.g. {'tg__0': 'xe1'})
112 :return: (tuple) pair of ip address and mask
114 if isinstance(intf, six.string_types):
115 ip, mask = tuple(intf.split('/'))
118 node_name, intf_name = next(iter(intf.items()))
119 node = self._context_cfg["nodes"].get(node_name, {})
120 interface = node.get("interfaces", {})[intf_name]
121 ip = interface["local_ip"]
122 mask = interface["netmask"]
123 ipaddr = ipaddress.ip_network(six.text_type('{}/{}'.format(ip, mask)),
125 return ip, ipaddr.prefixlen
128 def _get_endpoints_src_dst_id_pairs(flows_params):
129 """Get list of flows src/dst port pairs
131 Create list of flows src/dst port pairs based on traffic profile
132 flows data. Each uplink/downlink pair in traffic profile represents
133 specific flows between the pair of ports.
135 Example ('port' key represents port on which flow will be created):
155 Result list: ['xe0', 'xe1', 'xe2', 'xe3']
157 Result list means that the following flows pairs will be created:
158 - uplink 0: port xe0 <-> port xe1
159 - downlink 0: port xe1 <-> port xe0
160 - uplink 1: port xe2 <-> port xe3
161 - downlink 1: port xe3 <-> port xe2
163 :param flows_params: ordered dict of traffic profile flows params
164 :return: (list) list of flows src/dst ports
166 if len(flows_params) % 2:
167 raise RuntimeError('Number of uplink/downlink pairs'
168 ' in traffic profile is not equal')
170 for flow in flows_params:
171 port = flows_params[flow]['ipv4'].get('port')
174 endpoint_pairs.append(port)
175 return endpoint_pairs
177 def _get_endpoints_src_dst_obj_pairs(self, endpoints_id_pairs):
178 """Create list of uplink/downlink device groups pairs
180 Based on traffic profile options, create list of uplink/downlink
181 device groups pairs between which flow groups will be created:
183 1. In case uplink/downlink flows in traffic profile doesn't have
184 specified 'port' key, flows will be created between each device
185 group on access port and device group on corresponding core port.
187 Device groups created on access port xe0: dg1, dg2, dg3
188 Device groups created on core port xe1: dg4
189 Flows will be created between:
197 2. In case uplink/downlink flows in traffic profile have specified
198 'port' key, flows will be created between device groups on this
200 E.g., for the following traffic profile
209 Flows will be created between:
210 Port xe0 (dg1) -> Port xe1 (dg1)
211 Port xe1 (dg1) -> Port xe0 (dg1)
212 Port xe0 (dg2) -> Port xe3 (dg1)
213 Port xe3 (dg3) -> Port xe0 (dg1)
215 :param endpoints_id_pairs: (list) List of uplink/downlink flows ports
217 :return: (list) list of uplink/downlink device groups descriptors pairs
219 pppoe = self._ixia_cfg['pppoe_client']
220 sessions_per_port = pppoe['sessions_per_port']
221 sessions_per_svlan = pppoe['sessions_per_svlan']
222 svlan_count = int(sessions_per_port / sessions_per_svlan)
224 uplink_ports = [p['tg__0'] for p in self._ixia_cfg['flow']['src_ip']]
225 downlink_ports = [p['tg__0'] for p in self._ixia_cfg['flow']['dst_ip']]
226 uplink_port_topology_map = zip(uplink_ports, self._access_topologies)
227 downlink_port_topology_map = zip(downlink_ports, self._core_topologies)
229 port_to_dev_group_mapping = {}
230 for port, topology in uplink_port_topology_map:
231 topology_dgs = self.client.get_topology_device_groups(topology)
232 port_to_dev_group_mapping[port] = topology_dgs
233 for port, topology in downlink_port_topology_map:
234 topology_dgs = self.client.get_topology_device_groups(topology)
235 port_to_dev_group_mapping[port] = topology_dgs
237 uplink_endpoints = endpoints_id_pairs[::2]
238 downlink_endpoints = endpoints_id_pairs[1::2]
240 uplink_dev_groups = []
241 group_up = [uplink_endpoints[i:i + svlan_count]
242 for i in range(0, len(uplink_endpoints), svlan_count)]
244 for group in group_up:
245 for i, port in enumerate(group):
246 uplink_dev_groups.append(port_to_dev_group_mapping[port][i])
248 downlink_dev_groups = []
249 for port in downlink_endpoints:
250 downlink_dev_groups.append(port_to_dev_group_mapping[port][0])
252 endpoint_obj_pairs = []
253 [endpoint_obj_pairs.extend([up, down])
254 for up, down in zip(uplink_dev_groups, downlink_dev_groups)]
256 if not endpoint_obj_pairs:
257 for up, down in zip(uplink_ports, downlink_ports):
258 uplink_dev_groups = port_to_dev_group_mapping[up]
259 downlink_dev_groups = \
260 port_to_dev_group_mapping[down] * len(uplink_dev_groups)
261 [endpoint_obj_pairs.extend(list(i))
262 for i in zip(uplink_dev_groups, downlink_dev_groups)]
263 return endpoint_obj_pairs
265 def _fill_ixia_config(self):
266 pppoe = self._ixia_cfg["pppoe_client"]
267 ipv4 = self._ixia_cfg["ipv4_client"]
269 _ip = [self._get_intf_addr(intf)[0] for intf in pppoe["ip"]]
270 self._ixia_cfg["pppoe_client"]["ip"] = _ip
272 _ip = [self._get_intf_addr(intf)[0] for intf in ipv4["gateway_ip"]]
273 self._ixia_cfg["ipv4_client"]["gateway_ip"] = _ip
275 addrs = [self._get_intf_addr(intf) for intf in ipv4["ip"]]
276 _ip = [addr[0] for addr in addrs]
277 _prefix = [addr[1] for addr in addrs]
279 self._ixia_cfg["ipv4_client"]["ip"] = _ip
280 self._ixia_cfg["ipv4_client"]["prefix"] = _prefix
282 def _apply_access_network_config(self):
283 pppoe = self._ixia_cfg["pppoe_client"]
284 sessions_per_port = pppoe['sessions_per_port']
285 sessions_per_svlan = pppoe['sessions_per_svlan']
286 svlan_count = int(sessions_per_port / sessions_per_svlan)
288 # add topology per uplink port (access network)
289 for access_tp_id, vport in enumerate(self._uplink_vports):
290 name = 'Topology access {}'.format(access_tp_id)
291 tp = self.client.add_topology(name, vport)
292 self._access_topologies.append(tp)
293 # add device group per svlan
294 for dg_id in range(svlan_count):
295 s_vlan_id = int(pppoe['s_vlan']) + dg_id + access_tp_id * svlan_count
296 s_vlan = ixnet_api.Vlan(vlan_id=s_vlan_id)
297 c_vlan = ixnet_api.Vlan(vlan_id=pppoe['c_vlan'], vlan_id_step=1)
298 name = 'SVLAN {}'.format(s_vlan_id)
299 dg = self.client.add_device_group(tp, name, sessions_per_svlan)
300 self.device_groups.append(dg)
301 # add ethernet layer to device group
302 ethernet = self.client.add_ethernet(dg, 'Ethernet')
303 self.protocols.append(ethernet)
304 self.client.add_vlans(ethernet, [s_vlan, c_vlan])
305 # add ppp over ethernet
306 if 'pap_user' in pppoe:
307 ppp = self.client.add_pppox_client(ethernet, 'pap',
309 pppoe['pap_password'])
311 ppp = self.client.add_pppox_client(ethernet, 'chap',
313 pppoe['chap_password'])
314 self.protocols.append(ppp)
316 def _apply_core_network_config(self):
317 ipv4 = self._ixia_cfg["ipv4_client"]
318 sessions_per_port = ipv4['sessions_per_port']
319 sessions_per_vlan = ipv4['sessions_per_vlan']
320 vlan_count = int(sessions_per_port / sessions_per_vlan)
322 # add topology per downlink port (core network)
323 for core_tp_id, vport in enumerate(self._downlink_vports):
324 name = 'Topology core {}'.format(core_tp_id)
325 tp = self.client.add_topology(name, vport)
326 self._core_topologies.append(tp)
327 # add device group per vlan
328 for dg_id in range(vlan_count):
329 name = 'Core port {}'.format(core_tp_id)
330 dg = self.client.add_device_group(tp, name, sessions_per_vlan)
331 self.device_groups.append(dg)
332 # add ethernet layer to device group
333 ethernet = self.client.add_ethernet(dg, 'Ethernet')
334 self.protocols.append(ethernet)
336 vlan_id = int(ipv4['vlan']) + dg_id + core_tp_id * vlan_count
337 vlan = ixnet_api.Vlan(vlan_id=vlan_id)
338 self.client.add_vlans(ethernet, [vlan])
340 gw_ip = ipv4['gateway_ip'][core_tp_id]
341 # use gw addr to generate ip addr from the same network
342 ip_addr = ipaddress.IPv4Address(gw_ip) + 1
343 ipv4_obj = self.client.add_ipv4(ethernet, name='ipv4',
346 prefix=ipv4['prefix'][core_tp_id],
348 self.protocols.append(ipv4_obj)
350 bgp_peer_obj = self.client.add_bgp(ipv4_obj,
351 dut_ip=ipv4["bgp"]["dut_ip"],
352 local_as=ipv4["bgp"]["as_number"],
353 bgp_type=ipv4["bgp"].get("bgp_type"))
354 self.protocols.append(bgp_peer_obj)
357 class IxiaRfc2544Helper(Rfc2544ResourceHelper):
360 return self.latency and self.iteration.value > 10
363 class IxiaResourceHelper(ClientResourceHelper):
365 LATENCY_TIME_SLEEP = 120
367 def __init__(self, setup_helper, rfc_helper_type=None):
368 super(IxiaResourceHelper, self).__init__(setup_helper)
369 self.scenario_helper = setup_helper.scenario_helper
371 self._ixia_scenarios = {
372 "IxiaBasic": IxiaBasicScenario,
373 "IxiaPppoeClient": IxiaPppoeClientScenario,
376 self.client = ixnet_api.IxNextgen()
378 if rfc_helper_type is None:
379 rfc_helper_type = IxiaRfc2544Helper
381 self.rfc_helper = rfc_helper_type(self.scenario_helper)
382 self.uplink_ports = None
383 self.downlink_ports = None
384 self.context_cfg = None
385 self._ix_scenario = None
388 def _connect(self, client=None):
389 self.client.connect(self.vnfd_helper)
391 def get_stats(self, *args, **kwargs):
392 return self.client.get_statistics()
395 super(IxiaResourceHelper, self).setup()
396 self._init_ix_scenario()
398 def stop_collect(self):
399 self._ix_scenario.stop_protocols()
400 self._terminated.value = 1
402 def generate_samples(self, ports, duration):
403 stats = self.get_stats()
406 # this is not DPDK port num, but this is whatever number we gave
407 # when we selected ports and programmed the profile
408 for port_num in ports:
410 # reverse lookup port name from port_num so the stats dict is descriptive
411 intf = self.vnfd_helper.find_interface_by_port(port_num)
412 port_name = intf['name']
413 avg_latency = stats['Store-Forward_Avg_latency_ns'][port_num]
414 min_latency = stats['Store-Forward_Min_latency_ns'][port_num]
415 max_latency = stats['Store-Forward_Max_latency_ns'][port_num]
416 samples[port_name] = {
417 'rx_throughput_kps': float(stats['Rx_Rate_Kbps'][port_num]),
418 'tx_throughput_kps': float(stats['Tx_Rate_Kbps'][port_num]),
419 'rx_throughput_mbps': float(stats['Rx_Rate_Mbps'][port_num]),
420 'tx_throughput_mbps': float(stats['Tx_Rate_Mbps'][port_num]),
421 'in_packets': int(stats['Valid_Frames_Rx'][port_num]),
422 'out_packets': int(stats['Frames_Tx'][port_num]),
423 'RxThroughput': float(stats['Valid_Frames_Rx'][port_num]) / duration,
424 'TxThroughput': float(stats['Frames_Tx'][port_num]) / duration,
425 'Store-Forward_Avg_latency_ns': utils.safe_cast(avg_latency, int, 0),
426 'Store-Forward_Min_latency_ns': utils.safe_cast(min_latency, int, 0),
427 'Store-Forward_Max_latency_ns': utils.safe_cast(max_latency, int, 0)
434 def _init_ix_scenario(self):
435 ixia_config = self.scenario_helper.scenario_cfg.get('ixia_config', 'IxiaBasic')
437 if ixia_config in self._ixia_scenarios:
438 scenario_type = self._ixia_scenarios[ixia_config]
440 self._ix_scenario = scenario_type(self.client, self.context_cfg,
441 self.scenario_helper.scenario_cfg['options'])
444 "IXIA config type '{}' not supported".format(ixia_config))
446 def _initialize_client(self, traffic_profile):
447 """Initialize the IXIA IxNetwork client and configure the server"""
448 self.client.clear_config()
449 self.client.assign_ports()
450 self._ix_scenario.apply_config()
451 self._ix_scenario.create_traffic_model(traffic_profile)
453 def run_traffic(self, traffic_profile, *args):
454 if self._terminated.value:
457 min_tol = self.rfc_helper.tolerance_low
458 max_tol = self.rfc_helper.tolerance_high
459 precision = self.rfc_helper.tolerance_precision
460 default = "00:00:00:00:00:00"
463 traffic_profile.update_traffic_profile(self)
464 self._initialize_client(traffic_profile)
467 for port_name in self.vnfd_helper.port_pairs.all_ports:
468 intf = self.vnfd_helper.find_interface(name=port_name)
469 virt_intf = intf["virtual-interface"]
470 # we only know static traffic id by reading the json
471 # this is used by _get_ixia_trafficrofile
472 port_num = self.vnfd_helper.port_num(intf)
473 mac["src_mac_{}".format(port_num)] = virt_intf.get("local_mac", default)
474 mac["dst_mac_{}".format(port_num)] = virt_intf.get("dst_mac", default)
476 self._ix_scenario.run_protocols()
479 while not self._terminated.value:
480 first_run = traffic_profile.execute_traffic(
481 self, self.client, mac)
482 self.client_started.value = 1
483 # pylint: disable=unnecessary-lambda
484 utils.wait_until_true(lambda: self.client.is_traffic_stopped(),
485 timeout=traffic_profile.config.duration * 2)
486 samples = self.generate_samples(traffic_profile.ports,
487 traffic_profile.config.duration)
489 completed, samples = traffic_profile.get_drop_percentage(
490 samples, min_tol, max_tol, precision, first_run=first_run)
491 self._queue.put(samples)
494 self._terminated.value = 1
496 except Exception: # pylint: disable=broad-except
497 LOG.exception('Run Traffic terminated')
499 self._ix_scenario.stop_protocols()
500 self._terminated.value = 1
502 def collect_kpi(self):
503 self.rfc_helper.iteration.value += 1
504 return super(IxiaResourceHelper, self).collect_kpi()
507 class IxiaTrafficGen(SampleVNFTrafficGen):
511 def __init__(self, name, vnfd, task_id, setup_env_helper_type=None,
512 resource_helper_type=None):
513 if resource_helper_type is None:
514 resource_helper_type = IxiaResourceHelper
515 super(IxiaTrafficGen, self).__init__(
516 name, vnfd, task_id, setup_env_helper_type, resource_helper_type)
517 self._ixia_traffic_gen = None
518 self.ixia_file_name = ''
519 self.vnf_port_pairs = []
521 def _check_status(self):
525 self.resource_helper.stop_collect()
526 super(IxiaTrafficGen, self).terminate()