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.
19 from yardstick.common import exceptions
20 from yardstick.common import utils
23 log = logging.getLogger(__name__)
28 PROTO_ETHERNET = 'ethernet'
35 IP_VERSION_4_MASK = '0.0.0.255'
36 IP_VERSION_6_MASK = '0:0:0:0:0:0:0:ff'
38 TRAFFIC_STATUS_STARTED = 'started'
39 TRAFFIC_STATUS_STOPPED = 'stopped'
42 # NOTE(ralonsoh): this pragma will be removed in the last patch of this series
43 class IxNextgen(object): # pragma: no cover
45 PORT_STATS_NAME_MAP = {
46 "stat_name": 'Stat Name',
47 "Frames_Tx": 'Frames Tx.',
48 "Valid_Frames_Rx": 'Valid Frames Rx.',
49 "Frames_Tx_Rate": 'Frames Tx. Rate',
50 "Valid_Frames_Rx_Rate": 'Valid Frames Rx. Rate',
51 "Tx_Rate_Kbps": 'Tx. Rate (Kbps)',
52 "Rx_Rate_Kbps": 'Rx. Rate (Kbps)',
53 "Tx_Rate_Mbps": 'Tx. Rate (Mbps)',
54 "Rx_Rate_Mbps": 'Rx. Rate (Mbps)',
58 "Store-Forward_Avg_latency_ns": 'Store-Forward Avg Latency (ns)',
59 "Store-Forward_Min_latency_ns": 'Store-Forward Min Latency (ns)',
60 "Store-Forward_Max_latency_ns": 'Store-Forward Max Latency (ns)',
64 def get_config(tg_cfg):
67 external_interface = tg_cfg["vdu"][0]["external-interface"]
68 for intf in external_interface:
69 card_port0 = intf["virtual-interface"]["vpci"]
70 card0, port0 = card_port0.split(':')[:2]
75 'machine': tg_cfg["mgmt-interface"]["ip"],
76 'port': tg_cfg["mgmt-interface"]["tg-config"]["tcl_port"],
77 'chassis': tg_cfg["mgmt-interface"]["tg-config"]["ixchassis"],
80 'output_dir': tg_cfg["mgmt-interface"]["tg-config"]["dut_result_dir"],
81 'version': tg_cfg["mgmt-interface"]["tg-config"]["version"],
87 def __init__(self): # pragma: no cover
94 def ixnet(self): # pragma: no cover
97 raise exceptions.IxNetworkClientNotConnected()
99 def _get_config_element_by_flow_group_name(self, flow_group_name):
100 """Get a config element using the flow group name
102 Each named flow group contains one config element (by configuration).
103 According to the documentation, "configElements" is a list and "each
104 item in this list is aligned to the sequential order of your endpoint
107 :param flow_group_name: (str) flow group name; this parameter is
108 always a number (converted to string) starting
110 :return: (str) config element reference ID or None.
112 traffic_item = self.ixnet.getList(self.ixnet.getRoot() + '/traffic',
114 flow_groups = self.ixnet.getList(traffic_item, 'endpointSet')
115 for flow_group in flow_groups:
116 if (str(self.ixnet.getAttribute(flow_group, '-name')) ==
118 return traffic_item + '/configElement:' + flow_group_name
120 def _get_stack_item(self, flow_group_name, protocol_name):
121 """Return the stack item given the flow group name and the proto name
123 :param flow_group_name: (str) flow group name
124 :param protocol_name: (str) protocol name, referred to PROTO_*
126 :return: list of stack item descriptors
128 celement = self._get_config_element_by_flow_group_name(flow_group_name)
130 raise exceptions.IxNetworkFlowNotPresent(
131 flow_group=flow_group_name)
132 stack_items = self.ixnet.getList(celement, 'stack')
133 return [s_i for s_i in stack_items if protocol_name in s_i]
135 def _get_field_in_stack_item(self, stack_item, field_name):
136 """Return the field in a stack item given the name
138 :param stack_item: (str) stack item descriptor
139 :param field_name: (str) field name
140 :return: (str) field descriptor
142 fields = self.ixnet.getList(stack_item, 'field')
143 for field in (field for field in fields if field_name in field):
145 raise exceptions.IxNetworkFieldNotPresentInStackItem(
146 field_name=field_name, stack_item=stack_item)
148 def _get_traffic_state(self):
149 """Get traffic state"""
150 return self.ixnet.getAttribute(self.ixnet.getRoot() + 'traffic',
153 def is_traffic_running(self):
154 """Returns true if traffic state == TRAFFIC_STATUS_STARTED"""
155 return self._get_traffic_state() == TRAFFIC_STATUS_STARTED
157 def is_traffic_stopped(self):
158 """Returns true if traffic state == TRAFFIC_STATUS_STOPPED"""
159 return self._get_traffic_state() == TRAFFIC_STATUS_STOPPED
162 def _parse_framesize(framesize):
163 """Parse "framesize" config param. to return a list of weighted pairs
165 :param framesize: dictionary of frame sizes and weights
166 :return: list of paired frame sizes and weights
168 weighted_range_pairs = []
169 for size, weight in ((s, w) for (s, w) in framesize.items()
171 size = int(size.upper().replace('B', ''))
172 weighted_range_pairs.append([size, size, int(weight)])
173 return weighted_range_pairs
175 def iter_over_get_lists(self, x1, x2, y2, offset=0):
176 for x in self.ixnet.getList(x1, x2):
177 y_list = self.ixnet.getList(x, y2)
178 for i, y in enumerate(y_list, offset):
181 def connect(self, tg_cfg):
182 self._cfg = self.get_config(tg_cfg)
183 self._ixnet = IxNetwork.IxNet()
185 machine = self._cfg['machine']
186 port = str(self._cfg['port'])
187 version = str(self._cfg['version'])
188 return self.ixnet.connect(machine, '-port', port,
191 def clear_config(self):
192 """Wipe out any possible configuration present in the client"""
193 self.ixnet.execute('newConfig')
195 def assign_ports(self):
196 """Create and assign vports for each physical port defined in config
198 This configuration is present in the IXIA profile file. E.g.:
203 vpci: "2:15" # Card:port
206 local_ip: "152.16.100.20"
207 netmask: "255.255.0.0"
208 local_mac: "00:98:10:64:14:00"
212 chassis_ip = self._cfg['chassis']
213 ports = [(chassis_ip, card, port) for card, port in
214 zip(self._cfg['cards'], self._cfg['ports'])]
216 log.info('Create and assign vports: %s', ports)
218 vport = self.ixnet.add(self.ixnet.getRoot(), 'vport')
220 self.ixnet.execute('assignPorts', [port], [], [vport], True)
222 if self.ixnet.getAttribute(vport, '-state') != 'up':
223 log.warning('Port %s is down', vport)
225 def _create_traffic_item(self):
226 """Create the traffic item to hold the flow groups
228 The traffic item tracking by "Traffic Item" is enabled to retrieve the
231 log.info('Create the traffic item "RFC2544"')
232 traffic_item = self.ixnet.add(self.ixnet.getRoot() + '/traffic',
234 self.ixnet.setMultiAttribute(traffic_item, '-name', 'RFC2544',
235 '-trafficType', 'raw')
238 traffic_item_id = self.ixnet.remapIds(traffic_item)[0]
239 self.ixnet.setAttribute(traffic_item_id + '/tracking',
240 '-trackBy', 'trafficGroupId0')
243 def _create_flow_groups(self):
244 """Create the flow groups between the assigned ports"""
245 traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
247 log.info('Create the flow groups')
248 vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
249 uplink_ports = vports[::2]
250 downlink_ports = vports[1::2]
252 for up, down in zip(uplink_ports, downlink_ports):
253 log.info('FGs: %s <--> %s', up, down)
254 endpoint_set_1 = self.ixnet.add(traffic_item_id, 'endpointSet')
255 endpoint_set_2 = self.ixnet.add(traffic_item_id, 'endpointSet')
256 self.ixnet.setMultiAttribute(
257 endpoint_set_1, '-name', str(index + 1),
258 '-sources', [up + '/protocols'],
259 '-destinations', [down + '/protocols'])
260 self.ixnet.setMultiAttribute(
261 endpoint_set_2, '-name', str(index + 2),
262 '-sources', [down + '/protocols'],
263 '-destinations', [up + '/protocols'])
267 def _append_procotol_to_stack(self, protocol_name, previous_element):
268 """Append a new element in the packet definition stack"""
269 protocol = (self.ixnet.getRoot() +
270 '/traffic/protocolTemplate:"{}"'.format(protocol_name))
271 self.ixnet.execute('append', previous_element, protocol)
273 def _setup_config_elements(self):
274 """Setup the config elements
276 The traffic item is configured to allow individual configurations per
277 config element. The default frame configuration is applied:
278 Ethernet II: added by default
281 Payload: added by default
282 Ethernet II (Trailer): added by default
285 traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
287 log.info('Split the frame rate distribution per config element')
288 config_elements = self.ixnet.getList(traffic_item_id, 'configElement')
289 for config_element in config_elements:
290 self.ixnet.setAttribute(config_element + '/frameRateDistribution',
291 '-portDistribution', 'splitRateEvenly')
292 self.ixnet.setAttribute(config_element + '/frameRateDistribution',
293 '-streamDistribution', 'splitRateEvenly')
295 self._append_procotol_to_stack(
296 PROTO_UDP, config_element + '/stack:"ethernet-1"')
297 self._append_procotol_to_stack(
298 PROTO_IPV4, config_element + '/stack:"ethernet-1"')
300 def create_traffic_model(self):
301 """Create a traffic item and the needed flow groups
303 Each flow group inside the traffic item (only one is present)
304 represents the traffic between two ports:
306 FlowGroup1: port1 -> port2
307 FlowGroup2: port1 <- port2
308 FlowGroup3: port3 -> port4
309 FlowGroup4: port3 <- port4
311 self._create_traffic_item()
312 self._create_flow_groups()
313 self._setup_config_elements()
315 def _update_frame_mac(self, ethernet_descriptor, field, mac_address):
316 """Set the MAC address in a config element stack Ethernet field
318 :param ethernet_descriptor: (str) ethernet descriptor, e.g.:
319 /traffic/trafficItem:1/configElement:1/stack:"ethernet-1"
320 :param field: (str) field name, e.g.: destinationAddress
321 :param mac_address: (str) MAC address
323 field_descriptor = self._get_field_in_stack_item(ethernet_descriptor,
325 self.ixnet.setMultiAttribute(field_descriptor,
326 '-singleValue', mac_address,
327 '-fieldValue', mac_address,
328 '-valueType', 'singleValue')
331 def update_frame(self, traffic):
332 """Update the L2 frame
334 This function updates the L2 frame options:
335 - Traffic type: "continuous", "fixedDuration".
336 - Duration: in case of traffic_type="fixedDuration", amount of seconds
338 - Rate: in frames per seconds or percentage.
339 - Type of rate: "framesPerSecond" ("bitsPerSecond" and
340 "percentLineRate" no used)
341 - Frame size: custom IMIX [1] definition; a list of packet size in
342 bytes and the weight. E.g.:
343 [[64, 64, 10], [128, 128, 15], [512, 512, 5]]
345 [1] https://en.wikipedia.org/wiki/Internet_Mix
347 :param traffic: list of traffic elements; each traffic element contains
348 the injection parameter for each flow group.
350 for traffic_param in traffic.values():
351 fg_id = str(traffic_param['id'])
352 config_element = self._get_config_element_by_flow_group_name(fg_id)
353 if not config_element:
354 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
356 type = traffic_param.get('traffic_type', 'fixedDuration')
357 duration = traffic_param.get('duration', 30)
358 rate = traffic_param['iload']
359 weighted_range_pairs = self._parse_framesize(
360 traffic_param['outer_l2']['framesize'])
361 srcmac = str(traffic_param.get('srcmac', '00:00:00:00:00:01'))
362 dstmac = str(traffic_param.get('dstmac', '00:00:00:00:00:02'))
363 # NOTE(ralonsoh): add QinQ tagging when
364 # traffic_param['outer_l2']['QinQ'] exists.
365 # s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
366 # c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
368 self.ixnet.setMultiAttribute(
369 config_element + '/transmissionControl',
370 '-type', type, '-duration', duration)
371 self.ixnet.setMultiAttribute(
372 config_element + '/frameRate',
373 '-rate', rate, '-type', 'framesPerSecond')
374 self.ixnet.setMultiAttribute(
375 config_element + '/frameSize',
376 '-type', 'weightedPairs',
377 '-weightedRangePairs', weighted_range_pairs)
380 self._update_frame_mac(
381 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
382 'destinationAddress', dstmac)
383 self._update_frame_mac(
384 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
385 'sourceAddress', srcmac)
387 def _update_ipv4_address(self, ip_descriptor, field, ip_address, seed,
389 """Set the IPv4 address in a config element stack IP field
391 :param ip_descriptor: (str) IP descriptor, e.g.:
392 /traffic/trafficItem:1/configElement:1/stack:"ipv4-2"
393 :param field: (str) field name, e.g.: scrIp, dstIp
394 :param ip_address: (str) IP address
395 :param seed: (int) seed length
396 :param mask: (str) IP address mask
397 :param count: (int) number of random IPs to generate
399 field_descriptor = self._get_field_in_stack_item(ip_descriptor,
401 self.ixnet.setMultiAttribute(field_descriptor,
403 '-fixedBits', ip_address,
405 '-valueType', 'random',
406 '-countValue', count)
409 def update_ip_packet(self, traffic):
410 """Update the IP packet
412 NOTE: Only IPv4 is currently supported.
413 :param traffic: list of traffic elements; each traffic element contains
414 the injection parameter for each flow group.
416 # NOTE(ralonsoh): L4 configuration is not set.
417 for traffic_param in traffic.values():
418 fg_id = str(traffic_param['id'])
419 if not self._get_config_element_by_flow_group_name(fg_id):
420 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
422 count = traffic_param['outer_l3']['count']
423 srcip4 = str(traffic_param['outer_l3']['srcip4'])
424 dstip4 = str(traffic_param['outer_l3']['dstip4'])
426 self._update_ipv4_address(
427 self._get_stack_item(fg_id, PROTO_IPV4)[0],
428 'srcIp', srcip4, 1, IP_VERSION_4_MASK, count)
429 self._update_ipv4_address(
430 self._get_stack_item(fg_id, PROTO_IPV4)[0],
431 'dstIp', dstip4, 1, IP_VERSION_4_MASK, count)
433 def _build_stats_map(self, view_obj, name_map):
434 return {data_yardstick: self.ixnet.execute(
435 'getColumnValues', view_obj, data_ixia)
436 for data_yardstick, data_ixia in name_map.items()}
438 def get_statistics(self):
439 """Retrieve port and flow statistics
441 "Port Statistics" parameters are stored in self.PORT_STATS_NAME_MAP.
442 "Flow Statistics" parameters are stored in self.LATENCY_NAME_MAP.
444 :return: dictionary with the statistics; the keys of this dictionary
445 are PORT_STATS_NAME_MAP and LATENCY_NAME_MAP keys.
447 port_statistics = '::ixNet::OBJ-/statistics/view:"Port Statistics"'
448 flow_statistics = '::ixNet::OBJ-/statistics/view:"Flow Statistics"'
449 stats = self._build_stats_map(port_statistics,
450 self.PORT_STATS_NAME_MAP)
451 stats.update(self._build_stats_map(flow_statistics,
452 self.LATENCY_NAME_MAP))
455 def start_traffic(self):
456 """Start the traffic injection in the traffic item
458 By configuration, there is only one traffic item. This function returns
459 when the traffic state is TRAFFIC_STATUS_STARTED.
461 traffic_items = self.ixnet.getList('/traffic', 'trafficItem')
462 if self.is_traffic_running():
463 self.ixnet.execute('stop', '/traffic')
464 # pylint: disable=unnecessary-lambda
465 utils.wait_until_true(lambda: self.is_traffic_stopped())
467 self.ixnet.execute('generate', traffic_items)
468 self.ixnet.execute('apply', '/traffic')
469 self.ixnet.execute('start', '/traffic')
470 # pylint: disable=unnecessary-lambda
471 utils.wait_until_true(lambda: self.is_traffic_running())