1 # Copyright (c) 2016-2017 Intel Corporation
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
20 from yardstick.common import exceptions
21 from yardstick.common import utils
22 from yardstick.network_services.traffic_profile import base as tp_base
25 log = logging.getLogger(__name__)
30 PROTO_ETHERNET = 'ethernet'
37 SINGLE_VALUE = "singleValue"
42 ETHER_TYPE_802_1ad = '0x88a8'
44 IP_VERSION_4_MASK = 24
45 IP_VERSION_6_MASK = 64
47 TRAFFIC_STATUS_STARTED = 'started'
48 TRAFFIC_STATUS_STOPPED = 'stopped'
50 SUPPORTED_PROTO = [PROTO_UDP]
53 # NOTE(ralonsoh): this pragma will be removed in the last patch of this series
54 class IxNextgen(object): # pragma: no cover
56 PORT_STATS_NAME_MAP = {
57 "stat_name": 'Stat Name',
58 "Frames_Tx": 'Frames Tx.',
59 "Valid_Frames_Rx": 'Valid Frames Rx.',
60 "Frames_Tx_Rate": 'Frames Tx. Rate',
61 "Valid_Frames_Rx_Rate": 'Valid Frames Rx. Rate',
62 "Tx_Rate_Kbps": 'Tx. Rate (Kbps)',
63 "Rx_Rate_Kbps": 'Rx. Rate (Kbps)',
64 "Tx_Rate_Mbps": 'Tx. Rate (Mbps)',
65 "Rx_Rate_Mbps": 'Rx. Rate (Mbps)',
69 "Store-Forward_Avg_latency_ns": 'Store-Forward Avg Latency (ns)',
70 "Store-Forward_Min_latency_ns": 'Store-Forward Min Latency (ns)',
71 "Store-Forward_Max_latency_ns": 'Store-Forward Max Latency (ns)',
75 def get_config(tg_cfg):
78 external_interface = tg_cfg["vdu"][0]["external-interface"]
79 for intf in external_interface:
80 card_port0 = intf["virtual-interface"]["vpci"]
81 card0, port0 = card_port0.split(':')[:2]
86 'machine': tg_cfg["mgmt-interface"]["ip"],
87 'port': tg_cfg["mgmt-interface"]["tg-config"]["tcl_port"],
88 'chassis': tg_cfg["mgmt-interface"]["tg-config"]["ixchassis"],
91 'output_dir': tg_cfg["mgmt-interface"]["tg-config"]["dut_result_dir"],
92 'version': tg_cfg["mgmt-interface"]["tg-config"]["version"],
98 def __init__(self): # pragma: no cover
105 def ixnet(self): # pragma: no cover
108 raise exceptions.IxNetworkClientNotConnected()
110 def _get_config_element_by_flow_group_name(self, flow_group_name):
111 """Get a config element using the flow group name
113 Each named flow group contains one config element (by configuration).
114 According to the documentation, "configElements" is a list and "each
115 item in this list is aligned to the sequential order of your endpoint
118 :param flow_group_name: (str) flow group name; this parameter is
119 always a number (converted to string) starting
121 :return: (str) config element reference ID or None.
123 traffic_item = self.ixnet.getList(self.ixnet.getRoot() + '/traffic',
125 flow_groups = self.ixnet.getList(traffic_item, 'endpointSet')
126 for flow_group in flow_groups:
127 if (str(self.ixnet.getAttribute(flow_group, '-name')) ==
129 return traffic_item + '/configElement:' + flow_group_name
131 def _get_stack_item(self, flow_group_name, protocol_name):
132 """Return the stack item given the flow group name and the proto name
134 :param flow_group_name: (str) flow group name
135 :param protocol_name: (str) protocol name, referred to PROTO_*
137 :return: list of stack item descriptors
139 celement = self._get_config_element_by_flow_group_name(flow_group_name)
141 raise exceptions.IxNetworkFlowNotPresent(
142 flow_group=flow_group_name)
143 stack_items = self.ixnet.getList(celement, 'stack')
144 return [s_i for s_i in stack_items if protocol_name in s_i]
146 def _get_field_in_stack_item(self, stack_item, field_name):
147 """Return the field in a stack item given the name
149 :param stack_item: (str) stack item descriptor
150 :param field_name: (str) field name
151 :return: (str) field descriptor
153 fields = self.ixnet.getList(stack_item, 'field')
154 for field in (field for field in fields if field_name in field):
156 raise exceptions.IxNetworkFieldNotPresentInStackItem(
157 field_name=field_name, stack_item=stack_item)
159 def _get_traffic_state(self):
160 """Get traffic state"""
161 return self.ixnet.getAttribute(self.ixnet.getRoot() + 'traffic',
164 def is_traffic_running(self):
165 """Returns true if traffic state == TRAFFIC_STATUS_STARTED"""
166 return self._get_traffic_state() == TRAFFIC_STATUS_STARTED
168 def is_traffic_stopped(self):
169 """Returns true if traffic state == TRAFFIC_STATUS_STOPPED"""
170 return self._get_traffic_state() == TRAFFIC_STATUS_STOPPED
173 def _parse_framesize(framesize):
174 """Parse "framesize" config param. to return a list of weighted pairs
176 :param framesize: dictionary of frame sizes and weights
177 :return: list of paired frame sizes and weights
179 weighted_range_pairs = []
180 for size, weight in ((s, w) for (s, w) in framesize.items()
182 size = int(size.upper().replace('B', ''))
183 weighted_range_pairs.append([size, size, int(weight)])
184 return weighted_range_pairs
186 def iter_over_get_lists(self, x1, x2, y2, offset=0):
187 for x in self.ixnet.getList(x1, x2):
188 y_list = self.ixnet.getList(x, y2)
189 for i, y in enumerate(y_list, offset):
192 def connect(self, tg_cfg):
193 self._cfg = self.get_config(tg_cfg)
194 self._ixnet = IxNetwork.IxNet()
196 machine = self._cfg['machine']
197 port = str(self._cfg['port'])
198 version = str(self._cfg['version'])
199 return self.ixnet.connect(machine, '-port', port,
202 def clear_config(self):
203 """Wipe out any possible configuration present in the client"""
204 self.ixnet.execute('newConfig')
206 def assign_ports(self):
207 """Create and assign vports for each physical port defined in config
209 This configuration is present in the IXIA profile file. E.g.:
214 vpci: "2:15" # Card:port
217 local_ip: "152.16.100.20"
218 netmask: "255.255.0.0"
219 local_mac: "00:98:10:64:14:00"
223 chassis_ip = self._cfg['chassis']
224 ports = [(chassis_ip, card, port) for card, port in
225 zip(self._cfg['cards'], self._cfg['ports'])]
227 log.info('Create and assign vports: %s', ports)
229 vport = self.ixnet.add(self.ixnet.getRoot(), 'vport')
231 self.ixnet.execute('assignPorts', [port], [], [vport], True)
233 if self.ixnet.getAttribute(vport, '-state') != 'up':
234 log.warning('Port %s is down', vport)
236 def _create_traffic_item(self):
237 """Create the traffic item to hold the flow groups
239 The traffic item tracking by "Traffic Item" is enabled to retrieve the
242 log.info('Create the traffic item "RFC2544"')
243 traffic_item = self.ixnet.add(self.ixnet.getRoot() + '/traffic',
245 self.ixnet.setMultiAttribute(traffic_item, '-name', 'RFC2544',
246 '-trafficType', 'raw')
249 traffic_item_id = self.ixnet.remapIds(traffic_item)[0]
250 self.ixnet.setAttribute(traffic_item_id + '/tracking',
251 '-trackBy', 'trafficGroupId0')
254 def _create_flow_groups(self):
255 """Create the flow groups between the assigned ports"""
256 traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
258 log.info('Create the flow groups')
259 vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
260 uplink_ports = vports[::2]
261 downlink_ports = vports[1::2]
263 for up, down in zip(uplink_ports, downlink_ports):
264 log.info('FGs: %s <--> %s', up, down)
265 endpoint_set_1 = self.ixnet.add(traffic_item_id, 'endpointSet')
266 endpoint_set_2 = self.ixnet.add(traffic_item_id, 'endpointSet')
267 self.ixnet.setMultiAttribute(
268 endpoint_set_1, '-name', str(index + 1),
269 '-sources', [up + '/protocols'],
270 '-destinations', [down + '/protocols'])
271 self.ixnet.setMultiAttribute(
272 endpoint_set_2, '-name', str(index + 2),
273 '-sources', [down + '/protocols'],
274 '-destinations', [up + '/protocols'])
278 def _append_procotol_to_stack(self, protocol_name, previous_element):
279 """Append a new element in the packet definition stack"""
280 protocol = (self.ixnet.getRoot() +
281 '/traffic/protocolTemplate:"{}"'.format(protocol_name))
282 self.ixnet.execute('append', previous_element, protocol)
284 def _setup_config_elements(self):
285 """Setup the config elements
287 The traffic item is configured to allow individual configurations per
288 config element. The default frame configuration is applied:
289 Ethernet II: added by default
292 Payload: added by default
293 Ethernet II (Trailer): added by default
296 traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
298 log.info('Split the frame rate distribution per config element')
299 config_elements = self.ixnet.getList(traffic_item_id, 'configElement')
300 for config_element in config_elements:
301 self.ixnet.setAttribute(config_element + '/frameRateDistribution',
302 '-portDistribution', 'splitRateEvenly')
303 self.ixnet.setAttribute(config_element + '/frameRateDistribution',
304 '-streamDistribution', 'splitRateEvenly')
306 self._append_procotol_to_stack(
307 PROTO_UDP, config_element + '/stack:"ethernet-1"')
308 self._append_procotol_to_stack(
309 PROTO_IPV4, config_element + '/stack:"ethernet-1"')
311 def create_traffic_model(self):
312 """Create a traffic item and the needed flow groups
314 Each flow group inside the traffic item (only one is present)
315 represents the traffic between two ports:
317 FlowGroup1: port1 -> port2
318 FlowGroup2: port1 <- port2
319 FlowGroup3: port3 -> port4
320 FlowGroup4: port3 <- port4
322 self._create_traffic_item()
323 self._create_flow_groups()
324 self._setup_config_elements()
326 def _update_frame_mac(self, ethernet_descriptor, field, mac_address):
327 """Set the MAC address in a config element stack Ethernet field
329 :param ethernet_descriptor: (str) ethernet descriptor, e.g.:
330 /traffic/trafficItem:1/configElement:1/stack:"ethernet-1"
331 :param field: (str) field name, e.g.: destinationAddress
332 :param mac_address: (str) MAC address
334 field_descriptor = self._get_field_in_stack_item(ethernet_descriptor,
336 self.ixnet.setMultiAttribute(field_descriptor,
337 '-singleValue', mac_address,
338 '-fieldValue', mac_address,
339 '-valueType', 'singleValue')
342 def update_frame(self, traffic, duration):
343 """Update the L2 frame
345 This function updates the L2 frame options:
346 - Traffic type: "continuous", "fixedDuration".
347 - Duration: in case of traffic_type="fixedDuration", amount of seconds
349 - Rate: in frames per seconds or percentage.
350 - Type of rate: "framesPerSecond" or "percentLineRate" ("bitsPerSecond"
352 - Frame size: custom IMIX [1] definition; a list of packet size in
353 bytes and the weight. E.g.:
354 [[64, 64, 10], [128, 128, 15], [512, 512, 5]]
356 [1] https://en.wikipedia.org/wiki/Internet_Mix
358 :param traffic: list of traffic elements; each traffic element contains
359 the injection parameter for each flow group.
360 :param duration: (int) injection time in seconds.
362 for traffic_param in traffic.values():
363 fg_id = str(traffic_param['id'])
364 config_element = self._get_config_element_by_flow_group_name(fg_id)
365 if not config_element:
366 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
368 type = traffic_param.get('traffic_type', 'fixedDuration')
369 rate = traffic_param['rate']
371 'framesPerSecond' if traffic_param['rate_unit'] ==
372 tp_base.TrafficProfileConfig.RATE_FPS else 'percentLineRate')
373 weighted_range_pairs = self._parse_framesize(
374 traffic_param['outer_l2']['framesize'])
375 srcmac = str(traffic_param.get('srcmac', '00:00:00:00:00:01'))
376 dstmac = str(traffic_param.get('dstmac', '00:00:00:00:00:02'))
378 if traffic_param['outer_l2']['QinQ']:
379 s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
380 c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
382 field_descriptor = self._get_field_in_stack_item(
383 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
386 self.ixnet.setMultiAttribute(field_descriptor,
388 '-singleValue', ETHER_TYPE_802_1ad,
389 '-fieldValue', ETHER_TYPE_802_1ad,
390 '-valueType', SINGLE_VALUE)
392 self._append_procotol_to_stack(
393 PROTO_VLAN, config_element + '/stack:"ethernet-1"')
394 self._append_procotol_to_stack(
395 PROTO_VLAN, config_element + '/stack:"ethernet-1"')
397 self._update_vlan_tag(fg_id, s_vlan, S_VLAN)
398 self._update_vlan_tag(fg_id, c_vlan, C_VLAN)
400 self.ixnet.setMultiAttribute(
401 config_element + '/transmissionControl',
402 '-type', type, '-duration', duration)
403 self.ixnet.setMultiAttribute(
404 config_element + '/frameRate',
405 '-rate', rate, '-type', rate_unit)
406 self.ixnet.setMultiAttribute(
407 config_element + '/frameSize',
408 '-type', 'weightedPairs',
409 '-weightedRangePairs', weighted_range_pairs)
412 self._update_frame_mac(
413 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
414 'destinationAddress', dstmac)
415 self._update_frame_mac(
416 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
417 'sourceAddress', srcmac)
419 def _update_vlan_tag(self, fg_id, params, vlan=0):
420 field_to_param_map = {
421 'vlanUserPriority': 'priority',
425 for field, param in field_to_param_map.items():
426 value = params.get(param)
428 field_descriptor = self._get_field_in_stack_item(
429 self._get_stack_item(fg_id, PROTO_VLAN)[vlan],
432 self.ixnet.setMultiAttribute(field_descriptor,
434 '-singleValue', value,
435 '-fieldValue', value,
436 '-valueType', SINGLE_VALUE)
440 def _update_ipv4_address(self, ip_descriptor, field, ip_address, seed,
442 """Set the IPv4 address in a config element stack IP field
444 :param ip_descriptor: (str) IP descriptor, e.g.:
445 /traffic/trafficItem:1/configElement:1/stack:"ipv4-2"
446 :param field: (str) field name, e.g.: scrIp, dstIp
447 :param ip_address: (str) IP address
448 :param seed: (int) seed length
449 :param mask: (int) IP address mask length
450 :param count: (int) number of random IPs to generate
452 field_descriptor = self._get_field_in_stack_item(ip_descriptor,
454 random_mask = str(ipaddress.IPv4Address(
455 2**(ipaddress.IPV4LENGTH - mask) - 1).compressed)
456 self.ixnet.setMultiAttribute(field_descriptor,
458 '-fixedBits', ip_address,
459 '-randomMask', random_mask,
460 '-valueType', 'random',
461 '-countValue', count)
464 def update_ip_packet(self, traffic):
465 """Update the IP packet
467 NOTE: Only IPv4 is currently supported.
468 :param traffic: list of traffic elements; each traffic element contains
469 the injection parameter for each flow group.
471 # NOTE(ralonsoh): L4 configuration is not set.
472 for traffic_param in traffic.values():
473 fg_id = str(traffic_param['id'])
474 if not self._get_config_element_by_flow_group_name(fg_id):
475 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
477 count = traffic_param['outer_l3']['count']
478 srcip = str(traffic_param['outer_l3']['srcip'])
479 dstip = str(traffic_param['outer_l3']['dstip'])
480 seed = traffic_param['outer_l3']['seed']
481 srcmask = traffic_param['outer_l3']['srcmask'] or IP_VERSION_4_MASK
482 dstmask = traffic_param['outer_l3']['dstmask'] or IP_VERSION_4_MASK
484 self._update_ipv4_address(
485 self._get_stack_item(fg_id, PROTO_IPV4)[0],
486 'srcIp', srcip, seed, srcmask, count)
487 self._update_ipv4_address(
488 self._get_stack_item(fg_id, PROTO_IPV4)[0],
489 'dstIp', dstip, seed, dstmask, count)
491 def update_l4(self, traffic):
492 """Update the L4 headers
494 NOTE: Only UDP is currently supported
495 :param traffic: list of traffic elements; each traffic element contains
496 the injection parameter for each flow group
498 for traffic_param in traffic.values():
499 fg_id = str(traffic_param['id'])
500 if not self._get_config_element_by_flow_group_name(fg_id):
501 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
503 proto = traffic_param['outer_l3']['proto']
504 if proto not in SUPPORTED_PROTO:
505 raise exceptions.IXIAUnsupportedProtocol(protocol=proto)
507 count = traffic_param['outer_l4']['count']
508 seed = traffic_param['outer_l4']['seed']
510 srcport = traffic_param['outer_l4']['srcport']
511 srcmask = traffic_param['outer_l4']['srcportmask']
513 dstport = traffic_param['outer_l4']['dstport']
514 dstmask = traffic_param['outer_l4']['dstportmask']
516 if proto in SUPPORTED_PROTO:
517 self._update_udp_port(self._get_stack_item(fg_id, proto)[0],
518 'srcPort', srcport, seed, srcmask, count)
520 self._update_udp_port(self._get_stack_item(fg_id, proto)[0],
521 'dstPort', dstport, seed, dstmask, count)
523 def _update_udp_port(self, descriptor, field, value,
524 seed=1, mask=0, count=1):
525 """Set the UDP port in a config element stack UDP field
527 :param udp_descriptor: (str) UDP descriptor, e.g.:
528 /traffic/trafficItem:1/configElement:1/stack:"udp-3"
529 :param field: (str) field name, e.g.: scrPort, dstPort
530 :param value: (int) UDP port fixed bits
531 :param seed: (int) seed length
532 :param mask: (int) UDP port mask
533 :param count: (int) number of random ports to generate
535 field_descriptor = self._get_field_in_stack_item(descriptor, field)
540 self.ixnet.setMultiAttribute(field_descriptor,
545 '-valueType', 'random',
546 '-countValue', count)
550 def _build_stats_map(self, view_obj, name_map):
551 return {data_yardstick: self.ixnet.execute(
552 'getColumnValues', view_obj, data_ixia)
553 for data_yardstick, data_ixia in name_map.items()}
555 def get_statistics(self):
556 """Retrieve port and flow statistics
558 "Port Statistics" parameters are stored in self.PORT_STATS_NAME_MAP.
559 "Flow Statistics" parameters are stored in self.LATENCY_NAME_MAP.
561 :return: dictionary with the statistics; the keys of this dictionary
562 are PORT_STATS_NAME_MAP and LATENCY_NAME_MAP keys.
564 port_statistics = '::ixNet::OBJ-/statistics/view:"Port Statistics"'
565 flow_statistics = '::ixNet::OBJ-/statistics/view:"Flow Statistics"'
566 stats = self._build_stats_map(port_statistics,
567 self.PORT_STATS_NAME_MAP)
568 stats.update(self._build_stats_map(flow_statistics,
569 self.LATENCY_NAME_MAP))
572 def start_traffic(self):
573 """Start the traffic injection in the traffic item
575 By configuration, there is only one traffic item. This function returns
576 when the traffic state is TRAFFIC_STATUS_STARTED.
578 traffic_items = self.ixnet.getList('/traffic', 'trafficItem')
579 if self.is_traffic_running():
580 self.ixnet.execute('stop', '/traffic')
581 # pylint: disable=unnecessary-lambda
582 utils.wait_until_true(lambda: self.is_traffic_stopped())
584 self.ixnet.execute('generate', traffic_items)
585 self.ixnet.execute('apply', '/traffic')
586 self.ixnet.execute('start', '/traffic')
587 # pylint: disable=unnecessary-lambda
588 utils.wait_until_true(lambda: self.is_traffic_running())