Create Dockerfile to create a yardstick-image of docker
[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 framesize.items():
170             weighted_range_pairs.append(int(size.upper().replace('B', '')))
171             weighted_range_pairs.append(int(weight))
172         return weighted_range_pairs
173
174     def iter_over_get_lists(self, x1, x2, y2, offset=0):
175         for x in self.ixnet.getList(x1, x2):
176             y_list = self.ixnet.getList(x, y2)
177             for i, y in enumerate(y_list, offset):
178                 yield x, y, i
179
180     def connect(self, tg_cfg):
181         self._cfg = self.get_config(tg_cfg)
182         self._ixnet = IxNetwork.IxNet()
183
184         machine = self._cfg['machine']
185         port = str(self._cfg['port'])
186         version = str(self._cfg['version'])
187         return self.ixnet.connect(machine, '-port', port,
188                                   '-version', version)
189
190     def clear_config(self):
191         """Wipe out any possible configuration present in the client"""
192         self.ixnet.execute('newConfig')
193
194     def assign_ports(self):
195         """Create and assign vports for each physical port defined in config
196
197         This configuration is present in the IXIA profile file. E.g.:
198             name: trafficgen_1
199             role: IxNet
200             interfaces:
201                 xe0:
202                     vpci: "2:15" # Card:port
203                     driver: "none"
204                     dpdk_port_num: 0
205                     local_ip: "152.16.100.20"
206                     netmask: "255.255.0.0"
207                     local_mac: "00:98:10:64:14:00"
208                 xe1:
209                     ...
210         """
211         chassis_ip = self._cfg['chassis']
212         ports = [(chassis_ip, card, port) for card, port in
213                  zip(self._cfg['cards'], self._cfg['ports'])]
214
215         log.info('Create and assign vports: %s', ports)
216         for port in ports:
217             vport = self.ixnet.add(self.ixnet.getRoot(), 'vport')
218             self.ixnet.commit()
219             self.ixnet.execute('assignPorts', [port], [], [vport], True)
220             self.ixnet.commit()
221             if self.ixnet.getAttribute(vport, '-state') != 'up':
222                 log.warning('Port %s is down', vport)
223
224     def _create_traffic_item(self):
225         """Create the traffic item to hold the flow groups
226
227         The traffic item tracking by "Traffic Item" is enabled to retrieve the
228         latency statistics.
229         """
230         log.info('Create the traffic item "RFC2544"')
231         traffic_item = self.ixnet.add(self.ixnet.getRoot() + '/traffic',
232                                       'trafficItem')
233         self.ixnet.setMultiAttribute(traffic_item, '-name', 'RFC2544',
234                                      '-trafficType', 'raw')
235         self.ixnet.commit()
236
237         traffic_item_id = self.ixnet.remapIds(traffic_item)[0]
238         self.ixnet.setAttribute(traffic_item_id + '/tracking',
239                                 '-trackBy', 'trafficGroupId0')
240         self.ixnet.commit()
241
242     def _create_flow_groups(self):
243         """Create the flow groups between the assigned ports"""
244         traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
245                                              'trafficItem')[0]
246         log.info('Create the flow groups')
247         vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
248         uplink_ports = vports[::2]
249         downlink_ports = vports[1::2]
250         index = 0
251         for up, down in zip(uplink_ports, downlink_ports):
252             log.info('FGs: %s <--> %s', up, down)
253             endpoint_set_1 = self.ixnet.add(traffic_item_id, 'endpointSet')
254             endpoint_set_2 = self.ixnet.add(traffic_item_id, 'endpointSet')
255             self.ixnet.setMultiAttribute(
256                 endpoint_set_1, '-name', str(index + 1),
257                 '-sources', [up + '/protocols'],
258                 '-destinations', [down + '/protocols'])
259             self.ixnet.setMultiAttribute(
260                 endpoint_set_2, '-name', str(index + 2),
261                 '-sources', [down + '/protocols'],
262                 '-destinations', [up + '/protocols'])
263             self.ixnet.commit()
264             index += 2
265
266     def _append_procotol_to_stack(self, protocol_name, previous_element):
267         """Append a new element in the packet definition stack"""
268         protocol = (self.ixnet.getRoot() +
269                     '/traffic/protocolTemplate:"{}"'.format(protocol_name))
270         self.ixnet.execute('append', previous_element, protocol)
271
272     def _setup_config_elements(self):
273         """Setup the config elements
274
275         The traffic item is configured to allow individual configurations per
276         config element. The default frame configuration is applied:
277             Ethernet II: added by default
278             IPv4: element to add
279             UDP: element to add
280             Payload: added by default
281             Ethernet II (Trailer): added by default
282         :return:
283         """
284         traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
285                                              'trafficItem')[0]
286         log.info('Split the frame rate distribution per config element')
287         config_elements = self.ixnet.getList(traffic_item_id, 'configElement')
288         for config_element in config_elements:
289             self.ixnet.setAttribute(config_element + '/frameRateDistribution',
290                                     '-portDistribution', 'splitRateEvenly')
291             self.ixnet.setAttribute(config_element + '/frameRateDistribution',
292                                     '-streamDistribution', 'splitRateEvenly')
293             self.ixnet.commit()
294             self._append_procotol_to_stack(
295                 PROTO_UDP, config_element + '/stack:"ethernet-1"')
296             self._append_procotol_to_stack(
297                 PROTO_IPV4, config_element + '/stack:"ethernet-1"')
298
299     def create_traffic_model(self):
300         """Create a traffic item and the needed flow groups
301
302         Each flow group inside the traffic item (only one is present)
303         represents the traffic between two ports:
304                         (uplink)    (downlink)
305             FlowGroup1: port1    -> port2
306             FlowGroup2: port1    <- port2
307             FlowGroup3: port3    -> port4
308             FlowGroup4: port3    <- port4
309         """
310         self._create_traffic_item()
311         self._create_flow_groups()
312         self._setup_config_elements()
313
314     def _update_frame_mac(self, ethernet_descriptor, field, mac_address):
315         """Set the MAC address in a config element stack Ethernet field
316
317         :param ethernet_descriptor: (str) ethernet descriptor, e.g.:
318             /traffic/trafficItem:1/configElement:1/stack:"ethernet-1"
319         :param field: (str) field name, e.g.: destinationAddress
320         :param mac_address: (str) MAC address
321         """
322         field_descriptor = self._get_field_in_stack_item(ethernet_descriptor,
323                                                          field)
324         self.ixnet.setMultiAttribute(field_descriptor,
325                                      '-singleValue', mac_address,
326                                      '-fieldValue', mac_address,
327                                      '-valueType', 'singleValue')
328         self.ixnet.commit()
329
330     def update_frame(self, traffic):
331         """Update the L2 frame
332
333         This function updates the L2 frame options:
334         - Traffic type: "continuous", "fixedDuration".
335         - Duration: in case of traffic_type="fixedDuration", amount of seconds
336                     to inject traffic.
337         - Rate: in frames per seconds or percentage.
338         - Type of rate: "framesPerSecond" ("bitsPerSecond" and
339                         "percentLineRate" no used)
340         - Frame size: custom IMIX [1] definition; a list of packet size in
341                       bytes and the weight. E.g.:
342                       [64, 10, 128, 15, 512, 5]
343
344         [1] https://en.wikipedia.org/wiki/Internet_Mix
345
346         :param traffic: list of traffic elements; each traffic element contains
347                         the injection parameter for each flow group.
348         """
349         for traffic_param in traffic.values():
350             fg_id = str(traffic_param['id'])
351             config_element = self._get_config_element_by_flow_group_name(fg_id)
352             if not config_element:
353                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
354
355             type = traffic_param.get('traffic_type', 'fixedDuration')
356             duration = traffic_param.get('duration', 30)
357             rate = traffic_param['iload']
358             weighted_range_pairs = self._parse_framesize(
359                 traffic_param['outer_l2']['framesize'])
360             srcmac = str(traffic_param.get('srcmac', '00:00:00:00:00:01'))
361             dstmac = str(traffic_param.get('dstmac', '00:00:00:00:00:02'))
362             # NOTE(ralonsoh): add QinQ tagging when
363             # traffic_param['outer_l2']['QinQ'] exists.
364             # s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
365             # c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
366
367             self.ixnet.setMultiAttribute(
368                 config_element + '/transmissionControl',
369                 '-type', type, '-duration', duration)
370             self.ixnet.setMultiAttribute(
371                 config_element + '/frameRate',
372                 '-rate', rate, '-type', 'framesPerSecond')
373             self.ixnet.setMultiAttribute(
374                 config_element + '/frameSize',
375                 '-type', 'weightedPairs',
376                 '-weightedRangePairs', weighted_range_pairs)
377             self.ixnet.commit()
378
379             self._update_frame_mac(
380                 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
381                 'destinationAddress', dstmac)
382             self._update_frame_mac(
383                 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
384                 'sourceAddress', srcmac)
385
386     def _update_ipv4_address(self, ip_descriptor, field, ip_address, seed,
387                              mask, count):
388         """Set the IPv4 address in a config element stack IP field
389
390         :param ip_descriptor: (str) IP descriptor, e.g.:
391             /traffic/trafficItem:1/configElement:1/stack:"ipv4-2"
392         :param field: (str) field name, e.g.: scrIp, dstIp
393         :param ip_address: (str) IP address
394         :param seed: (int) seed length
395         :param mask: (str) IP address mask
396         :param count: (int) number of random IPs to generate
397         """
398         field_descriptor = self._get_field_in_stack_item(ip_descriptor,
399                                                          field)
400         self.ixnet.setMultiAttribute(field_descriptor,
401                                      '-seed', seed,
402                                      '-fixedBits', ip_address,
403                                      '-randomMask', mask,
404                                      '-valueType', 'random',
405                                      '-countValue', count)
406         self.ixnet.commit()
407
408     def update_ip_packet(self, traffic):
409         """Update the IP packet
410
411         NOTE: Only IPv4 is currently supported.
412         :param traffic: list of traffic elements; each traffic element contains
413                         the injection parameter for each flow group.
414         """
415         # NOTE(ralonsoh): L4 configuration is not set.
416         for traffic_param in traffic.values():
417             fg_id = str(traffic_param['id'])
418             if not self._get_config_element_by_flow_group_name(fg_id):
419                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
420
421             count = traffic_param['outer_l3']['count']
422             srcip4 = str(traffic_param['outer_l3']['srcip4'])
423             dstip4 = str(traffic_param['outer_l3']['dstip4'])
424
425             self._update_ipv4_address(
426                 self._get_stack_item(fg_id, PROTO_IPV4)[0],
427                 'srcIp', srcip4, 1, IP_VERSION_4_MASK, count)
428             self._update_ipv4_address(
429                 self._get_stack_item(fg_id, PROTO_IPV4)[0],
430                 'dstIp', dstip4, 1, IP_VERSION_4_MASK, count)
431
432     def _build_stats_map(self, view_obj, name_map):
433         return {data_yardstick: self.ixnet.execute(
434             'getColumnValues', view_obj, data_ixia)
435             for data_yardstick, data_ixia in name_map.items()}
436
437     def get_statistics(self):
438         """Retrieve port and flow statistics
439
440         "Port Statistics" parameters are stored in self.PORT_STATS_NAME_MAP.
441         "Flow Statistics" parameters are stored in self.LATENCY_NAME_MAP.
442
443         :return: dictionary with the statistics; the keys of this dictionary
444                  are PORT_STATS_NAME_MAP and LATENCY_NAME_MAP keys.
445         """
446         port_statistics = '::ixNet::OBJ-/statistics/view:"Port Statistics"'
447         flow_statistics = '::ixNet::OBJ-/statistics/view:"Flow Statistics"'
448         stats = self._build_stats_map(port_statistics,
449                                       self.PORT_STATS_NAME_MAP)
450         stats.update(self._build_stats_map(flow_statistics,
451                                           self.LATENCY_NAME_MAP))
452         return stats
453
454     def start_traffic(self):
455         """Start the traffic injection in the traffic item
456
457         By configuration, there is only one traffic item. This function returns
458         when the traffic state is TRAFFIC_STATUS_STARTED.
459         """
460         traffic_items = self.ixnet.getList('/traffic', 'trafficItem')
461         if self.is_traffic_running():
462             self.ixnet.execute('stop', '/traffic')
463             # pylint: disable=unnecessary-lambda
464             utils.wait_until_true(lambda: self.is_traffic_stopped())
465
466         self.ixnet.execute('generate', traffic_items)
467         self.ixnet.execute('apply', '/traffic')
468         self.ixnet.execute('start', '/traffic')
469         # pylint: disable=unnecessary-lambda
470         utils.wait_until_true(lambda: self.is_traffic_running())