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