2f9eb3f0de77ddd88218256469ae4082e405a039
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / tg_rfc2544_ixia.py
1 # Copyright (c) 2016-2019 Intel Corporation
2 #
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
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
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
15 import ipaddress
16 import logging
17 import six
18 import collections
19
20 from six import moves
21 from yardstick.common import utils
22 from yardstick.common import exceptions
23 from yardstick.network_services.libs.ixia_libs.ixnet import ixnet_api
24 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNFTrafficGen
25 from yardstick.network_services.vnf_generic.vnf.sample_vnf import ClientResourceHelper
26 from yardstick.network_services.vnf_generic.vnf.sample_vnf import Rfc2544ResourceHelper
27
28
29 LOG = logging.getLogger(__name__)
30
31 WAIT_AFTER_CFG_LOAD = 10
32 WAIT_FOR_TRAFFIC = 30
33 WAIT_PROTOCOLS_STARTED = 420
34
35
36 class IxiaBasicScenario(object):
37     """Ixia Basic scenario for flow from port to port"""
38
39     def __init__(self, client, context_cfg, ixia_cfg):
40
41         self.client = client
42         self.context_cfg = context_cfg
43         self.ixia_cfg = ixia_cfg
44
45         self._uplink_vports = None
46         self._downlink_vports = None
47
48     def apply_config(self):
49         pass
50
51     def run_protocols(self):
52         pass
53
54     def stop_protocols(self):
55         pass
56
57     def create_traffic_model(self, traffic_profile=None):
58         # pylint: disable=unused-argument
59         vports = self.client.get_vports()
60         self._uplink_vports = vports[::2]
61         self._downlink_vports = vports[1::2]
62         self.client.create_traffic_model(self._uplink_vports,
63                                          self._downlink_vports)
64
65     def _get_stats(self):
66         return self.client.get_statistics()
67
68     def generate_samples(self, resource_helper, ports, duration):
69         stats = self._get_stats()
70
71         samples = {}
72         # this is not DPDK port num, but this is whatever number we gave
73         # when we selected ports and programmed the profile
74         for port_num in ports:
75             try:
76                 # reverse lookup port name from port_num so the stats dict is descriptive
77                 intf = resource_helper.vnfd_helper.find_interface_by_port(port_num)
78                 port_name = intf['name']
79                 avg_latency = stats['Store-Forward_Avg_latency_ns'][port_num]
80                 min_latency = stats['Store-Forward_Min_latency_ns'][port_num]
81                 max_latency = stats['Store-Forward_Max_latency_ns'][port_num]
82                 samples[port_name] = {
83                     'rx_throughput_kps': float(stats['Rx_Rate_Kbps'][port_num]),
84                     'tx_throughput_kps': float(stats['Tx_Rate_Kbps'][port_num]),
85                     'rx_throughput_mbps': float(stats['Rx_Rate_Mbps'][port_num]),
86                     'tx_throughput_mbps': float(stats['Tx_Rate_Mbps'][port_num]),
87                     'in_packets': int(stats['Valid_Frames_Rx'][port_num]),
88                     'out_packets': int(stats['Frames_Tx'][port_num]),
89                     'RxThroughput': float(stats['Valid_Frames_Rx'][port_num]) / duration,
90                     'TxThroughput': float(stats['Frames_Tx'][port_num]) / duration,
91                     'Store-Forward_Avg_latency_ns': utils.safe_cast(avg_latency, int, 0),
92                     'Store-Forward_Min_latency_ns': utils.safe_cast(min_latency, int, 0),
93                     'Store-Forward_Max_latency_ns': utils.safe_cast(max_latency, int, 0)
94                 }
95             except IndexError:
96                 pass
97
98         return samples
99
100     def update_tracking_options(self):
101         pass
102
103     def get_tc_rfc2544_options(self):
104         pass
105
106
107 class IxiaL3Scenario(IxiaBasicScenario):
108     """Ixia scenario for L3 flow between static ip's"""
109
110     def _add_static_ips(self):
111         vports = self.client.get_vports()
112         uplink_intf_vport = [(self.client.get_static_interface(vport), vport)
113                              for vport in vports[::2]]
114         downlink_intf_vport = [(self.client.get_static_interface(vport), vport)
115                                for vport in vports[1::2]]
116
117         for index in range(len(uplink_intf_vport)):
118             intf, vport = uplink_intf_vport[index]
119             try:
120                 iprange = self.ixia_cfg['flow'].get('src_ip')[index]
121                 start_ip = utils.get_ip_range_start(iprange)
122                 count = utils.get_ip_range_count(iprange)
123                 self.client.add_static_ipv4(intf, vport, start_ip, count, '32')
124             except IndexError:
125                 raise exceptions.IncorrectFlowOption(
126                     option="src_ip", link="uplink_{}".format(index))
127
128             intf, vport = downlink_intf_vport[index]
129             try:
130                 iprange = self.ixia_cfg['flow'].get('dst_ip')[index]
131                 start_ip = utils.get_ip_range_start(iprange)
132                 count = utils.get_ip_range_count(iprange)
133                 self.client.add_static_ipv4(intf, vport, start_ip, count, '32')
134             except IndexError:
135                 raise exceptions.IncorrectFlowOption(
136                     option="dst_ip", link="downlink_{}".format(index))
137
138     def _add_interfaces(self):
139         vports = self.client.get_vports()
140         uplink_vports = (vport for vport in vports[::2])
141         downlink_vports = (vport for vport in vports[1::2])
142
143         ix_node = next(node for _, node in self.context_cfg['nodes'].items()
144                        if node['role'] == 'IxNet')
145
146         for intf in ix_node['interfaces'].values():
147             ip = intf.get('local_ip')
148             mac = intf.get('local_mac')
149             gateway = None
150             try:
151                 gateway = next(route.get('gateway')
152                                for route in ix_node.get('routing_table')
153                                if route.get('if') == intf.get('ifname'))
154             except StopIteration:
155                 LOG.debug("Gateway not provided")
156
157             if 'uplink' in intf.get('vld_id'):
158                 self.client.add_interface(next(uplink_vports),
159                                           ip, mac, gateway)
160             else:
161                 self.client.add_interface(next(downlink_vports),
162                                           ip, mac, gateway)
163
164     def apply_config(self):
165         self._add_interfaces()
166         self._add_static_ips()
167
168     def create_traffic_model(self, traffic_profile=None):
169         # pylint: disable=unused-argument
170         vports = self.client.get_vports()
171         self._uplink_vports = vports[::2]
172         self._downlink_vports = vports[1::2]
173
174         uplink_endpoints = [port + '/protocols/static'
175                             for port in self._uplink_vports]
176         downlink_endpoints = [port + '/protocols/static'
177                               for port in self._downlink_vports]
178
179         self.client.create_ipv4_traffic_model(uplink_endpoints,
180                                               downlink_endpoints)
181
182
183 class IxiaPppoeClientScenario(object):
184     def __init__(self, client, context_cfg, ixia_cfg):
185
186         self.client = client
187
188         self._uplink_vports = None
189         self._downlink_vports = None
190
191         self._access_topologies = []
192         self._core_topologies = []
193
194         self._context_cfg = context_cfg
195         self._ixia_cfg = ixia_cfg
196         self.protocols = []
197         self.device_groups = []
198
199     def apply_config(self):
200         vports = self.client.get_vports()
201         self._uplink_vports = vports[::2]
202         self._downlink_vports = vports[1::2]
203         self._fill_ixia_config()
204         self._apply_access_network_config()
205         self._apply_core_network_config()
206
207     def create_traffic_model(self, traffic_profile):
208         endpoints_id_pairs = self._get_endpoints_src_dst_id_pairs(
209             traffic_profile.full_profile)
210         endpoints_obj_pairs = \
211             self._get_endpoints_src_dst_obj_pairs(endpoints_id_pairs)
212         if endpoints_obj_pairs:
213             uplink_endpoints = endpoints_obj_pairs[::2]
214             downlink_endpoints = endpoints_obj_pairs[1::2]
215         else:
216             uplink_endpoints = self._access_topologies
217             downlink_endpoints = self._core_topologies
218         self.client.create_ipv4_traffic_model(uplink_endpoints,
219                                               downlink_endpoints)
220
221     def run_protocols(self):
222         LOG.info('PPPoE Scenario - Start Protocols')
223         self.client.start_protocols()
224         utils.wait_until_true(
225             lambda: self.client.is_protocols_running(self.protocols),
226             timeout=WAIT_PROTOCOLS_STARTED, sleep=2)
227
228     def stop_protocols(self):
229         LOG.info('PPPoE Scenario - Stop Protocols')
230         self.client.stop_protocols()
231
232     def _get_intf_addr(self, intf):
233         """Retrieve interface IP address and mask
234
235         :param intf: could be the string which represents IP address
236         with mask (e.g 192.168.10.2/24) or a dictionary with the host
237         name and the port (e.g. {'tg__0': 'xe1'})
238         :return: (tuple) pair of ip address and mask
239         """
240         if isinstance(intf, six.string_types):
241             ip, mask = tuple(intf.split('/'))
242             return ip, int(mask)
243
244         node_name, intf_name = next(iter(intf.items()))
245         node = self._context_cfg["nodes"].get(node_name, {})
246         interface = node.get("interfaces", {})[intf_name]
247         ip = interface["local_ip"]
248         mask = interface["netmask"]
249         ipaddr = ipaddress.ip_network(six.text_type('{}/{}'.format(ip, mask)),
250                                       strict=False)
251         return ip, ipaddr.prefixlen
252
253     @staticmethod
254     def _get_endpoints_src_dst_id_pairs(flows_params):
255         """Get list of flows src/dst port pairs
256
257         Create list of flows src/dst port pairs based on traffic profile
258         flows data. Each uplink/downlink pair in traffic profile represents
259         specific flows between the pair of ports.
260
261         Example ('port' key represents port on which flow will be created):
262
263         Input flows data:
264         uplink_0:
265           ipv4:
266             id: 1
267             port: xe0
268         downlink_0:
269           ipv4:
270             id: 2
271             port: xe1
272         uplink_1:
273           ipv4:
274             id: 3
275             port: xe2
276         downlink_1:
277           ipv4:
278             id: 4
279             port: xe3
280
281         Result list: ['xe0', 'xe1', 'xe2', 'xe3']
282
283         Result list means that the following flows pairs will be created:
284         - uplink 0: port xe0 <-> port xe1
285         - downlink 0: port xe1 <-> port xe0
286         - uplink 1: port xe2 <-> port xe3
287         - downlink 1: port xe3 <-> port xe2
288
289         :param flows_params: ordered dict of traffic profile flows params
290         :return: (list) list of flows src/dst ports
291         """
292         if len(flows_params) % 2:
293             raise RuntimeError('Number of uplink/downlink pairs'
294                                ' in traffic profile is not equal')
295         endpoint_pairs = []
296         for flow in flows_params:
297             port = flows_params[flow]['ipv4'].get('port')
298             if port is None:
299                 continue
300             endpoint_pairs.append(port)
301         return endpoint_pairs
302
303     def _get_endpoints_src_dst_obj_pairs(self, endpoints_id_pairs):
304         """Create list of uplink/downlink device groups pairs
305
306         Based on traffic profile options, create list of uplink/downlink
307         device groups pairs between which flow groups will be created:
308
309         1. In case uplink/downlink flows in traffic profile doesn't have
310            specified 'port' key, flows will be created between topologies
311            on corresponding access and core port.
312            E.g.:
313            Access topology on xe0: topology1
314            Core topology on xe1: topology2
315            Flows will be created between:
316            topology1 -> topology2
317            topology2 -> topology1
318
319         2. In case uplink/downlink flows in traffic profile have specified
320            'port' key, flows will be created between device groups on this
321            port.
322            E.g., for the following traffic profile
323            uplink_0:
324              port: xe0
325            downlink_0:
326              port: xe1
327            uplink_1:
328              port: xe0
329            downlink_0:
330              port: xe3
331            Flows will be created between:
332            Port xe0 (dg1) -> Port xe1 (dg1)
333            Port xe1 (dg1) -> Port xe0 (dg1)
334            Port xe0 (dg2) -> Port xe3 (dg1)
335            Port xe3 (dg3) -> Port xe0 (dg1)
336
337         :param endpoints_id_pairs: (list) List of uplink/downlink flows ports
338          pairs
339         :return: (list) list of uplink/downlink device groups descriptors pairs
340         """
341         pppoe = self._ixia_cfg['pppoe_client']
342         sessions_per_port = pppoe['sessions_per_port']
343         sessions_per_svlan = pppoe['sessions_per_svlan']
344         svlan_count = int(sessions_per_port / sessions_per_svlan)
345
346         uplink_ports = [p['tg__0'] for p in self._ixia_cfg['flow']['src_ip']]
347         downlink_ports = [p['tg__0'] for p in self._ixia_cfg['flow']['dst_ip']]
348         uplink_port_topology_map = zip(uplink_ports, self._access_topologies)
349         downlink_port_topology_map = zip(downlink_ports, self._core_topologies)
350
351         port_to_dev_group_mapping = {}
352         for port, topology in uplink_port_topology_map:
353             topology_dgs = self.client.get_topology_device_groups(topology)
354             port_to_dev_group_mapping[port] = topology_dgs
355         for port, topology in downlink_port_topology_map:
356             topology_dgs = self.client.get_topology_device_groups(topology)
357             port_to_dev_group_mapping[port] = topology_dgs
358
359         uplink_endpoints = endpoints_id_pairs[::2]
360         downlink_endpoints = endpoints_id_pairs[1::2]
361
362         uplink_dev_groups = []
363         group_up = [uplink_endpoints[i:i + svlan_count]
364                     for i in range(0, len(uplink_endpoints), svlan_count)]
365
366         for group in group_up:
367             for i, port in enumerate(group):
368                 uplink_dev_groups.append(port_to_dev_group_mapping[port][i])
369
370         downlink_dev_groups = []
371         for port in downlink_endpoints:
372             downlink_dev_groups.append(port_to_dev_group_mapping[port][0])
373
374         endpoint_obj_pairs = []
375         [endpoint_obj_pairs.extend([up, down])
376          for up, down in zip(uplink_dev_groups, downlink_dev_groups)]
377
378         return endpoint_obj_pairs
379
380     def _fill_ixia_config(self):
381         pppoe = self._ixia_cfg["pppoe_client"]
382         ipv4 = self._ixia_cfg["ipv4_client"]
383
384         _ip = [self._get_intf_addr(intf)[0] for intf in pppoe["ip"]]
385         self._ixia_cfg["pppoe_client"]["ip"] = _ip
386
387         _ip = [self._get_intf_addr(intf)[0] for intf in ipv4["gateway_ip"]]
388         self._ixia_cfg["ipv4_client"]["gateway_ip"] = _ip
389
390         addrs = [self._get_intf_addr(intf) for intf in ipv4["ip"]]
391         _ip = [addr[0] for addr in addrs]
392         _prefix = [addr[1] for addr in addrs]
393
394         self._ixia_cfg["ipv4_client"]["ip"] = _ip
395         self._ixia_cfg["ipv4_client"]["prefix"] = _prefix
396
397     def _apply_access_network_config(self):
398         pppoe = self._ixia_cfg["pppoe_client"]
399         sessions_per_port = pppoe['sessions_per_port']
400         sessions_per_svlan = pppoe['sessions_per_svlan']
401         svlan_count = int(sessions_per_port / sessions_per_svlan)
402
403         # add topology per uplink port (access network)
404         for access_tp_id, vport in enumerate(self._uplink_vports):
405             name = 'Topology access {}'.format(access_tp_id)
406             tp = self.client.add_topology(name, vport)
407             self._access_topologies.append(tp)
408             # add device group per svlan
409             for dg_id in range(svlan_count):
410                 s_vlan_id = int(pppoe['s_vlan']) + dg_id + access_tp_id * svlan_count
411                 s_vlan = ixnet_api.Vlan(vlan_id=s_vlan_id)
412                 c_vlan = ixnet_api.Vlan(vlan_id=pppoe['c_vlan'], vlan_id_step=1)
413                 name = 'SVLAN {}'.format(s_vlan_id)
414                 dg = self.client.add_device_group(tp, name, sessions_per_svlan)
415                 self.device_groups.append(dg)
416                 # add ethernet layer to device group
417                 ethernet = self.client.add_ethernet(dg, 'Ethernet')
418                 self.protocols.append(ethernet)
419                 self.client.add_vlans(ethernet, [s_vlan, c_vlan])
420                 # add ppp over ethernet
421                 if 'pap_user' in pppoe:
422                     ppp = self.client.add_pppox_client(ethernet, 'pap',
423                                                        pppoe['pap_user'],
424                                                        pppoe['pap_password'])
425                 else:
426                     ppp = self.client.add_pppox_client(ethernet, 'chap',
427                                                        pppoe['chap_user'],
428                                                        pppoe['chap_password'])
429                 self.protocols.append(ppp)
430
431     def _apply_core_network_config(self):
432         ipv4 = self._ixia_cfg["ipv4_client"]
433         sessions_per_port = ipv4['sessions_per_port']
434         sessions_per_vlan = ipv4['sessions_per_vlan']
435         vlan_count = int(sessions_per_port / sessions_per_vlan)
436
437         # add topology per downlink port (core network)
438         for core_tp_id, vport in enumerate(self._downlink_vports):
439             name = 'Topology core {}'.format(core_tp_id)
440             tp = self.client.add_topology(name, vport)
441             self._core_topologies.append(tp)
442             # add device group per vlan
443             for dg_id in range(vlan_count):
444                 name = 'Core port {}'.format(core_tp_id)
445                 dg = self.client.add_device_group(tp, name, sessions_per_vlan)
446                 self.device_groups.append(dg)
447                 # add ethernet layer to device group
448                 ethernet = self.client.add_ethernet(dg, 'Ethernet')
449                 self.protocols.append(ethernet)
450                 if 'vlan' in ipv4:
451                     vlan_id = int(ipv4['vlan']) + dg_id + core_tp_id * vlan_count
452                     vlan = ixnet_api.Vlan(vlan_id=vlan_id)
453                     self.client.add_vlans(ethernet, [vlan])
454                 # add ipv4 layer
455                 gw_ip = ipv4['gateway_ip'][core_tp_id]
456                 # use gw addr to generate ip addr from the same network
457                 ip_addr = ipaddress.IPv4Address(gw_ip) + 1
458                 ipv4_obj = self.client.add_ipv4(ethernet, name='ipv4',
459                                                 addr=ip_addr,
460                                                 addr_step='0.0.0.1',
461                                                 prefix=ipv4['prefix'][core_tp_id],
462                                                 gateway=gw_ip)
463                 self.protocols.append(ipv4_obj)
464                 if ipv4.get("bgp"):
465                     bgp_peer_obj = self.client.add_bgp(ipv4_obj,
466                                                        dut_ip=ipv4["bgp"]["dut_ip"],
467                                                        local_as=ipv4["bgp"]["as_number"],
468                                                        bgp_type=ipv4["bgp"].get("bgp_type"))
469                     self.protocols.append(bgp_peer_obj)
470
471     def update_tracking_options(self):
472         priority_map = {
473             'raw': 'ipv4Raw0',
474             'tos': {'precedence': 'ipv4Precedence0'},
475             'dscp': {'defaultPHB': 'ipv4DefaultPhb0',
476                      'selectorPHB': 'ipv4ClassSelectorPhb0',
477                      'assuredPHB': 'ipv4AssuredForwardingPhb0',
478                      'expeditedPHB': 'ipv4ExpeditedForwardingPhb0'}
479         }
480
481         prio_trackby_key = 'ipv4Precedence0'
482
483         try:
484             priority = list(self._ixia_cfg['priority'])[0]
485             if priority == 'raw':
486                 prio_trackby_key = priority_map[priority]
487             elif priority in ['tos', 'dscp']:
488                 priority_type = list(self._ixia_cfg['priority'][priority])[0]
489                 prio_trackby_key = priority_map[priority][priority_type]
490         except KeyError:
491             pass
492
493         tracking_options = ['flowGroup0', 'vlanVlanId0', prio_trackby_key]
494         self.client.set_flow_tracking(tracking_options)
495
496     def get_tc_rfc2544_options(self):
497         return self._ixia_cfg.get('rfc2544')
498
499     def _get_stats(self):
500         return self.client.get_pppoe_scenario_statistics()
501
502     @staticmethod
503     def get_flow_id_data(stats, flow_id, key):
504         result = [float(flow.get(key)) for flow in stats if flow['id'] == flow_id]
505         return sum(result) / len(result)
506
507     def get_priority_flows_stats(self, samples, duration):
508         results = {}
509         priorities = set([flow['IP_Priority'] for flow in samples])
510         for priority in priorities:
511             tx_frames = sum(
512                 [int(flow['Tx_Frames']) for flow in samples
513                  if flow['IP_Priority'] == priority])
514             rx_frames = sum(
515                 [int(flow['Rx_Frames']) for flow in samples
516                  if flow['IP_Priority'] == priority])
517             prio_flows_num = len([flow for flow in samples
518                                   if flow['IP_Priority'] == priority])
519             avg_latency_ns = sum(
520                 [int(flow['Store-Forward_Avg_latency_ns']) for flow in samples
521                  if flow['IP_Priority'] == priority]) / prio_flows_num
522             min_latency_ns = sum(
523                 [int(flow['Store-Forward_Min_latency_ns']) for flow in samples
524                  if flow['IP_Priority'] == priority]) / prio_flows_num
525             max_latency_ns = sum(
526                 [int(flow['Store-Forward_Max_latency_ns']) for flow in samples
527                  if flow['IP_Priority'] == priority]) / prio_flows_num
528             tx_throughput = float(tx_frames) / duration
529             rx_throughput = float(rx_frames) / duration
530             results[priority] = {
531                 'in_packets': rx_frames,
532                 'out_packets': tx_frames,
533                 'RxThroughput': round(rx_throughput, 3),
534                 'TxThroughput': round(tx_throughput, 3),
535                 'avg_latency_ns': utils.safe_cast(avg_latency_ns, int, 0),
536                 'min_latency_ns': utils.safe_cast(min_latency_ns, int, 0),
537                 'max_latency_ns': utils.safe_cast(max_latency_ns, int, 0)
538             }
539         return results
540
541     def generate_samples(self, resource_helper, ports, duration):
542
543         stats = self._get_stats()
544         samples = {}
545         ports_stats = stats['port_statistics']
546         flows_stats = stats['flow_statistic']
547         pppoe_subs_per_port = stats['pppox_client_per_port']
548
549         # Get sorted list of ixia ports names
550         ixia_port_names = sorted([data['port_name'] for data in ports_stats])
551
552         # Set 'port_id' key for ports stats items
553         for item in ports_stats:
554             port_id = item.pop('port_name').split('-')[-1].strip()
555             item['port_id'] = int(port_id)
556
557         # Set 'id' key for flows stats items
558         for item in flows_stats:
559             flow_id = item.pop('Flow_Group').split('-')[1].strip()
560             item['id'] = int(flow_id)
561
562         # Set 'port_id' key for pppoe subs per port stats
563         for item in pppoe_subs_per_port:
564             port_id = item.pop('subs_port').split('-')[-1].strip()
565             item['port_id'] = int(port_id)
566
567         # Map traffic flows to ports
568         port_flow_map = collections.defaultdict(set)
569         for item in flows_stats:
570             tx_port = item.pop('Tx_Port')
571             tx_port_index = ixia_port_names.index(tx_port)
572             port_flow_map[tx_port_index].update([item['id']])
573
574         # Sort ports stats
575         ports_stats = sorted(ports_stats, key=lambda k: k['port_id'])
576
577         # Get priority flows stats
578         prio_flows_stats = self.get_priority_flows_stats(flows_stats, duration)
579         samples['priority_stats'] = prio_flows_stats
580
581         # this is not DPDK port num, but this is whatever number we gave
582         # when we selected ports and programmed the profile
583         for port_num in ports:
584             try:
585                 # reverse lookup port name from port_num so the stats dict is descriptive
586                 intf = resource_helper.vnfd_helper.find_interface_by_port(port_num)
587                 port_name = intf['name']
588                 port_id = ports_stats[port_num]['port_id']
589                 port_subs_stats = \
590                     [port_data for port_data in pppoe_subs_per_port
591                      if port_data.get('port_id') == port_id]
592
593                 avg_latency = \
594                     sum([float(self.get_flow_id_data(
595                         flows_stats, flow, 'Store-Forward_Avg_latency_ns'))
596                         for flow in port_flow_map[port_num]]) / len(port_flow_map[port_num])
597                 min_latency = \
598                     sum([float(self.get_flow_id_data(
599                         flows_stats, flow, 'Store-Forward_Min_latency_ns'))
600                         for flow in port_flow_map[port_num]]) / len(port_flow_map[port_num])
601                 max_latency = \
602                     sum([float(self.get_flow_id_data(
603                         flows_stats, flow, 'Store-Forward_Max_latency_ns'))
604                         for flow in port_flow_map[port_num]]) / len(port_flow_map[port_num])
605
606                 samples[port_name] = {
607                     'rx_throughput_kps': float(ports_stats[port_num]['Rx_Rate_Kbps']),
608                     'tx_throughput_kps': float(ports_stats[port_num]['Tx_Rate_Kbps']),
609                     'rx_throughput_mbps': float(ports_stats[port_num]['Rx_Rate_Mbps']),
610                     'tx_throughput_mbps': float(ports_stats[port_num]['Tx_Rate_Mbps']),
611                     'in_packets': int(ports_stats[port_num]['Valid_Frames_Rx']),
612                     'out_packets': int(ports_stats[port_num]['Frames_Tx']),
613                     'RxThroughput': float(ports_stats[port_num]['Valid_Frames_Rx']) / duration,
614                     'TxThroughput': float(ports_stats[port_num]['Frames_Tx']) / duration,
615                     'Store-Forward_Avg_latency_ns': utils.safe_cast(avg_latency, int, 0),
616                     'Store-Forward_Min_latency_ns': utils.safe_cast(min_latency, int, 0),
617                     'Store-Forward_Max_latency_ns': utils.safe_cast(max_latency, int, 0)
618                 }
619
620                 if port_subs_stats:
621                     samples[port_name].update(
622                         {'sessions_up': int(port_subs_stats[0]['Sessions_Up']),
623                          'sessions_down': int(port_subs_stats[0]['Sessions_Down']),
624                          'sessions_not_started': int(port_subs_stats[0]['Sessions_Not_Started']),
625                          'sessions_total': int(port_subs_stats[0]['Sessions_Total'])}
626                     )
627
628             except IndexError:
629                 pass
630
631         return samples
632
633
634 class IxiaRfc2544Helper(Rfc2544ResourceHelper):
635
636     def is_done(self):
637         return self.latency and self.iteration.value > 10
638
639
640 class IxiaResourceHelper(ClientResourceHelper):
641
642     LATENCY_TIME_SLEEP = 120
643
644     def __init__(self, setup_helper, rfc_helper_type=None):
645         super(IxiaResourceHelper, self).__init__(setup_helper)
646         self.scenario_helper = setup_helper.scenario_helper
647
648         self._ixia_scenarios = {
649             "IxiaBasic": IxiaBasicScenario,
650             "IxiaL3": IxiaL3Scenario,
651             "IxiaPppoeClient": IxiaPppoeClientScenario,
652         }
653
654         self.client = ixnet_api.IxNextgen()
655
656         if rfc_helper_type is None:
657             rfc_helper_type = IxiaRfc2544Helper
658
659         self.rfc_helper = rfc_helper_type(self.scenario_helper)
660         self.uplink_ports = None
661         self.downlink_ports = None
662         self.context_cfg = None
663         self._ix_scenario = None
664         self._connect()
665
666     def _connect(self, client=None):
667         self.client.connect(self.vnfd_helper)
668
669     def setup(self):
670         super(IxiaResourceHelper, self).setup()
671         self._init_ix_scenario()
672
673     def stop_collect(self):
674         self._ix_scenario.stop_protocols()
675         self._terminated.value = 1
676
677     def generate_samples(self, ports, duration):
678         return self._ix_scenario.generate_samples(self, ports, duration)
679
680     def _init_ix_scenario(self):
681         ixia_config = self.scenario_helper.scenario_cfg.get('ixia_config', 'IxiaBasic')
682
683         if ixia_config in self._ixia_scenarios:
684             scenario_type = self._ixia_scenarios[ixia_config]
685
686             self._ix_scenario = scenario_type(self.client, self.context_cfg,
687                                               self.scenario_helper.scenario_cfg['options'])
688         else:
689             raise RuntimeError(
690                 "IXIA config type '{}' not supported".format(ixia_config))
691
692     def _initialize_client(self, traffic_profile):
693         """Initialize the IXIA IxNetwork client and configure the server"""
694         self.client.clear_config()
695         self.client.assign_ports()
696         self._ix_scenario.apply_config()
697         self._ix_scenario.create_traffic_model(traffic_profile)
698
699     def update_tracking_options(self):
700         self._ix_scenario.update_tracking_options()
701
702     def run_traffic(self, traffic_profile):
703         if self._terminated.value:
704             return
705
706         min_tol = self.rfc_helper.tolerance_low
707         max_tol = self.rfc_helper.tolerance_high
708         precision = self.rfc_helper.tolerance_precision
709         resolution = self.rfc_helper.resolution
710         default = "00:00:00:00:00:00"
711
712         self._build_ports()
713         traffic_profile.update_traffic_profile(self)
714         self._initialize_client(traffic_profile)
715
716         mac = {}
717         for port_name in self.vnfd_helper.port_pairs.all_ports:
718             intf = self.vnfd_helper.find_interface(name=port_name)
719             virt_intf = intf["virtual-interface"]
720             # we only know static traffic id by reading the json
721             # this is used by _get_ixia_trafficrofile
722             port_num = self.vnfd_helper.port_num(intf)
723             mac["src_mac_{}".format(port_num)] = virt_intf.get("local_mac", default)
724             mac["dst_mac_{}".format(port_num)] = virt_intf.get("dst_mac", default)
725
726         self._ix_scenario.run_protocols()
727
728         try:
729             while not self._terminated.value:
730                 first_run = traffic_profile.execute_traffic(self, self.client,
731                                                             mac)
732                 self.client_started.value = 1
733                 # pylint: disable=unnecessary-lambda
734                 utils.wait_until_true(lambda: self.client.is_traffic_stopped(),
735                                       timeout=traffic_profile.config.duration * 2)
736                 rfc2544_opts = self._ix_scenario.get_tc_rfc2544_options()
737                 samples = self.generate_samples(traffic_profile.ports,
738                                                 traffic_profile.config.duration)
739
740                 completed, samples = traffic_profile.get_drop_percentage(
741                     samples, min_tol, max_tol, precision, resolution,
742                     first_run=first_run, tc_rfc2544_opts=rfc2544_opts)
743                 self._queue.put(samples)
744
745                 if completed:
746                     self._terminated.value = 1
747
748         except Exception:  # pylint: disable=broad-except
749             LOG.exception('Run Traffic terminated')
750
751         self._ix_scenario.stop_protocols()
752         self.client_started.value = 0
753         self._terminated.value = 1
754
755     def run_test(self, traffic_profile, tasks_queue, results_queue, *args): # pragma: no cover
756         LOG.info("Ixia resource_helper run_test")
757         if self._terminated.value:
758             return
759
760         min_tol = self.rfc_helper.tolerance_low
761         max_tol = self.rfc_helper.tolerance_high
762         precision = self.rfc_helper.tolerance_precision
763         resolution = self.rfc_helper.resolution
764         default = "00:00:00:00:00:00"
765
766         self._build_ports()
767         traffic_profile.update_traffic_profile(self)
768         self._initialize_client(traffic_profile)
769
770         mac = {}
771         for port_name in self.vnfd_helper.port_pairs.all_ports:
772             intf = self.vnfd_helper.find_interface(name=port_name)
773             virt_intf = intf["virtual-interface"]
774             # we only know static traffic id by reading the json
775             # this is used by _get_ixia_trafficrofile
776             port_num = self.vnfd_helper.port_num(intf)
777             mac["src_mac_{}".format(port_num)] = virt_intf.get("local_mac", default)
778             mac["dst_mac_{}".format(port_num)] = virt_intf.get("dst_mac", default)
779
780         self._ix_scenario.run_protocols()
781
782         try:
783             completed = False
784             self.rfc_helper.iteration.value = 0
785             self.client_started.value = 1
786             while completed is False and not self._terminated.value:
787                 LOG.info("Wait for task ...")
788
789                 try:
790                     task = tasks_queue.get(True, 5)
791                 except moves.queue.Empty:
792                     continue
793                 else:
794                     if task != 'RUN_TRAFFIC':
795                         continue
796
797                 self.rfc_helper.iteration.value += 1
798                 LOG.info("Got %s task, start iteration %d", task,
799                          self.rfc_helper.iteration.value)
800                 first_run = traffic_profile.execute_traffic(self, self.client,
801                                                             mac)
802                 # pylint: disable=unnecessary-lambda
803                 utils.wait_until_true(lambda: self.client.is_traffic_stopped(),
804                                       timeout=traffic_profile.config.duration * 2)
805                 samples = self.generate_samples(traffic_profile.ports,
806                                                 traffic_profile.config.duration)
807
808                 completed, samples = traffic_profile.get_drop_percentage(
809                     samples, min_tol, max_tol, precision, resolution,
810                     first_run=first_run)
811                 samples['Iteration'] = self.rfc_helper.iteration.value
812                 self._queue.put(samples)
813
814                 if completed:
815                     LOG.debug("IxiaResourceHelper::run_test - test completed")
816                     results_queue.put('COMPLETE')
817                 else:
818                     results_queue.put('CONTINUE')
819                 tasks_queue.task_done()
820
821         except Exception:  # pylint: disable=broad-except
822             LOG.exception('Run Traffic terminated')
823
824         self._ix_scenario.stop_protocols()
825         self.client_started.value = 0
826         LOG.debug("IxiaResourceHelper::run_test done")
827
828
829 class IxiaTrafficGen(SampleVNFTrafficGen):
830
831     APP_NAME = 'Ixia'
832
833     def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None):
834         if resource_helper_type is None:
835             resource_helper_type = IxiaResourceHelper
836
837         super(IxiaTrafficGen, self).__init__(name, vnfd, setup_env_helper_type,
838                                              resource_helper_type)
839         self._ixia_traffic_gen = None
840         self.ixia_file_name = ''
841         self.vnf_port_pairs = []
842
843     def _check_status(self):
844         pass
845
846     def terminate(self):
847         self.resource_helper.stop_collect()
848         super(IxiaTrafficGen, self).terminate()