Dashboard with Network and Platform NFVi metrics
[yardstick.git] / yardstick / network_services / libs / ixia_libs / ixnet / ixnet_api.py
1 # Copyright (c) 2016-2018 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
18 import IxNetwork
19
20 from yardstick.common import exceptions
21 from yardstick.common import utils
22 from yardstick.network_services.traffic_profile import base as tp_base
23
24
25 log = logging.getLogger(__name__)
26
27 IP_VERSION_4 = 4
28 IP_VERSION_6 = 6
29
30 PROTO_ETHERNET = 'ethernet'
31 PROTO_IPV4 = 'ipv4'
32 PROTO_IPV6 = 'ipv6'
33 PROTO_UDP = 'udp'
34 PROTO_TCP = 'tcp'
35 PROTO_VLAN = 'vlan'
36
37 SINGLE_VALUE = "singleValue"
38
39 S_VLAN = 0
40 C_VLAN = 1
41
42 ETHER_TYPE_802_1ad = '0x88a8'
43
44 IP_VERSION_4_MASK = 24
45 IP_VERSION_6_MASK = 64
46
47 TRAFFIC_STATUS_STARTED = 'started'
48 TRAFFIC_STATUS_STOPPED = 'stopped'
49
50 SUPPORTED_PROTO = [PROTO_UDP]
51
52
53 class Vlan(object):
54     def __init__(self,
55                  vlan_id, vlan_id_step=None, vlan_id_direction='increment',
56                  prio=None, prio_step=None, prio_direction='increment',
57                  tp_id=None):
58         self.vlan_id = vlan_id
59         self.vlan_id_step = vlan_id_step
60         self.vlan_id_direction = vlan_id_direction
61         self.prio = prio
62         self.prio_step = prio_step
63         self.prio_direction = prio_direction
64         self.tp_id = tp_id
65
66
67 # NOTE(ralonsoh): this pragma will be removed in the last patch of this series
68 class IxNextgen(object):  # pragma: no cover
69
70     PORT_STATS_NAME_MAP = {
71         "stat_name": 'Stat Name',
72         "Frames_Tx": 'Frames Tx.',
73         "Valid_Frames_Rx": 'Valid Frames Rx.',
74         "Frames_Tx_Rate": 'Frames Tx. Rate',
75         "Valid_Frames_Rx_Rate": 'Valid Frames Rx. Rate',
76         "Tx_Rate_Kbps": 'Tx. Rate (Kbps)',
77         "Rx_Rate_Kbps": 'Rx. Rate (Kbps)',
78         "Tx_Rate_Mbps": 'Tx. Rate (Mbps)',
79         "Rx_Rate_Mbps": 'Rx. Rate (Mbps)',
80     }
81
82     LATENCY_NAME_MAP = {
83         "Store-Forward_Avg_latency_ns": 'Store-Forward Avg Latency (ns)',
84         "Store-Forward_Min_latency_ns": 'Store-Forward Min Latency (ns)',
85         "Store-Forward_Max_latency_ns": 'Store-Forward Max Latency (ns)',
86     }
87
88     @staticmethod
89     def get_config(tg_cfg):
90         card = []
91         port = []
92         external_interface = tg_cfg["vdu"][0]["external-interface"]
93         for intf in external_interface:
94             card_port0 = intf["virtual-interface"]["vpci"]
95             card0, port0 = card_port0.split(':')[:2]
96             card.append(card0)
97             port.append(port0)
98
99         cfg = {
100             'machine': tg_cfg["mgmt-interface"]["ip"],
101             'port': tg_cfg["mgmt-interface"]["tg-config"]["tcl_port"],
102             'chassis': tg_cfg["mgmt-interface"]["tg-config"]["ixchassis"],
103             'cards': card,
104             'ports': port,
105             'output_dir': tg_cfg["mgmt-interface"]["tg-config"]["dut_result_dir"],
106             'version': tg_cfg["mgmt-interface"]["tg-config"]["version"],
107             'bidir': True,
108         }
109
110         return cfg
111
112     def __init__(self):  # pragma: no cover
113         self._ixnet = None
114         self._cfg = None
115         self._params = None
116         self._bidir = None
117
118     @property
119     def ixnet(self):  # pragma: no cover
120         if self._ixnet:
121             return self._ixnet
122         raise exceptions.IxNetworkClientNotConnected()
123
124     def get_vports(self):
125         """Return the list of assigned ports (vport objects)"""
126         vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
127         return vports
128
129     def _get_config_element_by_flow_group_name(self, flow_group_name):
130         """Get a config element using the flow group name
131
132         Each named flow group contains one config element (by configuration).
133         According to the documentation, "configElements" is a list and "each
134         item in this list is aligned to the sequential order of your endpoint
135         list".
136
137         :param flow_group_name: (str) flow group name; this parameter is
138                                 always a number (converted to string) starting
139                                 from "1".
140         :return: (str) config element reference ID or None.
141         """
142         traffic_item = self.ixnet.getList(self.ixnet.getRoot() + '/traffic',
143                                           'trafficItem')[0]
144         flow_groups = self.ixnet.getList(traffic_item, 'endpointSet')
145         for flow_group in flow_groups:
146             if (str(self.ixnet.getAttribute(flow_group, '-name')) ==
147                     flow_group_name):
148                 return traffic_item + '/configElement:' + flow_group_name
149
150     def _get_stack_item(self, flow_group_name, protocol_name):
151         """Return the stack item given the flow group name and the proto name
152
153         :param flow_group_name: (str) flow group name
154         :param protocol_name: (str) protocol name, referred to PROTO_*
155                               constants
156         :return: list of stack item descriptors
157         """
158         celement = self._get_config_element_by_flow_group_name(flow_group_name)
159         if not celement:
160             raise exceptions.IxNetworkFlowNotPresent(
161                 flow_group=flow_group_name)
162         stack_items = self.ixnet.getList(celement, 'stack')
163         return [s_i for s_i in stack_items if protocol_name in s_i]
164
165     def _get_field_in_stack_item(self, stack_item, field_name):
166         """Return the field in a stack item given the name
167
168         :param stack_item: (str) stack item descriptor
169         :param field_name: (str) field name
170         :return: (str) field descriptor
171         """
172         fields = self.ixnet.getList(stack_item, 'field')
173         for field in (field for field in fields if field_name in field):
174             return field
175         raise exceptions.IxNetworkFieldNotPresentInStackItem(
176             field_name=field_name, stack_item=stack_item)
177
178     def _get_traffic_state(self):
179         """Get traffic state"""
180         return self.ixnet.getAttribute(self.ixnet.getRoot() + 'traffic',
181                                        '-state')
182
183     def is_traffic_running(self):
184         """Returns true if traffic state == TRAFFIC_STATUS_STARTED"""
185         return self._get_traffic_state() == TRAFFIC_STATUS_STARTED
186
187     def is_traffic_stopped(self):
188         """Returns true if traffic state == TRAFFIC_STATUS_STOPPED"""
189         return self._get_traffic_state() == TRAFFIC_STATUS_STOPPED
190
191     @staticmethod
192     def _parse_framesize(framesize):
193         """Parse "framesize" config param. to return a list of weighted pairs
194
195         :param framesize: dictionary of frame sizes and weights
196         :return: list of paired frame sizes and weights
197         """
198         weighted_range_pairs = []
199         for size, weight in ((s, w) for (s, w) in framesize.items()
200                              if int(w) != 0):
201             size = int(size.upper().replace('B', ''))
202             weighted_range_pairs.append([size, size, int(weight)])
203         return weighted_range_pairs
204
205     def iter_over_get_lists(self, x1, x2, y2, offset=0):
206         for x in self.ixnet.getList(x1, x2):
207             y_list = self.ixnet.getList(x, y2)
208             for i, y in enumerate(y_list, offset):
209                 yield x, y, i
210
211     def connect(self, tg_cfg):
212         self._cfg = self.get_config(tg_cfg)
213         self._ixnet = IxNetwork.IxNet()
214
215         machine = self._cfg['machine']
216         port = str(self._cfg['port'])
217         version = str(self._cfg['version'])
218         return self.ixnet.connect(machine, '-port', port,
219                                   '-version', version)
220
221     def clear_config(self):
222         """Wipe out any possible configuration present in the client"""
223         self.ixnet.execute('newConfig')
224
225     def assign_ports(self):
226         """Create and assign vports for each physical port defined in config
227
228         This configuration is present in the IXIA profile file. E.g.:
229             name: trafficgen_1
230             role: IxNet
231             interfaces:
232                 xe0:
233                     vpci: "2:15" # Card:port
234                     driver: "none"
235                     dpdk_port_num: 0
236                     local_ip: "152.16.100.20"
237                     netmask: "255.255.0.0"
238                     local_mac: "00:98:10:64:14:00"
239                 xe1:
240                     ...
241         """
242         chassis_ip = self._cfg['chassis']
243         ports = [(chassis_ip, card, port) for card, port in
244                  zip(self._cfg['cards'], self._cfg['ports'])]
245
246         log.info('Create and assign vports: %s', ports)
247
248         vports = []
249         for _ in ports:
250             vports.append(self.ixnet.add(self.ixnet.getRoot(), 'vport'))
251             self.ixnet.commit()
252
253         self.ixnet.execute('assignPorts', ports, [], vports, True)
254         self.ixnet.commit()
255
256         for vport in vports:
257             if self.ixnet.getAttribute(vport, '-state') != 'up':
258                 log.warning('Port %s is down', vport)
259
260     def _create_traffic_item(self, traffic_type='raw'):
261         """Create the traffic item to hold the flow groups
262
263         The traffic item tracking by "Traffic Item" is enabled to retrieve the
264         latency statistics.
265         """
266         log.info('Create the traffic item "RFC2544"')
267         traffic_item = self.ixnet.add(self.ixnet.getRoot() + '/traffic',
268                                       'trafficItem')
269         self.ixnet.setMultiAttribute(traffic_item, '-name', 'RFC2544',
270                                      '-trafficType', traffic_type)
271         self.ixnet.commit()
272
273         traffic_item_id = self.ixnet.remapIds(traffic_item)[0]
274         self.ixnet.setAttribute(traffic_item_id + '/tracking',
275                                 '-trackBy', 'trafficGroupId0')
276         self.ixnet.commit()
277
278     def _create_flow_groups(self, uplink, downlink):
279         """Create the flow groups between the endpoints"""
280         traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
281                                              'trafficItem')[0]
282         log.info('Create the flow groups')
283
284         index = 0
285         for up, down in zip(uplink, downlink):
286             log.info('FGs: %s <--> %s', up, down)
287             endpoint_set_1 = self.ixnet.add(traffic_item_id, 'endpointSet')
288             endpoint_set_2 = self.ixnet.add(traffic_item_id, 'endpointSet')
289             self.ixnet.setMultiAttribute(
290                 endpoint_set_1, '-name', str(index + 1),
291                 '-sources', [up],
292                 '-destinations', [down])
293             self.ixnet.setMultiAttribute(
294                 endpoint_set_2, '-name', str(index + 2),
295                 '-sources', [down],
296                 '-destinations', [up])
297             self.ixnet.commit()
298             index += 2
299
300     def _append_procotol_to_stack(self, protocol_name, previous_element):
301         """Append a new element in the packet definition stack"""
302         protocol = (self.ixnet.getRoot() +
303                     '/traffic/protocolTemplate:"{}"'.format(protocol_name))
304         self.ixnet.execute('append', previous_element, protocol)
305
306     def _setup_config_elements(self, add_default_proto=True):
307         """Setup the config elements
308
309         The traffic item is configured to allow individual configurations per
310         config element. The default frame configuration is applied:
311             Ethernet II: added by default
312             IPv4: element to add
313             UDP: element to add
314             Payload: added by default
315             Ethernet II (Trailer): added by default
316         :return:
317         """
318         traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
319                                              'trafficItem')[0]
320         log.info('Split the frame rate distribution per config element')
321         config_elements = self.ixnet.getList(traffic_item_id, 'configElement')
322         for config_element in config_elements:
323             self.ixnet.setAttribute(config_element + '/frameRateDistribution',
324                                     '-portDistribution', 'splitRateEvenly')
325             self.ixnet.setAttribute(config_element + '/frameRateDistribution',
326                                     '-streamDistribution', 'splitRateEvenly')
327             self.ixnet.commit()
328             if add_default_proto:
329                 self._append_procotol_to_stack(
330                     PROTO_UDP, config_element + '/stack:"ethernet-1"')
331                 self._append_procotol_to_stack(
332                     PROTO_IPV4, config_element + '/stack:"ethernet-1"')
333
334     def create_traffic_model(self, uplink_ports, downlink_ports):
335         """Create a traffic item and the needed flow groups
336
337         Each flow group inside the traffic item (only one is present)
338         represents the traffic between two ports:
339                         (uplink)    (downlink)
340             FlowGroup1: port1    -> port2
341             FlowGroup2: port1    <- port2
342             FlowGroup3: port3    -> port4
343             FlowGroup4: port3    <- port4
344         """
345         self._create_traffic_item('raw')
346         uplink_endpoints = [port + '/protocols' for port in uplink_ports]
347         downlink_endpoints = [port + '/protocols' for port in downlink_ports]
348         self._create_flow_groups(uplink_endpoints, downlink_endpoints)
349         self._setup_config_elements()
350
351     def create_ipv4_traffic_model(self, uplink_topologies, downlink_topologies):
352         """Create a traffic item and the needed flow groups
353
354         Each flow group inside the traffic item (only one is present)
355         represents the traffic between two topologies:
356                         (uplink)    (downlink)
357             FlowGroup1: uplink1    -> downlink1
358             FlowGroup2: uplink1    <- downlink1
359             FlowGroup3: uplink2    -> downlink2
360             FlowGroup4: uplink2    <- downlink2
361         """
362         self._create_traffic_item('ipv4')
363         self._create_flow_groups(uplink_topologies, downlink_topologies)
364         self._setup_config_elements(False)
365
366     def _update_frame_mac(self, ethernet_descriptor, field, mac_address):
367         """Set the MAC address in a config element stack Ethernet field
368
369         :param ethernet_descriptor: (str) ethernet descriptor, e.g.:
370             /traffic/trafficItem:1/configElement:1/stack:"ethernet-1"
371         :param field: (str) field name, e.g.: destinationAddress
372         :param mac_address: (str) MAC address
373         """
374         field_descriptor = self._get_field_in_stack_item(ethernet_descriptor,
375                                                          field)
376         self.ixnet.setMultiAttribute(field_descriptor,
377                                      '-singleValue', mac_address,
378                                      '-fieldValue', mac_address,
379                                      '-valueType', 'singleValue')
380         self.ixnet.commit()
381
382     def update_frame(self, traffic, duration):
383         """Update the L2 frame
384
385         This function updates the L2 frame options:
386         - Traffic type: "continuous", "fixedDuration".
387         - Duration: in case of traffic_type="fixedDuration", amount of seconds
388                     to inject traffic.
389         - Rate: in frames per seconds or percentage.
390         - Type of rate: "framesPerSecond" or "percentLineRate" ("bitsPerSecond"
391                          no used)
392         - Frame size: custom IMIX [1] definition; a list of packet size in
393                       bytes and the weight. E.g.:
394                       [[64, 64, 10], [128, 128, 15], [512, 512, 5]]
395
396         [1] https://en.wikipedia.org/wiki/Internet_Mix
397
398         :param traffic: list of traffic elements; each traffic element contains
399                         the injection parameter for each flow group.
400         :param duration: (int) injection time in seconds.
401         """
402         for traffic_param in traffic.values():
403             fg_id = str(traffic_param['id'])
404             config_element = self._get_config_element_by_flow_group_name(fg_id)
405             if not config_element:
406                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
407
408             type = traffic_param.get('traffic_type', 'fixedDuration')
409             rate_unit = (
410                 'framesPerSecond' if traffic_param['rate_unit'] ==
411                 tp_base.TrafficProfileConfig.RATE_FPS else 'percentLineRate')
412             weighted_range_pairs = self._parse_framesize(
413                 traffic_param['outer_l2'].get('framesize', {}))
414             srcmac = str(traffic_param['outer_l2'].get('srcmac', '00:00:00:00:00:01'))
415             dstmac = str(traffic_param['outer_l2'].get('dstmac', '00:00:00:00:00:02'))
416
417             if traffic_param['outer_l2'].get('QinQ'):
418                 s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
419                 c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
420
421                 field_descriptor = self._get_field_in_stack_item(
422                     self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
423                     'etherType')
424
425                 self.ixnet.setMultiAttribute(field_descriptor,
426                                              '-auto', 'false',
427                                              '-singleValue', ETHER_TYPE_802_1ad,
428                                              '-fieldValue', ETHER_TYPE_802_1ad,
429                                              '-valueType', SINGLE_VALUE)
430
431                 self._append_procotol_to_stack(
432                     PROTO_VLAN, config_element + '/stack:"ethernet-1"')
433                 self._append_procotol_to_stack(
434                     PROTO_VLAN, config_element + '/stack:"ethernet-1"')
435
436                 self._update_vlan_tag(fg_id, s_vlan, S_VLAN)
437                 self._update_vlan_tag(fg_id, c_vlan, C_VLAN)
438
439             self.ixnet.setMultiAttribute(
440                 config_element + '/transmissionControl',
441                 '-type', type, '-duration', duration)
442
443             self.ixnet.setMultiAttribute(
444                 config_element + '/frameRate',
445                 '-rate', traffic_param['rate'], '-type', rate_unit)
446
447             if len(weighted_range_pairs):
448                 self.ixnet.setMultiAttribute(
449                     config_element + '/frameSize',
450                     '-type', 'weightedPairs',
451                     '-weightedRangePairs', weighted_range_pairs)
452
453             self.ixnet.commit()
454
455             if dstmac:
456                 self._update_frame_mac(
457                     self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
458                     'destinationAddress', dstmac)
459             if srcmac:
460                 self._update_frame_mac(
461                     self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
462                     'sourceAddress', srcmac)
463
464     def _update_vlan_tag(self, fg_id, params, vlan=0):
465         field_to_param_map = {
466             'vlanUserPriority': 'priority',
467             'cfi': 'cfi',
468             'vlanID': 'id'
469         }
470         for field, param in field_to_param_map.items():
471             value = params.get(param)
472             if value:
473                 field_descriptor = self._get_field_in_stack_item(
474                     self._get_stack_item(fg_id, PROTO_VLAN)[vlan],
475                     field)
476
477                 self.ixnet.setMultiAttribute(field_descriptor,
478                                              '-auto', 'false',
479                                              '-singleValue', value,
480                                              '-fieldValue', value,
481                                              '-valueType', SINGLE_VALUE)
482
483         self.ixnet.commit()
484
485     def _update_ipv4_address(self, ip_descriptor, field, ip_address, seed,
486                              mask, count):
487         """Set the IPv4 address in a config element stack IP field
488
489         :param ip_descriptor: (str) IP descriptor, e.g.:
490             /traffic/trafficItem:1/configElement:1/stack:"ipv4-2"
491         :param field: (str) field name, e.g.: scrIp, dstIp
492         :param ip_address: (str) IP address
493         :param seed: (int) seed length
494         :param mask: (int) IP address mask length
495         :param count: (int) number of random IPs to generate
496         """
497         field_descriptor = self._get_field_in_stack_item(ip_descriptor,
498                                                          field)
499         random_mask = str(ipaddress.IPv4Address(
500             2**(ipaddress.IPV4LENGTH - mask) - 1).compressed)
501         self.ixnet.setMultiAttribute(field_descriptor,
502                                      '-seed', seed,
503                                      '-fixedBits', ip_address,
504                                      '-randomMask', random_mask,
505                                      '-valueType', 'random',
506                                      '-countValue', count)
507         self.ixnet.commit()
508
509     def update_ip_packet(self, traffic):
510         """Update the IP packet
511
512         NOTE: Only IPv4 is currently supported.
513         :param traffic: list of traffic elements; each traffic element contains
514                         the injection parameter for each flow group.
515         """
516         # NOTE(ralonsoh): L4 configuration is not set.
517         for traffic_param in traffic.values():
518             fg_id = str(traffic_param['id'])
519             if not self._get_config_element_by_flow_group_name(fg_id):
520                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
521
522             if traffic_param['outer_l3']:
523                 count = traffic_param['outer_l3']['count']
524                 srcip = traffic_param['outer_l3']['srcip']
525                 dstip = traffic_param['outer_l3']['dstip']
526                 srcseed = traffic_param['outer_l3']['srcseed']
527                 dstseed = traffic_param['outer_l3']['dstseed']
528                 srcmask = traffic_param['outer_l3']['srcmask'] \
529                           or IP_VERSION_4_MASK
530                 dstmask = traffic_param['outer_l3']['dstmask'] \
531                           or IP_VERSION_4_MASK
532                 if srcip:
533                     self._update_ipv4_address(
534                         self._get_stack_item(fg_id, PROTO_IPV4)[0],
535                         'srcIp', str(srcip), srcseed, srcmask, count)
536                 if dstip:
537                     self._update_ipv4_address(
538                         self._get_stack_item(fg_id, PROTO_IPV4)[0],
539                         'dstIp', str(dstip), dstseed, dstmask, count)
540
541     def update_l4(self, traffic):
542         """Update the L4 headers
543
544         NOTE: Only UDP is currently supported
545         :param traffic: list of traffic elements; each traffic element contains
546                         the injection parameter for each flow group
547         """
548         for traffic_param in traffic.values():
549             fg_id = str(traffic_param['id'])
550             if not self._get_config_element_by_flow_group_name(fg_id):
551                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
552
553             proto = traffic_param['outer_l3'].get('proto')
554             if not (proto and traffic_param['outer_l4']):
555                 continue
556
557             if proto not in SUPPORTED_PROTO:
558                 raise exceptions.IXIAUnsupportedProtocol(protocol=proto)
559
560             count = traffic_param['outer_l4']['count']
561             seed = traffic_param['outer_l4']['seed']
562
563             srcport = traffic_param['outer_l4']['srcport']
564             srcmask = traffic_param['outer_l4']['srcportmask']
565
566             dstport = traffic_param['outer_l4']['dstport']
567             dstmask = traffic_param['outer_l4']['dstportmask']
568
569             if proto == PROTO_UDP:
570                 if srcport:
571                     self._update_udp_port(
572                         self._get_stack_item(fg_id, proto)[0],
573                         'srcPort', srcport, seed, srcmask, count)
574                 if dstport:
575                     self._update_udp_port(
576                         self._get_stack_item(fg_id, proto)[0],
577                         'dstPort', dstport, seed, dstmask, count)
578
579     def _update_udp_port(self, descriptor, field, value,
580                          seed=1, mask=0, count=1):
581         """Set the UDP port in a config element stack UDP field
582
583         :param udp_descriptor: (str) UDP descriptor, e.g.:
584             /traffic/trafficItem:1/configElement:1/stack:"udp-3"
585         :param field: (str) field name, e.g.: scrPort, dstPort
586         :param value: (int) UDP port fixed bits
587         :param seed: (int) seed length
588         :param mask: (int) UDP port mask
589         :param count: (int) number of random ports to generate
590         """
591         field_descriptor = self._get_field_in_stack_item(descriptor, field)
592
593         if mask == 0:
594             seed = count = 1
595
596         self.ixnet.setMultiAttribute(field_descriptor,
597                                      '-auto', 'false',
598                                      '-seed', seed,
599                                      '-fixedBits', value,
600                                      '-randomMask', mask,
601                                      '-valueType', 'random',
602                                      '-countValue', count)
603
604         self.ixnet.commit()
605
606     def _build_stats_map(self, view_obj, name_map):
607         return {data_yardstick: self.ixnet.execute(
608             'getColumnValues', view_obj, data_ixia)
609             for data_yardstick, data_ixia in name_map.items()}
610
611     def get_statistics(self):
612         """Retrieve port and flow statistics
613
614         "Port Statistics" parameters are stored in self.PORT_STATS_NAME_MAP.
615         "Flow Statistics" parameters are stored in self.LATENCY_NAME_MAP.
616
617         :return: dictionary with the statistics; the keys of this dictionary
618                  are PORT_STATS_NAME_MAP and LATENCY_NAME_MAP keys.
619         """
620         port_statistics = '::ixNet::OBJ-/statistics/view:"Port Statistics"'
621         flow_statistics = '::ixNet::OBJ-/statistics/view:"Flow Statistics"'
622         stats = self._build_stats_map(port_statistics,
623                                       self.PORT_STATS_NAME_MAP)
624         stats.update(self._build_stats_map(flow_statistics,
625                                           self.LATENCY_NAME_MAP))
626         return stats
627
628     def start_protocols(self):
629         self.ixnet.execute('startAllProtocols')
630
631     def stop_protocols(self):
632         self.ixnet.execute('stopAllProtocols')
633
634     def start_traffic(self):
635         """Start the traffic injection in the traffic item
636
637         By configuration, there is only one traffic item. This function returns
638         when the traffic state is TRAFFIC_STATUS_STARTED.
639         """
640         traffic_items = self.ixnet.getList('/traffic', 'trafficItem')
641         if self.is_traffic_running():
642             self.ixnet.execute('stop', '/traffic')
643             # pylint: disable=unnecessary-lambda
644             utils.wait_until_true(lambda: self.is_traffic_stopped())
645
646         self.ixnet.execute('generate', traffic_items)
647         self.ixnet.execute('apply', '/traffic')
648         self.ixnet.execute('start', '/traffic')
649         # pylint: disable=unnecessary-lambda
650         utils.wait_until_true(lambda: self.is_traffic_running())
651
652     def add_topology(self, name, vports):
653         log.debug("add_topology: name='%s' ports='%s'", name, vports)
654         obj = self.ixnet.add(self.ixnet.getRoot(), 'topology')
655         self.ixnet.setMultiAttribute(obj, '-name', name, '-vports', vports)
656         self.ixnet.commit()
657         return obj
658
659     def add_device_group(self, topology, name, multiplier):
660         log.debug("add_device_group: tpl='%s', name='%s', multiplier='%s'",
661                   topology, name, multiplier)
662
663         obj = self.ixnet.add(topology, 'deviceGroup')
664         self.ixnet.setMultiAttribute(obj, '-name', name, '-multiplier',
665                                      multiplier)
666         self.ixnet.commit()
667         return obj
668
669     def add_ethernet(self, dev_group, name):
670         log.debug(
671             "add_ethernet: device_group='%s' name='%s'", dev_group, name)
672         obj = self.ixnet.add(dev_group, 'ethernet')
673         self.ixnet.setMultiAttribute(obj, '-name', name)
674         self.ixnet.commit()
675         return obj
676
677     def _create_vlans(self, ethernet, count):
678         self.ixnet.setMultiAttribute(ethernet, '-useVlans', 'true')
679         self.ixnet.setMultiAttribute(ethernet, '-vlanCount', count)
680         self.ixnet.commit()
681
682     def _configure_vlans(self, ethernet, vlans):
683         vlans_obj = self.ixnet.getList(ethernet, 'vlan')
684         for i, vlan_obj in enumerate(vlans_obj):
685             if vlans[i].vlan_id_step is not None:
686                 vlan_id_obj = self.ixnet.getAttribute(vlan_obj, '-vlanId')
687                 self.ixnet.setMultiAttribute(vlan_id_obj, '-clearOverlays',
688                                              'true', '-pattern', 'counter')
689                 vlan_id_counter = self.ixnet.add(vlan_id_obj, 'counter')
690                 self.ixnet.setMultiAttribute(vlan_id_counter, '-start',
691                                              vlans[i].vlan_id, '-step',
692                                              vlans[i].vlan_id_step,
693                                              '-direction',
694                                              vlans[i].vlan_id_direction)
695             else:
696                 vlan_id_obj = self.ixnet.getAttribute(vlan_obj, '-vlanId')
697                 self.ixnet.setMultiAttribute(vlan_id_obj + '/singleValue',
698                                              '-value', vlans[i].vlan_id)
699
700             if vlans[i].prio_step is not None:
701                 prio_obj = self.ixnet.getAttribute(vlan_obj, '-priority')
702                 self.ixnet.setMultiAttribute(prio_obj, '-clearOverlays', 'true',
703                                              '-pattern', 'counter')
704                 prio_counter = self.ixnet.add(prio_obj, 'counter')
705                 self.ixnet.setMultiAttribute(prio_counter,
706                                         '-start', vlans[i].prio,
707                                         '-step', vlans[i].prio_step,
708                                         '-direction', vlans[i].prio_direction)
709             elif vlans[i].prio is not None:
710                 prio_obj = self.ixnet.getAttribute(vlan_obj, '-priority')
711                 self.ixnet.setMultiAttribute(prio_obj + '/singleValue',
712                                              '-value', vlans[i].prio)
713
714             if vlans[i].tp_id is not None:
715                 tp_id_obj = self.ixnet.getAttribute(vlan_obj, '-tpid')
716                 self.ixnet.setMultiAttribute(tp_id_obj + '/singleValue',
717                                              '-value', vlans[i].tp_id)
718
719         self.ixnet.commit()
720
721     def add_vlans(self, ethernet, vlans):
722         log.debug("add_vlans: ethernet='%s'", ethernet)
723
724         if vlans is None or len(vlans) == 0:
725             raise RuntimeError(
726                 "Invalid 'vlans' argument. Expected list of Vlan instances.")
727
728         self._create_vlans(ethernet, len(vlans))
729         self._configure_vlans(ethernet, vlans)
730
731     def add_ipv4(self, ethernet, name='',
732                  addr=None, addr_step=None, addr_direction='increment',
733                  prefix=None, prefix_step=None, prefix_direction='increment',
734                  gateway=None, gw_step=None, gw_direction='increment'):
735         log.debug("add_ipv4: ethernet='%s' name='%s'", ethernet, name)
736         obj = self.ixnet.add(ethernet, 'ipv4')
737         if name != '':
738             self.ixnet.setAttribute(obj, '-name', name)
739             self.ixnet.commit()
740
741         if addr_step is not None:
742             # handle counter pattern
743             _address = self.ixnet.getAttribute(obj, '-address')
744             self.ixnet.setMultiAttribute(_address, '-clearOverlays', 'true',
745                                          '-pattern', 'counter')
746
747             address_counter = self.ixnet.add(_address, 'counter')
748             self.ixnet.setMultiAttribute(address_counter,
749                                          '-start', addr,
750                                          '-step', addr_step,
751                                          '-direction', addr_direction)
752         elif addr is not None:
753             # handle single value
754             _address = self.ixnet.getAttribute(obj, '-address')
755             self.ixnet.setMultiAttribute(_address + '/singleValue', '-value',
756                                          addr)
757
758         if prefix_step is not None:
759             # handle counter pattern
760             _prefix = self.ixnet.getAttribute(obj, '-prefix')
761             self.ixnet.setMultiAttribute(_prefix, '-clearOverlays', 'true',
762                                          '-pattern', 'counter')
763             prefix_counter = self.ixnet.add(_prefix, 'counter')
764             self.ixnet.setMultiAttribute(prefix_counter,
765                                          '-start', prefix,
766                                          '-step', prefix_step,
767                                          '-direction', prefix_direction)
768         elif prefix is not None:
769             # handle single value
770             _prefix = self.ixnet.getAttribute(obj, '-prefix')
771             self.ixnet.setMultiAttribute(_prefix + '/singleValue', '-value',
772                                          prefix)
773
774         if gw_step is not None:
775             # handle counter pattern
776             _gateway = self.ixnet.getAttribute(obj, '-gatewayIp')
777             self.ixnet.setMultiAttribute(_gateway, '-clearOverlays', 'true',
778                                          '-pattern', 'counter')
779
780             gateway_counter = self.ixnet.add(_gateway, 'counter')
781             self.ixnet.setMultiAttribute(gateway_counter,
782                                          '-start', gateway,
783                                          '-step', gw_step,
784                                          '-direction', gw_direction)
785         elif gateway is not None:
786             # handle single value
787             _gateway = self.ixnet.getAttribute(obj, '-gatewayIp')
788             self.ixnet.setMultiAttribute(_gateway + '/singleValue', '-value',
789                                          gateway)
790
791         self.ixnet.commit()
792         return obj
793
794     def add_pppox_client(self, xproto, auth, user, pwd):
795         log.debug(
796             "add_pppox_client: xproto='%s', auth='%s', user='%s', pwd='%s'",
797             xproto, auth, user, pwd)
798         obj = self.ixnet.add(xproto, 'pppoxclient')
799         self.ixnet.commit()
800
801         if auth == 'pap':
802             auth_type = self.ixnet.getAttribute(obj, '-authType')
803             self.ixnet.setMultiAttribute(auth_type + '/singleValue', '-value',
804                                          auth)
805             pap_user = self.ixnet.getAttribute(obj, '-papUser')
806             self.ixnet.setMultiAttribute(pap_user + '/singleValue', '-value',
807                                          user)
808             pap_pwd = self.ixnet.getAttribute(obj, '-papPassword')
809             self.ixnet.setMultiAttribute(pap_pwd + '/singleValue', '-value',
810                                          pwd)
811         else:
812             raise NotImplementedError()
813
814         self.ixnet.commit()
815         return obj
816
817     def add_bgp(self, ipv4, dut_ip, local_as, bgp_type=None):
818         """Add BGP protocol"""
819         log.debug("add_bgp: ipv4='%s', dut_ip='%s', local_as='%s'", ipv4,
820                   dut_ip, local_as)
821         obj = self.ixnet.add(ipv4, 'bgpIpv4Peer')
822         self.ixnet.commit()
823
824         # Set DUT IP address
825         dut_ip_addr = self.ixnet.getAttribute(obj, '-dutIp')
826         self.ixnet.setAttribute(dut_ip_addr + '/singleValue',
827                                 '-value', dut_ip)
828
829         # Set local AS number
830         local_as_number = self.ixnet.getAttribute(obj, '-localAs2Bytes')
831         self.ixnet.setAttribute(local_as_number + '/singleValue',
832                                 '-value', local_as)
833
834         if bgp_type:
835             # Set BGP type. If not specified, default value is using.
836             # Default type is "internal"
837             bgp_type_field = self.ixnet.getAttribute(obj, '-type')
838             self.ixnet.setAttribute(bgp_type_field + '/singleValue',
839                                     '-value', bgp_type)
840         self.ixnet.commit()
841         return obj