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