1 # Copyright (c) 2016-2019 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.common import exceptions
21 from yardstick.network_services.libs.ixia_libs.ixnet import ixnet_api
22 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNFTrafficGen
23 from yardstick.network_services.vnf_generic.vnf.sample_vnf import ClientResourceHelper
24 from yardstick.network_services.vnf_generic.vnf.sample_vnf import Rfc2544ResourceHelper
27 LOG = logging.getLogger(__name__)
29 WAIT_AFTER_CFG_LOAD = 10
31 WAIT_PROTOCOLS_STARTED = 360
34 class IxiaBasicScenario(object):
35 """Ixia Basic scenario for flow from port to port"""
37 def __init__(self, client, context_cfg, ixia_cfg):
40 self.context_cfg = context_cfg
41 self.ixia_cfg = ixia_cfg
43 self._uplink_vports = None
44 self._downlink_vports = None
46 def apply_config(self):
49 def run_protocols(self):
52 def stop_protocols(self):
55 def create_traffic_model(self, traffic_profile=None):
56 # pylint: disable=unused-argument
57 vports = self.client.get_vports()
58 self._uplink_vports = vports[::2]
59 self._downlink_vports = vports[1::2]
60 self.client.create_traffic_model(self._uplink_vports,
61 self._downlink_vports)
64 class IxiaL3Scenario(IxiaBasicScenario):
65 """Ixia scenario for L3 flow between static ip's"""
67 def _add_static_ips(self):
68 vports = self.client.get_vports()
69 uplink_intf_vport = [(self.client.get_static_interface(vport), vport)
70 for vport in vports[::2]]
71 downlink_intf_vport = [(self.client.get_static_interface(vport), vport)
72 for vport in vports[1::2]]
74 for index in range(len(uplink_intf_vport)):
75 intf, vport = uplink_intf_vport[index]
77 iprange = self.ixia_cfg['flow'].get('src_ip')[index]
78 start_ip = utils.get_ip_range_start(iprange)
79 count = utils.get_ip_range_count(iprange)
80 self.client.add_static_ipv4(intf, vport, start_ip, count, '32')
82 raise exceptions.IncorrectFlowOption(
83 option="src_ip", link="uplink_{}".format(index))
85 intf, vport = downlink_intf_vport[index]
87 iprange = self.ixia_cfg['flow'].get('dst_ip')[index]
88 start_ip = utils.get_ip_range_start(iprange)
89 count = utils.get_ip_range_count(iprange)
90 self.client.add_static_ipv4(intf, vport, start_ip, count, '32')
92 raise exceptions.IncorrectFlowOption(
93 option="dst_ip", link="downlink_{}".format(index))
95 def _add_interfaces(self):
96 vports = self.client.get_vports()
97 uplink_vports = (vport for vport in vports[::2])
98 downlink_vports = (vport for vport in vports[1::2])
100 ix_node = next(node for _, node in self.context_cfg['nodes'].items()
101 if node['role'] == 'IxNet')
103 for intf in ix_node['interfaces'].values():
104 ip = intf.get('local_ip')
105 mac = intf.get('local_mac')
108 gateway = next(route.get('gateway')
109 for route in ix_node.get('routing_table')
110 if route.get('if') == intf.get('ifname'))
111 except StopIteration:
112 LOG.debug("Gateway not provided")
114 if 'uplink' in intf.get('vld_id'):
115 self.client.add_interface(next(uplink_vports),
118 self.client.add_interface(next(downlink_vports),
121 def apply_config(self):
122 self._add_interfaces()
123 self._add_static_ips()
125 def create_traffic_model(self, traffic_profile=None):
126 # pylint: disable=unused-argument
127 vports = self.client.get_vports()
128 self._uplink_vports = vports[::2]
129 self._downlink_vports = vports[1::2]
131 uplink_endpoints = [port + '/protocols/static'
132 for port in self._uplink_vports]
133 downlink_endpoints = [port + '/protocols/static'
134 for port in self._downlink_vports]
136 self.client.create_ipv4_traffic_model(uplink_endpoints,
140 class IxiaPppoeClientScenario(object):
141 def __init__(self, client, context_cfg, ixia_cfg):
145 self._uplink_vports = None
146 self._downlink_vports = None
148 self._access_topologies = []
149 self._core_topologies = []
151 self._context_cfg = context_cfg
152 self._ixia_cfg = ixia_cfg
154 self.device_groups = []
156 def apply_config(self):
157 vports = self.client.get_vports()
158 self._uplink_vports = vports[::2]
159 self._downlink_vports = vports[1::2]
160 self._fill_ixia_config()
161 self._apply_access_network_config()
162 self._apply_core_network_config()
164 def create_traffic_model(self, traffic_profile):
165 endpoints_id_pairs = self._get_endpoints_src_dst_id_pairs(
166 traffic_profile.full_profile)
167 endpoints_obj_pairs = \
168 self._get_endpoints_src_dst_obj_pairs(endpoints_id_pairs)
169 uplink_endpoints = endpoints_obj_pairs[::2]
170 downlink_endpoints = endpoints_obj_pairs[1::2]
171 self.client.create_ipv4_traffic_model(uplink_endpoints,
174 def run_protocols(self):
175 LOG.info('PPPoE Scenario - Start Protocols')
176 self.client.start_protocols()
177 utils.wait_until_true(
178 lambda: self.client.is_protocols_running(self.protocols),
179 timeout=WAIT_PROTOCOLS_STARTED, sleep=2)
181 def stop_protocols(self):
182 LOG.info('PPPoE Scenario - Stop Protocols')
183 self.client.stop_protocols()
185 def _get_intf_addr(self, intf):
186 """Retrieve interface IP address and mask
188 :param intf: could be the string which represents IP address
189 with mask (e.g 192.168.10.2/24) or a dictionary with the host
190 name and the port (e.g. {'tg__0': 'xe1'})
191 :return: (tuple) pair of ip address and mask
193 if isinstance(intf, six.string_types):
194 ip, mask = tuple(intf.split('/'))
197 node_name, intf_name = next(iter(intf.items()))
198 node = self._context_cfg["nodes"].get(node_name, {})
199 interface = node.get("interfaces", {})[intf_name]
200 ip = interface["local_ip"]
201 mask = interface["netmask"]
202 ipaddr = ipaddress.ip_network(six.text_type('{}/{}'.format(ip, mask)),
204 return ip, ipaddr.prefixlen
207 def _get_endpoints_src_dst_id_pairs(flows_params):
208 """Get list of flows src/dst port pairs
210 Create list of flows src/dst port pairs based on traffic profile
211 flows data. Each uplink/downlink pair in traffic profile represents
212 specific flows between the pair of ports.
214 Example ('port' key represents port on which flow will be created):
234 Result list: ['xe0', 'xe1', 'xe2', 'xe3']
236 Result list means that the following flows pairs will be created:
237 - uplink 0: port xe0 <-> port xe1
238 - downlink 0: port xe1 <-> port xe0
239 - uplink 1: port xe2 <-> port xe3
240 - downlink 1: port xe3 <-> port xe2
242 :param flows_params: ordered dict of traffic profile flows params
243 :return: (list) list of flows src/dst ports
245 if len(flows_params) % 2:
246 raise RuntimeError('Number of uplink/downlink pairs'
247 ' in traffic profile is not equal')
249 for flow in flows_params:
250 port = flows_params[flow]['ipv4'].get('port')
253 endpoint_pairs.append(port)
254 return endpoint_pairs
256 def _get_endpoints_src_dst_obj_pairs(self, endpoints_id_pairs):
257 """Create list of uplink/downlink device groups pairs
259 Based on traffic profile options, create list of uplink/downlink
260 device groups pairs between which flow groups will be created:
262 1. In case uplink/downlink flows in traffic profile doesn't have
263 specified 'port' key, flows will be created between each device
264 group on access port and device group on corresponding core port.
266 Device groups created on access port xe0: dg1, dg2, dg3
267 Device groups created on core port xe1: dg4
268 Flows will be created between:
276 2. In case uplink/downlink flows in traffic profile have specified
277 'port' key, flows will be created between device groups on this
279 E.g., for the following traffic profile
288 Flows will be created between:
289 Port xe0 (dg1) -> Port xe1 (dg1)
290 Port xe1 (dg1) -> Port xe0 (dg1)
291 Port xe0 (dg2) -> Port xe3 (dg1)
292 Port xe3 (dg3) -> Port xe0 (dg1)
294 :param endpoints_id_pairs: (list) List of uplink/downlink flows ports
296 :return: (list) list of uplink/downlink device groups descriptors pairs
298 pppoe = self._ixia_cfg['pppoe_client']
299 sessions_per_port = pppoe['sessions_per_port']
300 sessions_per_svlan = pppoe['sessions_per_svlan']
301 svlan_count = int(sessions_per_port / sessions_per_svlan)
303 uplink_ports = [p['tg__0'] for p in self._ixia_cfg['flow']['src_ip']]
304 downlink_ports = [p['tg__0'] for p in self._ixia_cfg['flow']['dst_ip']]
305 uplink_port_topology_map = zip(uplink_ports, self._access_topologies)
306 downlink_port_topology_map = zip(downlink_ports, self._core_topologies)
308 port_to_dev_group_mapping = {}
309 for port, topology in uplink_port_topology_map:
310 topology_dgs = self.client.get_topology_device_groups(topology)
311 port_to_dev_group_mapping[port] = topology_dgs
312 for port, topology in downlink_port_topology_map:
313 topology_dgs = self.client.get_topology_device_groups(topology)
314 port_to_dev_group_mapping[port] = topology_dgs
316 uplink_endpoints = endpoints_id_pairs[::2]
317 downlink_endpoints = endpoints_id_pairs[1::2]
319 uplink_dev_groups = []
320 group_up = [uplink_endpoints[i:i + svlan_count]
321 for i in range(0, len(uplink_endpoints), svlan_count)]
323 for group in group_up:
324 for i, port in enumerate(group):
325 uplink_dev_groups.append(port_to_dev_group_mapping[port][i])
327 downlink_dev_groups = []
328 for port in downlink_endpoints:
329 downlink_dev_groups.append(port_to_dev_group_mapping[port][0])
331 endpoint_obj_pairs = []
332 [endpoint_obj_pairs.extend([up, down])
333 for up, down in zip(uplink_dev_groups, downlink_dev_groups)]
335 if not endpoint_obj_pairs:
336 for up, down in zip(uplink_ports, downlink_ports):
337 uplink_dev_groups = port_to_dev_group_mapping[up]
338 downlink_dev_groups = \
339 port_to_dev_group_mapping[down] * len(uplink_dev_groups)
340 [endpoint_obj_pairs.extend(list(i))
341 for i in zip(uplink_dev_groups, downlink_dev_groups)]
342 return endpoint_obj_pairs
344 def _fill_ixia_config(self):
345 pppoe = self._ixia_cfg["pppoe_client"]
346 ipv4 = self._ixia_cfg["ipv4_client"]
348 _ip = [self._get_intf_addr(intf)[0] for intf in pppoe["ip"]]
349 self._ixia_cfg["pppoe_client"]["ip"] = _ip
351 _ip = [self._get_intf_addr(intf)[0] for intf in ipv4["gateway_ip"]]
352 self._ixia_cfg["ipv4_client"]["gateway_ip"] = _ip
354 addrs = [self._get_intf_addr(intf) for intf in ipv4["ip"]]
355 _ip = [addr[0] for addr in addrs]
356 _prefix = [addr[1] for addr in addrs]
358 self._ixia_cfg["ipv4_client"]["ip"] = _ip
359 self._ixia_cfg["ipv4_client"]["prefix"] = _prefix
361 def _apply_access_network_config(self):
362 pppoe = self._ixia_cfg["pppoe_client"]
363 sessions_per_port = pppoe['sessions_per_port']
364 sessions_per_svlan = pppoe['sessions_per_svlan']
365 svlan_count = int(sessions_per_port / sessions_per_svlan)
367 # add topology per uplink port (access network)
368 for access_tp_id, vport in enumerate(self._uplink_vports):
369 name = 'Topology access {}'.format(access_tp_id)
370 tp = self.client.add_topology(name, vport)
371 self._access_topologies.append(tp)
372 # add device group per svlan
373 for dg_id in range(svlan_count):
374 s_vlan_id = int(pppoe['s_vlan']) + dg_id + access_tp_id * svlan_count
375 s_vlan = ixnet_api.Vlan(vlan_id=s_vlan_id)
376 c_vlan = ixnet_api.Vlan(vlan_id=pppoe['c_vlan'], vlan_id_step=1)
377 name = 'SVLAN {}'.format(s_vlan_id)
378 dg = self.client.add_device_group(tp, name, sessions_per_svlan)
379 self.device_groups.append(dg)
380 # add ethernet layer to device group
381 ethernet = self.client.add_ethernet(dg, 'Ethernet')
382 self.protocols.append(ethernet)
383 self.client.add_vlans(ethernet, [s_vlan, c_vlan])
384 # add ppp over ethernet
385 if 'pap_user' in pppoe:
386 ppp = self.client.add_pppox_client(ethernet, 'pap',
388 pppoe['pap_password'])
390 ppp = self.client.add_pppox_client(ethernet, 'chap',
392 pppoe['chap_password'])
393 self.protocols.append(ppp)
395 def _apply_core_network_config(self):
396 ipv4 = self._ixia_cfg["ipv4_client"]
397 sessions_per_port = ipv4['sessions_per_port']
398 sessions_per_vlan = ipv4['sessions_per_vlan']
399 vlan_count = int(sessions_per_port / sessions_per_vlan)
401 # add topology per downlink port (core network)
402 for core_tp_id, vport in enumerate(self._downlink_vports):
403 name = 'Topology core {}'.format(core_tp_id)
404 tp = self.client.add_topology(name, vport)
405 self._core_topologies.append(tp)
406 # add device group per vlan
407 for dg_id in range(vlan_count):
408 name = 'Core port {}'.format(core_tp_id)
409 dg = self.client.add_device_group(tp, name, sessions_per_vlan)
410 self.device_groups.append(dg)
411 # add ethernet layer to device group
412 ethernet = self.client.add_ethernet(dg, 'Ethernet')
413 self.protocols.append(ethernet)
415 vlan_id = int(ipv4['vlan']) + dg_id + core_tp_id * vlan_count
416 vlan = ixnet_api.Vlan(vlan_id=vlan_id)
417 self.client.add_vlans(ethernet, [vlan])
419 gw_ip = ipv4['gateway_ip'][core_tp_id]
420 # use gw addr to generate ip addr from the same network
421 ip_addr = ipaddress.IPv4Address(gw_ip) + 1
422 ipv4_obj = self.client.add_ipv4(ethernet, name='ipv4',
425 prefix=ipv4['prefix'][core_tp_id],
427 self.protocols.append(ipv4_obj)
429 bgp_peer_obj = self.client.add_bgp(ipv4_obj,
430 dut_ip=ipv4["bgp"]["dut_ip"],
431 local_as=ipv4["bgp"]["as_number"],
432 bgp_type=ipv4["bgp"].get("bgp_type"))
433 self.protocols.append(bgp_peer_obj)
436 class IxiaRfc2544Helper(Rfc2544ResourceHelper):
439 return self.latency and self.iteration.value > 10
442 class IxiaResourceHelper(ClientResourceHelper):
444 LATENCY_TIME_SLEEP = 120
446 def __init__(self, setup_helper, rfc_helper_type=None):
447 super(IxiaResourceHelper, self).__init__(setup_helper)
448 self.scenario_helper = setup_helper.scenario_helper
450 self._ixia_scenarios = {
451 "IxiaBasic": IxiaBasicScenario,
452 "IxiaL3": IxiaL3Scenario,
453 "IxiaPppoeClient": IxiaPppoeClientScenario,
456 self.client = ixnet_api.IxNextgen()
458 if rfc_helper_type is None:
459 rfc_helper_type = IxiaRfc2544Helper
461 self.rfc_helper = rfc_helper_type(self.scenario_helper)
462 self.uplink_ports = None
463 self.downlink_ports = None
464 self.context_cfg = None
465 self._ix_scenario = None
468 def _connect(self, client=None):
469 self.client.connect(self.vnfd_helper)
471 def get_stats(self, *args, **kwargs):
472 return self.client.get_statistics()
475 super(IxiaResourceHelper, self).setup()
476 self._init_ix_scenario()
478 def stop_collect(self):
479 self._ix_scenario.stop_protocols()
480 self._terminated.value = 1
482 def generate_samples(self, ports, duration):
483 stats = self.get_stats()
486 # this is not DPDK port num, but this is whatever number we gave
487 # when we selected ports and programmed the profile
488 for port_num in ports:
490 # reverse lookup port name from port_num so the stats dict is descriptive
491 intf = self.vnfd_helper.find_interface_by_port(port_num)
492 port_name = intf['name']
493 avg_latency = stats['Store-Forward_Avg_latency_ns'][port_num]
494 min_latency = stats['Store-Forward_Min_latency_ns'][port_num]
495 max_latency = stats['Store-Forward_Max_latency_ns'][port_num]
496 samples[port_name] = {
497 'rx_throughput_kps': float(stats['Rx_Rate_Kbps'][port_num]),
498 'tx_throughput_kps': float(stats['Tx_Rate_Kbps'][port_num]),
499 'rx_throughput_mbps': float(stats['Rx_Rate_Mbps'][port_num]),
500 'tx_throughput_mbps': float(stats['Tx_Rate_Mbps'][port_num]),
501 'in_packets': int(stats['Valid_Frames_Rx'][port_num]),
502 'out_packets': int(stats['Frames_Tx'][port_num]),
503 'RxThroughput': float(stats['Valid_Frames_Rx'][port_num]) / duration,
504 'TxThroughput': float(stats['Frames_Tx'][port_num]) / duration,
505 'Store-Forward_Avg_latency_ns': utils.safe_cast(avg_latency, int, 0),
506 'Store-Forward_Min_latency_ns': utils.safe_cast(min_latency, int, 0),
507 'Store-Forward_Max_latency_ns': utils.safe_cast(max_latency, int, 0)
514 def _init_ix_scenario(self):
515 ixia_config = self.scenario_helper.scenario_cfg.get('ixia_config', 'IxiaBasic')
517 if ixia_config in self._ixia_scenarios:
518 scenario_type = self._ixia_scenarios[ixia_config]
520 self._ix_scenario = scenario_type(self.client, self.context_cfg,
521 self.scenario_helper.scenario_cfg['options'])
524 "IXIA config type '{}' not supported".format(ixia_config))
526 def _initialize_client(self, traffic_profile):
527 """Initialize the IXIA IxNetwork client and configure the server"""
528 self.client.clear_config()
529 self.client.assign_ports()
530 self._ix_scenario.apply_config()
531 self._ix_scenario.create_traffic_model(traffic_profile)
533 def run_traffic(self, traffic_profile):
534 if self._terminated.value:
537 min_tol = self.rfc_helper.tolerance_low
538 max_tol = self.rfc_helper.tolerance_high
539 precision = self.rfc_helper.tolerance_precision
540 default = "00:00:00:00:00:00"
543 traffic_profile.update_traffic_profile(self)
544 self._initialize_client(traffic_profile)
547 for port_name in self.vnfd_helper.port_pairs.all_ports:
548 intf = self.vnfd_helper.find_interface(name=port_name)
549 virt_intf = intf["virtual-interface"]
550 # we only know static traffic id by reading the json
551 # this is used by _get_ixia_trafficrofile
552 port_num = self.vnfd_helper.port_num(intf)
553 mac["src_mac_{}".format(port_num)] = virt_intf.get("local_mac", default)
554 mac["dst_mac_{}".format(port_num)] = virt_intf.get("dst_mac", default)
556 self._ix_scenario.run_protocols()
559 while not self._terminated.value:
560 first_run = traffic_profile.execute_traffic(
561 self, self.client, mac)
562 self.client_started.value = 1
563 # pylint: disable=unnecessary-lambda
564 utils.wait_until_true(lambda: self.client.is_traffic_stopped(),
565 timeout=traffic_profile.config.duration * 2)
566 samples = self.generate_samples(traffic_profile.ports,
567 traffic_profile.config.duration)
569 completed, samples = traffic_profile.get_drop_percentage(
570 samples, min_tol, max_tol, precision, first_run=first_run)
571 self._queue.put(samples)
574 self._terminated.value = 1
576 except Exception: # pylint: disable=broad-except
577 LOG.exception('Run Traffic terminated')
579 self._ix_scenario.stop_protocols()
580 self._terminated.value = 1
582 def collect_kpi(self):
583 self.rfc_helper.iteration.value += 1
584 return super(IxiaResourceHelper, self).collect_kpi()
587 class IxiaTrafficGen(SampleVNFTrafficGen):
591 def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None):
592 if resource_helper_type is None:
593 resource_helper_type = IxiaResourceHelper
595 super(IxiaTrafficGen, self).__init__(name, vnfd, setup_env_helper_type,
596 resource_helper_type)
597 self._ixia_traffic_gen = None
598 self.ixia_file_name = ''
599 self.vnf_port_pairs = []
601 def _check_status(self):
605 self.resource_helper.stop_collect()
606 super(IxiaTrafficGen, self).terminate()