Merge "Add send socket commands function"
[yardstick.git] / yardstick / network_services / libs / ixia_libs / ixnet / ixnet_api.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 logging
16
17 import IxNetwork
18
19 from yardstick.common import exceptions
20 from yardstick.common import utils
21
22
23 log = logging.getLogger(__name__)
24
25 IP_VERSION_4 = 4
26 IP_VERSION_6 = 6
27
28 PROTO_ETHERNET = 'ethernet'
29 PROTO_IPV4 = 'ipv4'
30 PROTO_IPV6 = 'ipv6'
31 PROTO_UDP = 'udp'
32 PROTO_TCP = 'tcp'
33 PROTO_VLAN = 'vlan'
34
35 IP_VERSION_4_MASK = '0.0.0.255'
36 IP_VERSION_6_MASK = '0:0:0:0:0:0:0:ff'
37
38 TRAFFIC_STATUS_STARTED = 'started'
39 TRAFFIC_STATUS_STOPPED = 'stopped'
40
41
42 # NOTE(ralonsoh): this pragma will be removed in the last patch of this series
43 class IxNextgen(object):  # pragma: no cover
44
45     PORT_STATS_NAME_MAP = {
46         "stat_name": 'Stat Name',
47         "Frames_Tx": 'Frames Tx.',
48         "Valid_Frames_Rx": 'Valid Frames Rx.',
49         "Frames_Tx_Rate": 'Frames Tx. Rate',
50         "Valid_Frames_Rx_Rate": 'Valid Frames Rx. Rate',
51         "Tx_Rate_Kbps": 'Tx. Rate (Kbps)',
52         "Rx_Rate_Kbps": 'Rx. Rate (Kbps)',
53         "Tx_Rate_Mbps": 'Tx. Rate (Mbps)',
54         "Rx_Rate_Mbps": 'Rx. Rate (Mbps)',
55     }
56
57     LATENCY_NAME_MAP = {
58         "Store-Forward_Avg_latency_ns": 'Store-Forward Avg Latency (ns)',
59         "Store-Forward_Min_latency_ns": 'Store-Forward Min Latency (ns)',
60         "Store-Forward_Max_latency_ns": 'Store-Forward Max Latency (ns)',
61     }
62
63     @staticmethod
64     def get_config(tg_cfg):
65         card = []
66         port = []
67         external_interface = tg_cfg["vdu"][0]["external-interface"]
68         for intf in external_interface:
69             card_port0 = intf["virtual-interface"]["vpci"]
70             card0, port0 = card_port0.split(':')[:2]
71             card.append(card0)
72             port.append(port0)
73
74         cfg = {
75             'machine': tg_cfg["mgmt-interface"]["ip"],
76             'port': tg_cfg["mgmt-interface"]["tg-config"]["tcl_port"],
77             'chassis': tg_cfg["mgmt-interface"]["tg-config"]["ixchassis"],
78             'cards': card,
79             'ports': port,
80             'output_dir': tg_cfg["mgmt-interface"]["tg-config"]["dut_result_dir"],
81             'version': tg_cfg["mgmt-interface"]["tg-config"]["version"],
82             'bidir': True,
83         }
84
85         return cfg
86
87     def __init__(self):  # pragma: no cover
88         self._ixnet = None
89         self._cfg = None
90         self._params = None
91         self._bidir = None
92
93     @property
94     def ixnet(self):  # pragma: no cover
95         if self._ixnet:
96             return self._ixnet
97         raise exceptions.IxNetworkClientNotConnected()
98
99     def _get_config_element_by_flow_group_name(self, flow_group_name):
100         """Get a config element using the flow group name
101
102         Each named flow group contains one config element (by configuration).
103         According to the documentation, "configElements" is a list and "each
104         item in this list is aligned to the sequential order of your endpoint
105         list".
106
107         :param flow_group_name: (str) flow group name; this parameter is
108                                 always a number (converted to string) starting
109                                 from "1".
110         :return: (str) config element reference ID or None.
111         """
112         traffic_item = self.ixnet.getList(self.ixnet.getRoot() + '/traffic',
113                                           'trafficItem')[0]
114         flow_groups = self.ixnet.getList(traffic_item, 'endpointSet')
115         for flow_group in flow_groups:
116             if (str(self.ixnet.getAttribute(flow_group, '-name')) ==
117                     flow_group_name):
118                 return traffic_item + '/configElement:' + flow_group_name
119
120     def _get_stack_item(self, flow_group_name, protocol_name):
121         """Return the stack item given the flow group name and the proto name
122
123         :param flow_group_name: (str) flow group name
124         :param protocol_name: (str) protocol name, referred to PROTO_*
125                               constants
126         :return: list of stack item descriptors
127         """
128         celement = self._get_config_element_by_flow_group_name(flow_group_name)
129         if not celement:
130             raise exceptions.IxNetworkFlowNotPresent(
131                 flow_group=flow_group_name)
132         stack_items = self.ixnet.getList(celement, 'stack')
133         return [s_i for s_i in stack_items if protocol_name in s_i]
134
135     def _get_field_in_stack_item(self, stack_item, field_name):
136         """Return the field in a stack item given the name
137
138         :param stack_item: (str) stack item descriptor
139         :param field_name: (str) field name
140         :return: (str) field descriptor
141         """
142         fields = self.ixnet.getList(stack_item, 'field')
143         for field in (field for field in fields if field_name in field):
144             return field
145         raise exceptions.IxNetworkFieldNotPresentInStackItem(
146             field_name=field_name, stack_item=stack_item)
147
148     def _get_traffic_state(self):
149         """Get traffic state"""
150         return self.ixnet.getAttribute(self.ixnet.getRoot() + 'traffic',
151                                        '-state')
152
153     def is_traffic_running(self):
154         """Returns true if traffic state == TRAFFIC_STATUS_STARTED"""
155         return self._get_traffic_state() == TRAFFIC_STATUS_STARTED
156
157     def is_traffic_stopped(self):
158         """Returns true if traffic state == TRAFFIC_STATUS_STOPPED"""
159         return self._get_traffic_state() == TRAFFIC_STATUS_STOPPED
160
161     @staticmethod
162     def _parse_framesize(framesize):
163         """Parse "framesize" config param. to return a list of weighted pairs
164
165         :param framesize: dictionary of frame sizes and weights
166         :return: list of paired frame sizes and weights
167         """
168         weighted_range_pairs = []
169         for size, weight in ((s, w) for (s, w) in framesize.items()
170                              if int(w) != 0):
171             size = int(size.upper().replace('B', ''))
172             weighted_range_pairs.append([size, size, int(weight)])
173         return weighted_range_pairs
174
175     def iter_over_get_lists(self, x1, x2, y2, offset=0):
176         for x in self.ixnet.getList(x1, x2):
177             y_list = self.ixnet.getList(x, y2)
178             for i, y in enumerate(y_list, offset):
179                 yield x, y, i
180
181     def connect(self, tg_cfg):
182         self._cfg = self.get_config(tg_cfg)
183         self._ixnet = IxNetwork.IxNet()
184
185         machine = self._cfg['machine']
186         port = str(self._cfg['port'])
187         version = str(self._cfg['version'])
188         return self.ixnet.connect(machine, '-port', port,
189                                   '-version', version)
190
191     def clear_config(self):
192         """Wipe out any possible configuration present in the client"""
193         self.ixnet.execute('newConfig')
194
195     def assign_ports(self):
196         """Create and assign vports for each physical port defined in config
197
198         This configuration is present in the IXIA profile file. E.g.:
199             name: trafficgen_1
200             role: IxNet
201             interfaces:
202                 xe0:
203                     vpci: "2:15" # Card:port
204                     driver: "none"
205                     dpdk_port_num: 0
206                     local_ip: "152.16.100.20"
207                     netmask: "255.255.0.0"
208                     local_mac: "00:98:10:64:14:00"
209                 xe1:
210                     ...
211         """
212         chassis_ip = self._cfg['chassis']
213         ports = [(chassis_ip, card, port) for card, port in
214                  zip(self._cfg['cards'], self._cfg['ports'])]
215
216         log.info('Create and assign vports: %s', ports)
217         for port in ports:
218             vport = self.ixnet.add(self.ixnet.getRoot(), 'vport')
219             self.ixnet.commit()
220             self.ixnet.execute('assignPorts', [port], [], [vport], True)
221             self.ixnet.commit()
222             if self.ixnet.getAttribute(vport, '-state') != 'up':
223                 log.warning('Port %s is down', vport)
224
225     def _create_traffic_item(self):
226         """Create the traffic item to hold the flow groups
227
228         The traffic item tracking by "Traffic Item" is enabled to retrieve the
229         latency statistics.
230         """
231         log.info('Create the traffic item "RFC2544"')
232         traffic_item = self.ixnet.add(self.ixnet.getRoot() + '/traffic',
233                                       'trafficItem')
234         self.ixnet.setMultiAttribute(traffic_item, '-name', 'RFC2544',
235                                      '-trafficType', 'raw')
236         self.ixnet.commit()
237
238         traffic_item_id = self.ixnet.remapIds(traffic_item)[0]
239         self.ixnet.setAttribute(traffic_item_id + '/tracking',
240                                 '-trackBy', 'trafficGroupId0')
241         self.ixnet.commit()
242
243     def _create_flow_groups(self):
244         """Create the flow groups between the assigned ports"""
245         traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
246                                              'trafficItem')[0]
247         log.info('Create the flow groups')
248         vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
249         uplink_ports = vports[::2]
250         downlink_ports = vports[1::2]
251         index = 0
252         for up, down in zip(uplink_ports, downlink_ports):
253             log.info('FGs: %s <--> %s', up, down)
254             endpoint_set_1 = self.ixnet.add(traffic_item_id, 'endpointSet')
255             endpoint_set_2 = self.ixnet.add(traffic_item_id, 'endpointSet')
256             self.ixnet.setMultiAttribute(
257                 endpoint_set_1, '-name', str(index + 1),
258                 '-sources', [up + '/protocols'],
259                 '-destinations', [down + '/protocols'])
260             self.ixnet.setMultiAttribute(
261                 endpoint_set_2, '-name', str(index + 2),
262                 '-sources', [down + '/protocols'],
263                 '-destinations', [up + '/protocols'])
264             self.ixnet.commit()
265             index += 2
266
267     def _append_procotol_to_stack(self, protocol_name, previous_element):
268         """Append a new element in the packet definition stack"""
269         protocol = (self.ixnet.getRoot() +
270                     '/traffic/protocolTemplate:"{}"'.format(protocol_name))
271         self.ixnet.execute('append', previous_element, protocol)
272
273     def _setup_config_elements(self):
274         """Setup the config elements
275
276         The traffic item is configured to allow individual configurations per
277         config element. The default frame configuration is applied:
278             Ethernet II: added by default
279             IPv4: element to add
280             UDP: element to add
281             Payload: added by default
282             Ethernet II (Trailer): added by default
283         :return:
284         """
285         traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
286                                              'trafficItem')[0]
287         log.info('Split the frame rate distribution per config element')
288         config_elements = self.ixnet.getList(traffic_item_id, 'configElement')
289         for config_element in config_elements:
290             self.ixnet.setAttribute(config_element + '/frameRateDistribution',
291                                     '-portDistribution', 'splitRateEvenly')
292             self.ixnet.setAttribute(config_element + '/frameRateDistribution',
293                                     '-streamDistribution', 'splitRateEvenly')
294             self.ixnet.commit()
295             self._append_procotol_to_stack(
296                 PROTO_UDP, config_element + '/stack:"ethernet-1"')
297             self._append_procotol_to_stack(
298                 PROTO_IPV4, config_element + '/stack:"ethernet-1"')
299
300     def create_traffic_model(self):
301         """Create a traffic item and the needed flow groups
302
303         Each flow group inside the traffic item (only one is present)
304         represents the traffic between two ports:
305                         (uplink)    (downlink)
306             FlowGroup1: port1    -> port2
307             FlowGroup2: port1    <- port2
308             FlowGroup3: port3    -> port4
309             FlowGroup4: port3    <- port4
310         """
311         self._create_traffic_item()
312         self._create_flow_groups()
313         self._setup_config_elements()
314
315     def _update_frame_mac(self, ethernet_descriptor, field, mac_address):
316         """Set the MAC address in a config element stack Ethernet field
317
318         :param ethernet_descriptor: (str) ethernet descriptor, e.g.:
319             /traffic/trafficItem:1/configElement:1/stack:"ethernet-1"
320         :param field: (str) field name, e.g.: destinationAddress
321         :param mac_address: (str) MAC address
322         """
323         field_descriptor = self._get_field_in_stack_item(ethernet_descriptor,
324                                                          field)
325         self.ixnet.setMultiAttribute(field_descriptor,
326                                      '-singleValue', mac_address,
327                                      '-fieldValue', mac_address,
328                                      '-valueType', 'singleValue')
329         self.ixnet.commit()
330
331     def update_frame(self, traffic):
332         """Update the L2 frame
333
334         This function updates the L2 frame options:
335         - Traffic type: "continuous", "fixedDuration".
336         - Duration: in case of traffic_type="fixedDuration", amount of seconds
337                     to inject traffic.
338         - Rate: in frames per seconds or percentage.
339         - Type of rate: "framesPerSecond" ("bitsPerSecond" and
340                         "percentLineRate" no used)
341         - Frame size: custom IMIX [1] definition; a list of packet size in
342                       bytes and the weight. E.g.:
343                       [[64, 64, 10], [128, 128, 15], [512, 512, 5]]
344
345         [1] https://en.wikipedia.org/wiki/Internet_Mix
346
347         :param traffic: list of traffic elements; each traffic element contains
348                         the injection parameter for each flow group.
349         """
350         for traffic_param in traffic.values():
351             fg_id = str(traffic_param['id'])
352             config_element = self._get_config_element_by_flow_group_name(fg_id)
353             if not config_element:
354                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
355
356             type = traffic_param.get('traffic_type', 'fixedDuration')
357             duration = traffic_param.get('duration', 30)
358             rate = traffic_param['iload']
359             weighted_range_pairs = self._parse_framesize(
360                 traffic_param['outer_l2']['framesize'])
361             srcmac = str(traffic_param.get('srcmac', '00:00:00:00:00:01'))
362             dstmac = str(traffic_param.get('dstmac', '00:00:00:00:00:02'))
363             # NOTE(ralonsoh): add QinQ tagging when
364             # traffic_param['outer_l2']['QinQ'] exists.
365             # s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
366             # c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
367
368             self.ixnet.setMultiAttribute(
369                 config_element + '/transmissionControl',
370                 '-type', type, '-duration', duration)
371             self.ixnet.setMultiAttribute(
372                 config_element + '/frameRate',
373                 '-rate', rate, '-type', 'framesPerSecond')
374             self.ixnet.setMultiAttribute(
375                 config_element + '/frameSize',
376                 '-type', 'weightedPairs',
377                 '-weightedRangePairs', weighted_range_pairs)
378             self.ixnet.commit()
379
380             self._update_frame_mac(
381                 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
382                 'destinationAddress', dstmac)
383             self._update_frame_mac(
384                 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
385                 'sourceAddress', srcmac)
386
387     def _update_ipv4_address(self, ip_descriptor, field, ip_address, seed,
388                              mask, count):
389         """Set the IPv4 address in a config element stack IP field
390
391         :param ip_descriptor: (str) IP descriptor, e.g.:
392             /traffic/trafficItem:1/configElement:1/stack:"ipv4-2"
393         :param field: (str) field name, e.g.: scrIp, dstIp
394         :param ip_address: (str) IP address
395         :param seed: (int) seed length
396         :param mask: (str) IP address mask
397         :param count: (int) number of random IPs to generate
398         """
399         field_descriptor = self._get_field_in_stack_item(ip_descriptor,
400                                                          field)
401         self.ixnet.setMultiAttribute(field_descriptor,
402                                      '-seed', seed,
403                                      '-fixedBits', ip_address,
404                                      '-randomMask', mask,
405                                      '-valueType', 'random',
406                                      '-countValue', count)
407         self.ixnet.commit()
408
409     def update_ip_packet(self, traffic):
410         """Update the IP packet
411
412         NOTE: Only IPv4 is currently supported.
413         :param traffic: list of traffic elements; each traffic element contains
414                         the injection parameter for each flow group.
415         """
416         # NOTE(ralonsoh): L4 configuration is not set.
417         for traffic_param in traffic.values():
418             fg_id = str(traffic_param['id'])
419             if not self._get_config_element_by_flow_group_name(fg_id):
420                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
421
422             count = traffic_param['outer_l3']['count']
423             srcip4 = str(traffic_param['outer_l3']['srcip4'])
424             dstip4 = str(traffic_param['outer_l3']['dstip4'])
425
426             self._update_ipv4_address(
427                 self._get_stack_item(fg_id, PROTO_IPV4)[0],
428                 'srcIp', srcip4, 1, IP_VERSION_4_MASK, count)
429             self._update_ipv4_address(
430                 self._get_stack_item(fg_id, PROTO_IPV4)[0],
431                 'dstIp', dstip4, 1, IP_VERSION_4_MASK, count)
432
433     def _build_stats_map(self, view_obj, name_map):
434         return {data_yardstick: self.ixnet.execute(
435             'getColumnValues', view_obj, data_ixia)
436             for data_yardstick, data_ixia in name_map.items()}
437
438     def get_statistics(self):
439         """Retrieve port and flow statistics
440
441         "Port Statistics" parameters are stored in self.PORT_STATS_NAME_MAP.
442         "Flow Statistics" parameters are stored in self.LATENCY_NAME_MAP.
443
444         :return: dictionary with the statistics; the keys of this dictionary
445                  are PORT_STATS_NAME_MAP and LATENCY_NAME_MAP keys.
446         """
447         port_statistics = '::ixNet::OBJ-/statistics/view:"Port Statistics"'
448         flow_statistics = '::ixNet::OBJ-/statistics/view:"Flow Statistics"'
449         stats = self._build_stats_map(port_statistics,
450                                       self.PORT_STATS_NAME_MAP)
451         stats.update(self._build_stats_map(flow_statistics,
452                                           self.LATENCY_NAME_MAP))
453         return stats
454
455     def start_traffic(self):
456         """Start the traffic injection in the traffic item
457
458         By configuration, there is only one traffic item. This function returns
459         when the traffic state is TRAFFIC_STATUS_STARTED.
460         """
461         traffic_items = self.ixnet.getList('/traffic', 'trafficItem')
462         if self.is_traffic_running():
463             self.ixnet.execute('stop', '/traffic')
464             # pylint: disable=unnecessary-lambda
465             utils.wait_until_true(lambda: self.is_traffic_stopped())
466
467         self.ixnet.execute('generate', traffic_items)
468         self.ixnet.execute('apply', '/traffic')
469         self.ixnet.execute('start', '/traffic')
470         # pylint: disable=unnecessary-lambda
471         utils.wait_until_true(lambda: self.is_traffic_running())