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
21 from yardstick.network_services.traffic_profile import base as tp_base
24 log = logging.getLogger(__name__)
29 PROTO_ETHERNET = 'ethernet'
36 IP_VERSION_4_MASK = '0.0.0.255'
37 IP_VERSION_6_MASK = '0:0:0:0:0:0:0:ff'
39 TRAFFIC_STATUS_STARTED = 'started'
40 TRAFFIC_STATUS_STOPPED = 'stopped'
43 # NOTE(ralonsoh): this pragma will be removed in the last patch of this series
44 class IxNextgen(object): # pragma: no cover
46 PORT_STATS_NAME_MAP = {
47 "stat_name": 'Stat Name',
48 "Frames_Tx": 'Frames Tx.',
49 "Valid_Frames_Rx": 'Valid Frames Rx.',
50 "Frames_Tx_Rate": 'Frames Tx. Rate',
51 "Valid_Frames_Rx_Rate": 'Valid Frames Rx. Rate',
52 "Tx_Rate_Kbps": 'Tx. Rate (Kbps)',
53 "Rx_Rate_Kbps": 'Rx. Rate (Kbps)',
54 "Tx_Rate_Mbps": 'Tx. Rate (Mbps)',
55 "Rx_Rate_Mbps": 'Rx. Rate (Mbps)',
59 "Store-Forward_Avg_latency_ns": 'Store-Forward Avg Latency (ns)',
60 "Store-Forward_Min_latency_ns": 'Store-Forward Min Latency (ns)',
61 "Store-Forward_Max_latency_ns": 'Store-Forward Max Latency (ns)',
65 def get_config(tg_cfg):
68 external_interface = tg_cfg["vdu"][0]["external-interface"]
69 for intf in external_interface:
70 card_port0 = intf["virtual-interface"]["vpci"]
71 card0, port0 = card_port0.split(':')[:2]
76 'machine': tg_cfg["mgmt-interface"]["ip"],
77 'port': tg_cfg["mgmt-interface"]["tg-config"]["tcl_port"],
78 'chassis': tg_cfg["mgmt-interface"]["tg-config"]["ixchassis"],
81 'output_dir': tg_cfg["mgmt-interface"]["tg-config"]["dut_result_dir"],
82 'version': tg_cfg["mgmt-interface"]["tg-config"]["version"],
88 def __init__(self): # pragma: no cover
95 def ixnet(self): # pragma: no cover
98 raise exceptions.IxNetworkClientNotConnected()
100 def _get_config_element_by_flow_group_name(self, flow_group_name):
101 """Get a config element using the flow group name
103 Each named flow group contains one config element (by configuration).
104 According to the documentation, "configElements" is a list and "each
105 item in this list is aligned to the sequential order of your endpoint
108 :param flow_group_name: (str) flow group name; this parameter is
109 always a number (converted to string) starting
111 :return: (str) config element reference ID or None.
113 traffic_item = self.ixnet.getList(self.ixnet.getRoot() + '/traffic',
115 flow_groups = self.ixnet.getList(traffic_item, 'endpointSet')
116 for flow_group in flow_groups:
117 if (str(self.ixnet.getAttribute(flow_group, '-name')) ==
119 return traffic_item + '/configElement:' + flow_group_name
121 def _get_stack_item(self, flow_group_name, protocol_name):
122 """Return the stack item given the flow group name and the proto name
124 :param flow_group_name: (str) flow group name
125 :param protocol_name: (str) protocol name, referred to PROTO_*
127 :return: list of stack item descriptors
129 celement = self._get_config_element_by_flow_group_name(flow_group_name)
131 raise exceptions.IxNetworkFlowNotPresent(
132 flow_group=flow_group_name)
133 stack_items = self.ixnet.getList(celement, 'stack')
134 return [s_i for s_i in stack_items if protocol_name in s_i]
136 def _get_field_in_stack_item(self, stack_item, field_name):
137 """Return the field in a stack item given the name
139 :param stack_item: (str) stack item descriptor
140 :param field_name: (str) field name
141 :return: (str) field descriptor
143 fields = self.ixnet.getList(stack_item, 'field')
144 for field in (field for field in fields if field_name in field):
146 raise exceptions.IxNetworkFieldNotPresentInStackItem(
147 field_name=field_name, stack_item=stack_item)
149 def _get_traffic_state(self):
150 """Get traffic state"""
151 return self.ixnet.getAttribute(self.ixnet.getRoot() + 'traffic',
154 def is_traffic_running(self):
155 """Returns true if traffic state == TRAFFIC_STATUS_STARTED"""
156 return self._get_traffic_state() == TRAFFIC_STATUS_STARTED
158 def is_traffic_stopped(self):
159 """Returns true if traffic state == TRAFFIC_STATUS_STOPPED"""
160 return self._get_traffic_state() == TRAFFIC_STATUS_STOPPED
163 def _parse_framesize(framesize):
164 """Parse "framesize" config param. to return a list of weighted pairs
166 :param framesize: dictionary of frame sizes and weights
167 :return: list of paired frame sizes and weights
169 weighted_range_pairs = []
170 for size, weight in ((s, w) for (s, w) in framesize.items()
172 size = int(size.upper().replace('B', ''))
173 weighted_range_pairs.append([size, size, int(weight)])
174 return weighted_range_pairs
176 def iter_over_get_lists(self, x1, x2, y2, offset=0):
177 for x in self.ixnet.getList(x1, x2):
178 y_list = self.ixnet.getList(x, y2)
179 for i, y in enumerate(y_list, offset):
182 def connect(self, tg_cfg):
183 self._cfg = self.get_config(tg_cfg)
184 self._ixnet = IxNetwork.IxNet()
186 machine = self._cfg['machine']
187 port = str(self._cfg['port'])
188 version = str(self._cfg['version'])
189 return self.ixnet.connect(machine, '-port', port,
192 def clear_config(self):
193 """Wipe out any possible configuration present in the client"""
194 self.ixnet.execute('newConfig')
196 def assign_ports(self):
197 """Create and assign vports for each physical port defined in config
199 This configuration is present in the IXIA profile file. E.g.:
204 vpci: "2:15" # Card:port
207 local_ip: "152.16.100.20"
208 netmask: "255.255.0.0"
209 local_mac: "00:98:10:64:14:00"
213 chassis_ip = self._cfg['chassis']
214 ports = [(chassis_ip, card, port) for card, port in
215 zip(self._cfg['cards'], self._cfg['ports'])]
217 log.info('Create and assign vports: %s', ports)
219 vport = self.ixnet.add(self.ixnet.getRoot(), 'vport')
221 self.ixnet.execute('assignPorts', [port], [], [vport], True)
223 if self.ixnet.getAttribute(vport, '-state') != 'up':
224 log.warning('Port %s is down', vport)
226 def _create_traffic_item(self):
227 """Create the traffic item to hold the flow groups
229 The traffic item tracking by "Traffic Item" is enabled to retrieve the
232 log.info('Create the traffic item "RFC2544"')
233 traffic_item = self.ixnet.add(self.ixnet.getRoot() + '/traffic',
235 self.ixnet.setMultiAttribute(traffic_item, '-name', 'RFC2544',
236 '-trafficType', 'raw')
239 traffic_item_id = self.ixnet.remapIds(traffic_item)[0]
240 self.ixnet.setAttribute(traffic_item_id + '/tracking',
241 '-trackBy', 'trafficGroupId0')
244 def _create_flow_groups(self):
245 """Create the flow groups between the assigned ports"""
246 traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
248 log.info('Create the flow groups')
249 vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
250 uplink_ports = vports[::2]
251 downlink_ports = vports[1::2]
253 for up, down in zip(uplink_ports, downlink_ports):
254 log.info('FGs: %s <--> %s', up, down)
255 endpoint_set_1 = self.ixnet.add(traffic_item_id, 'endpointSet')
256 endpoint_set_2 = self.ixnet.add(traffic_item_id, 'endpointSet')
257 self.ixnet.setMultiAttribute(
258 endpoint_set_1, '-name', str(index + 1),
259 '-sources', [up + '/protocols'],
260 '-destinations', [down + '/protocols'])
261 self.ixnet.setMultiAttribute(
262 endpoint_set_2, '-name', str(index + 2),
263 '-sources', [down + '/protocols'],
264 '-destinations', [up + '/protocols'])
268 def _append_procotol_to_stack(self, protocol_name, previous_element):
269 """Append a new element in the packet definition stack"""
270 protocol = (self.ixnet.getRoot() +
271 '/traffic/protocolTemplate:"{}"'.format(protocol_name))
272 self.ixnet.execute('append', previous_element, protocol)
274 def _setup_config_elements(self):
275 """Setup the config elements
277 The traffic item is configured to allow individual configurations per
278 config element. The default frame configuration is applied:
279 Ethernet II: added by default
282 Payload: added by default
283 Ethernet II (Trailer): added by default
286 traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
288 log.info('Split the frame rate distribution per config element')
289 config_elements = self.ixnet.getList(traffic_item_id, 'configElement')
290 for config_element in config_elements:
291 self.ixnet.setAttribute(config_element + '/frameRateDistribution',
292 '-portDistribution', 'splitRateEvenly')
293 self.ixnet.setAttribute(config_element + '/frameRateDistribution',
294 '-streamDistribution', 'splitRateEvenly')
296 self._append_procotol_to_stack(
297 PROTO_UDP, config_element + '/stack:"ethernet-1"')
298 self._append_procotol_to_stack(
299 PROTO_IPV4, config_element + '/stack:"ethernet-1"')
301 def create_traffic_model(self):
302 """Create a traffic item and the needed flow groups
304 Each flow group inside the traffic item (only one is present)
305 represents the traffic between two ports:
307 FlowGroup1: port1 -> port2
308 FlowGroup2: port1 <- port2
309 FlowGroup3: port3 -> port4
310 FlowGroup4: port3 <- port4
312 self._create_traffic_item()
313 self._create_flow_groups()
314 self._setup_config_elements()
316 def _update_frame_mac(self, ethernet_descriptor, field, mac_address):
317 """Set the MAC address in a config element stack Ethernet field
319 :param ethernet_descriptor: (str) ethernet descriptor, e.g.:
320 /traffic/trafficItem:1/configElement:1/stack:"ethernet-1"
321 :param field: (str) field name, e.g.: destinationAddress
322 :param mac_address: (str) MAC address
324 field_descriptor = self._get_field_in_stack_item(ethernet_descriptor,
326 self.ixnet.setMultiAttribute(field_descriptor,
327 '-singleValue', mac_address,
328 '-fieldValue', mac_address,
329 '-valueType', 'singleValue')
332 def update_frame(self, traffic):
333 """Update the L2 frame
335 This function updates the L2 frame options:
336 - Traffic type: "continuous", "fixedDuration".
337 - Duration: in case of traffic_type="fixedDuration", amount of seconds
339 - Rate: in frames per seconds or percentage.
340 - Type of rate: "framesPerSecond" or "percentLineRate" ("bitsPerSecond"
342 - Frame size: custom IMIX [1] definition; a list of packet size in
343 bytes and the weight. E.g.:
344 [[64, 64, 10], [128, 128, 15], [512, 512, 5]]
346 [1] https://en.wikipedia.org/wiki/Internet_Mix
348 :param traffic: list of traffic elements; each traffic element contains
349 the injection parameter for each flow group.
351 for traffic_param in traffic.values():
352 fg_id = str(traffic_param['id'])
353 config_element = self._get_config_element_by_flow_group_name(fg_id)
354 if not config_element:
355 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
357 type = traffic_param.get('traffic_type', 'fixedDuration')
358 duration = traffic_param.get('duration', 30)
359 rate = traffic_param['rate']
361 'framesPerSecond' if traffic_param['rate_unit'] ==
362 tp_base.TrafficProfileConfig.RATE_FPS else 'percentLineRate')
363 weighted_range_pairs = self._parse_framesize(
364 traffic_param['outer_l2']['framesize'])
365 srcmac = str(traffic_param.get('srcmac', '00:00:00:00:00:01'))
366 dstmac = str(traffic_param.get('dstmac', '00:00:00:00:00:02'))
367 # NOTE(ralonsoh): add QinQ tagging when
368 # traffic_param['outer_l2']['QinQ'] exists.
369 # s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
370 # c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
372 self.ixnet.setMultiAttribute(
373 config_element + '/transmissionControl',
374 '-type', type, '-duration', duration)
375 self.ixnet.setMultiAttribute(
376 config_element + '/frameRate',
377 '-rate', rate, '-type', rate_unit)
378 self.ixnet.setMultiAttribute(
379 config_element + '/frameSize',
380 '-type', 'weightedPairs',
381 '-weightedRangePairs', weighted_range_pairs)
384 self._update_frame_mac(
385 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
386 'destinationAddress', dstmac)
387 self._update_frame_mac(
388 self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
389 'sourceAddress', srcmac)
391 def _update_ipv4_address(self, ip_descriptor, field, ip_address, seed,
393 """Set the IPv4 address in a config element stack IP field
395 :param ip_descriptor: (str) IP descriptor, e.g.:
396 /traffic/trafficItem:1/configElement:1/stack:"ipv4-2"
397 :param field: (str) field name, e.g.: scrIp, dstIp
398 :param ip_address: (str) IP address
399 :param seed: (int) seed length
400 :param mask: (str) IP address mask
401 :param count: (int) number of random IPs to generate
403 field_descriptor = self._get_field_in_stack_item(ip_descriptor,
405 self.ixnet.setMultiAttribute(field_descriptor,
407 '-fixedBits', ip_address,
409 '-valueType', 'random',
410 '-countValue', count)
413 def update_ip_packet(self, traffic):
414 """Update the IP packet
416 NOTE: Only IPv4 is currently supported.
417 :param traffic: list of traffic elements; each traffic element contains
418 the injection parameter for each flow group.
420 # NOTE(ralonsoh): L4 configuration is not set.
421 for traffic_param in traffic.values():
422 fg_id = str(traffic_param['id'])
423 if not self._get_config_element_by_flow_group_name(fg_id):
424 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
426 count = traffic_param['outer_l3']['count']
427 srcip4 = str(traffic_param['outer_l3']['srcip4'])
428 dstip4 = str(traffic_param['outer_l3']['dstip4'])
430 self._update_ipv4_address(
431 self._get_stack_item(fg_id, PROTO_IPV4)[0],
432 'srcIp', srcip4, 1, IP_VERSION_4_MASK, count)
433 self._update_ipv4_address(
434 self._get_stack_item(fg_id, PROTO_IPV4)[0],
435 'dstIp', dstip4, 1, IP_VERSION_4_MASK, count)
437 def _build_stats_map(self, view_obj, name_map):
438 return {data_yardstick: self.ixnet.execute(
439 'getColumnValues', view_obj, data_ixia)
440 for data_yardstick, data_ixia in name_map.items()}
442 def get_statistics(self):
443 """Retrieve port and flow statistics
445 "Port Statistics" parameters are stored in self.PORT_STATS_NAME_MAP.
446 "Flow Statistics" parameters are stored in self.LATENCY_NAME_MAP.
448 :return: dictionary with the statistics; the keys of this dictionary
449 are PORT_STATS_NAME_MAP and LATENCY_NAME_MAP keys.
451 port_statistics = '::ixNet::OBJ-/statistics/view:"Port Statistics"'
452 flow_statistics = '::ixNet::OBJ-/statistics/view:"Flow Statistics"'
453 stats = self._build_stats_map(port_statistics,
454 self.PORT_STATS_NAME_MAP)
455 stats.update(self._build_stats_map(flow_statistics,
456 self.LATENCY_NAME_MAP))
459 def start_traffic(self):
460 """Start the traffic injection in the traffic item
462 By configuration, there is only one traffic item. This function returns
463 when the traffic state is TRAFFIC_STATUS_STARTED.
465 traffic_items = self.ixnet.getList('/traffic', 'trafficItem')
466 if self.is_traffic_running():
467 self.ixnet.execute('stop', '/traffic')
468 # pylint: disable=unnecessary-lambda
469 utils.wait_until_true(lambda: self.is_traffic_stopped())
471 self.ixnet.execute('generate', traffic_items)
472 self.ixnet.execute('apply', '/traffic')
473 self.ixnet.execute('start', '/traffic')
474 # pylint: disable=unnecessary-lambda
475 utils.wait_until_true(lambda: self.is_traffic_running())