Merge "[docs][glossary] Update the glossary"
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / tg_rfc2544_ixia.py
1 # Copyright (c) 2016-2017 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
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
24
25
26 LOG = logging.getLogger(__name__)
27
28 WAIT_AFTER_CFG_LOAD = 10
29 WAIT_FOR_TRAFFIC = 30
30 WAIT_PROTOCOLS_STARTED = 360
31
32
33 class IxiaBasicScenario(object):
34     def __init__(self, client, context_cfg, ixia_cfg):
35
36         self.client = client
37         self.context_cfg = context_cfg
38         self.ixia_cfg = ixia_cfg
39
40         self._uplink_vports = None
41         self._downlink_vports = None
42
43     def apply_config(self):
44         pass
45
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)
53
54     def run_protocols(self):
55         pass
56
57     def stop_protocols(self):
58         pass
59
60
61 class IxiaPppoeClientScenario(object):
62     def __init__(self, client, context_cfg, ixia_cfg):
63
64         self.client = client
65
66         self._uplink_vports = None
67         self._downlink_vports = None
68
69         self._access_topologies = []
70         self._core_topologies = []
71
72         self._context_cfg = context_cfg
73         self._ixia_cfg = ixia_cfg
74         self.protocols = []
75         self.device_groups = []
76
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()
84
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,
93                                               downlink_endpoints)
94
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)
101
102     def stop_protocols(self):
103         LOG.info('PPPoE Scenario - Stop Protocols')
104         self.client.stop_protocols()
105
106     def _get_intf_addr(self, intf):
107         """Retrieve interface IP address and mask
108
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
113         """
114         if isinstance(intf, six.string_types):
115             ip, mask = tuple(intf.split('/'))
116             return ip, int(mask)
117
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)),
124                                       strict=False)
125         return ip, ipaddr.prefixlen
126
127     @staticmethod
128     def _get_endpoints_src_dst_id_pairs(flows_params):
129         """Get list of flows src/dst port pairs
130
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.
134
135         Example ('port' key represents port on which flow will be created):
136
137         Input flows data:
138         uplink_0:
139           ipv4:
140             id: 1
141             port: xe0
142         downlink_0:
143           ipv4:
144             id: 2
145             port: xe1
146         uplink_1:
147           ipv4:
148             id: 3
149             port: xe2
150         downlink_1:
151           ipv4:
152             id: 4
153             port: xe3
154
155         Result list: ['xe0', 'xe1', 'xe2', 'xe3']
156
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
162
163         :param flows_params: ordered dict of traffic profile flows params
164         :return: (list) list of flows src/dst ports
165         """
166         if len(flows_params) % 2:
167             raise RuntimeError('Number of uplink/downlink pairs'
168                                ' in traffic profile is not equal')
169         endpoint_pairs = []
170         for flow in flows_params:
171             port = flows_params[flow]['ipv4'].get('port')
172             if port is None:
173                 continue
174             endpoint_pairs.append(port)
175         return endpoint_pairs
176
177     def _get_endpoints_src_dst_obj_pairs(self, endpoints_id_pairs):
178         """Create list of uplink/downlink device groups pairs
179
180         Based on traffic profile options, create list of uplink/downlink
181         device groups pairs between which flow groups will be created:
182
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.
186            E.g.:
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:
190            dg1 -> dg4
191            dg4 -> dg1
192            dg2 -> dg4
193            dg4 -> dg2
194            dg3 -> dg4
195            dg4 -> dg3
196
197         2. In case uplink/downlink flows in traffic profile have specified
198            'port' key, flows will be created between device groups on this
199            port.
200            E.g., for the following traffic profile
201            uplink_0:
202              port: xe0
203            downlink_0:
204              port: xe1
205            uplink_1:
206              port: xe0
207            downlink_0:
208              port: xe3
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)
214
215         :param endpoints_id_pairs: (list) List of uplink/downlink flows ports
216          pairs
217         :return: (list) list of uplink/downlink device groups descriptors pairs
218         """
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)
223
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)
228
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
236
237         uplink_endpoints = endpoints_id_pairs[::2]
238         downlink_endpoints = endpoints_id_pairs[1::2]
239
240         uplink_dev_groups = []
241         group_up = [uplink_endpoints[i:i + svlan_count]
242                     for i in range(0, len(uplink_endpoints), svlan_count)]
243
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])
247
248         downlink_dev_groups = []
249         for port in downlink_endpoints:
250             downlink_dev_groups.append(port_to_dev_group_mapping[port][0])
251
252         endpoint_obj_pairs = []
253         [endpoint_obj_pairs.extend([up, down])
254          for up, down in zip(uplink_dev_groups, downlink_dev_groups)]
255
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
264
265     def _fill_ixia_config(self):
266         pppoe = self._ixia_cfg["pppoe_client"]
267         ipv4 = self._ixia_cfg["ipv4_client"]
268
269         _ip = [self._get_intf_addr(intf)[0] for intf in pppoe["ip"]]
270         self._ixia_cfg["pppoe_client"]["ip"] = _ip
271
272         _ip = [self._get_intf_addr(intf)[0] for intf in ipv4["gateway_ip"]]
273         self._ixia_cfg["ipv4_client"]["gateway_ip"] = _ip
274
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]
278
279         self._ixia_cfg["ipv4_client"]["ip"] = _ip
280         self._ixia_cfg["ipv4_client"]["prefix"] = _prefix
281
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)
287
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',
308                                                        pppoe['pap_user'],
309                                                        pppoe['pap_password'])
310                 else:
311                     ppp = self.client.add_pppox_client(ethernet, 'chap',
312                                                        pppoe['chap_user'],
313                                                        pppoe['chap_password'])
314                 self.protocols.append(ppp)
315
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)
321
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)
335                 if 'vlan' in ipv4:
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])
339                 # add ipv4 layer
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',
344                                                 addr=ip_addr,
345                                                 addr_step='0.0.0.1',
346                                                 prefix=ipv4['prefix'][core_tp_id],
347                                                 gateway=gw_ip)
348                 self.protocols.append(ipv4_obj)
349                 if ipv4.get("bgp"):
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)
355
356
357 class IxiaRfc2544Helper(Rfc2544ResourceHelper):
358
359     def is_done(self):
360         return self.latency and self.iteration.value > 10
361
362
363 class IxiaResourceHelper(ClientResourceHelper):
364
365     LATENCY_TIME_SLEEP = 120
366
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
370
371         self._ixia_scenarios = {
372             "IxiaBasic": IxiaBasicScenario,
373             "IxiaPppoeClient": IxiaPppoeClientScenario,
374         }
375
376         self.client = ixnet_api.IxNextgen()
377
378         if rfc_helper_type is None:
379             rfc_helper_type = IxiaRfc2544Helper
380
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
386         self._connect()
387
388     def _connect(self, client=None):
389         self.client.connect(self.vnfd_helper)
390
391     def get_stats(self, *args, **kwargs):
392         return self.client.get_statistics()
393
394     def setup(self):
395         super(IxiaResourceHelper, self).setup()
396         self._init_ix_scenario()
397
398     def stop_collect(self):
399         self._ix_scenario.stop_protocols()
400         self._terminated.value = 1
401
402     def generate_samples(self, ports, duration):
403         stats = self.get_stats()
404
405         samples = {}
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:
409             try:
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)
428                 }
429             except IndexError:
430                 pass
431
432         return samples
433
434     def _init_ix_scenario(self):
435         ixia_config = self.scenario_helper.scenario_cfg.get('ixia_config', 'IxiaBasic')
436
437         if ixia_config in self._ixia_scenarios:
438             scenario_type = self._ixia_scenarios[ixia_config]
439
440             self._ix_scenario = scenario_type(self.client, self.context_cfg,
441                                               self.scenario_helper.scenario_cfg['options'])
442         else:
443             raise RuntimeError(
444                 "IXIA config type '{}' not supported".format(ixia_config))
445
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)
452
453     def run_traffic(self, traffic_profile, *args):
454         if self._terminated.value:
455             return
456
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"
461
462         self._build_ports()
463         traffic_profile.update_traffic_profile(self)
464         self._initialize_client(traffic_profile)
465
466         mac = {}
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)
475
476         self._ix_scenario.run_protocols()
477
478         try:
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)
488
489                 completed, samples = traffic_profile.get_drop_percentage(
490                     samples, min_tol, max_tol, precision, first_run=first_run)
491                 self._queue.put(samples)
492
493                 if completed:
494                     self._terminated.value = 1
495
496         except Exception:  # pylint: disable=broad-except
497             LOG.exception('Run Traffic terminated')
498
499         self._ix_scenario.stop_protocols()
500         self._terminated.value = 1
501
502     def collect_kpi(self):
503         self.rfc_helper.iteration.value += 1
504         return super(IxiaResourceHelper, self).collect_kpi()
505
506
507 class IxiaTrafficGen(SampleVNFTrafficGen):
508
509     APP_NAME = 'Ixia'
510
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 = []
520
521     def _check_status(self):
522         pass
523
524     def terminate(self):
525         self.resource_helper.stop_collect()
526         super(IxiaTrafficGen, self).terminate()