ab347bcfe272066664a482a4f2e5559a620804bd
[yardstick.git] / yardstick / network_services / traffic_profile / ixia_rfc2544.py
1 # Copyright (c) 2016-2019 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 logging
16 import collections
17
18 from yardstick.common import utils
19 from yardstick.network_services.traffic_profile import base as tp_base
20 from yardstick.network_services.traffic_profile import trex_traffic_profile
21
22
23 LOG = logging.getLogger(__name__)
24
25
26 class IXIARFC2544Profile(trex_traffic_profile.TrexProfile):
27
28     UPLINK = 'uplink'
29     DOWNLINK = 'downlink'
30     DROP_PERCENT_ROUND = 6
31     RATE_ROUND = 5
32     STATUS_SUCCESS = "Success"
33     STATUS_FAIL = "Failure"
34
35     def __init__(self, yaml_data):
36         super(IXIARFC2544Profile, self).__init__(yaml_data)
37         self.rate = self.config.frame_rate
38         self.rate_unit = self.config.rate_unit
39         self.full_profile = {}
40
41     def _get_ip_and_mask(self, ip_range):
42         _ip_range = ip_range.split('-')
43         if len(_ip_range) == 1:
44             return _ip_range[0], None
45
46         mask = utils.get_mask_from_ip_range(_ip_range[0], _ip_range[1])
47         return _ip_range[0], mask
48
49     def _get_fixed_and_mask(self, port_range):
50         _port_range = str(port_range).split('-')
51         if len(_port_range) == 1:
52             return int(_port_range[0]), 0
53
54         return int(_port_range[0]), int(_port_range[1])
55
56     def _get_ixia_traffic_profile(self, profile_data, mac=None):
57         mac = {} if mac is None else mac
58         result = {}
59         for traffickey, values in profile_data.items():
60             if not traffickey.startswith((self.UPLINK, self.DOWNLINK)):
61                 continue
62
63             # values should be single-item dict, so just grab the first item
64             try:
65                 key, value = next(iter(values.items()))
66             except StopIteration:
67                 result[traffickey] = {}
68                 continue
69
70             port_id = value.get('id', 1)
71             port_index = port_id - 1
72
73             result[traffickey] = {
74                 'bidir': False,
75                 'id': port_id,
76                 'rate': self.rate,
77                 'rate_unit': self.rate_unit,
78                 'outer_l2': {},
79                 'outer_l3': {},
80                 'outer_l4': {},
81             }
82
83             frame_rate = value.get('frame_rate')
84             if frame_rate:
85                 flow_rate, flow_rate_unit = self.config.parse_rate(frame_rate)
86                 result[traffickey]['rate'] = flow_rate
87                 result[traffickey]['rate_unit'] = flow_rate_unit
88
89             outer_l2 = value.get('outer_l2')
90             if outer_l2:
91                 result[traffickey]['outer_l2'].update({
92                     'framesize': outer_l2.get('framesize'),
93                     'framesPerSecond': True,
94                     'QinQ': outer_l2.get('QinQ'),
95                     'srcmac': mac.get('src_mac_{}'.format(port_index)),
96                     'dstmac': mac.get('dst_mac_{}'.format(port_index)),
97                 })
98
99             if value.get('outer_l3v4'):
100                 outer_l3 = value['outer_l3v4']
101                 src_key, dst_key = 'srcip4', 'dstip4'
102             else:
103                 outer_l3 = value.get('outer_l3v6')
104                 src_key, dst_key = 'srcip6', 'dstip6'
105             if outer_l3:
106                 srcip = srcmask = dstip = dstmask = None
107                 if outer_l3.get(src_key):
108                     srcip, srcmask = self._get_ip_and_mask(outer_l3[src_key])
109                 if outer_l3.get(dst_key):
110                     dstip, dstmask = self._get_ip_and_mask(outer_l3[dst_key])
111
112                 result[traffickey]['outer_l3'].update({
113                     'count': outer_l3.get('count', 1),
114                     'dscp': outer_l3.get('dscp'),
115                     'ttl': outer_l3.get('ttl'),
116                     'srcseed': outer_l3.get('srcseed', 1),
117                     'dstseed': outer_l3.get('dstseed', 1),
118                     'srcip': srcip,
119                     'dstip': dstip,
120                     'srcmask': srcmask,
121                     'dstmask': dstmask,
122                     'type': key,
123                     'proto': outer_l3.get('proto'),
124                     'priority': outer_l3.get('priority')
125                 })
126
127             outer_l4 = value.get('outer_l4')
128             if outer_l4:
129                 src_port = src_port_mask = dst_port = dst_port_mask = None
130                 if outer_l4.get('srcport'):
131                     src_port, src_port_mask = (
132                         self._get_fixed_and_mask(outer_l4['srcport']))
133
134                 if outer_l4.get('dstport'):
135                     dst_port, dst_port_mask = (
136                         self._get_fixed_and_mask(outer_l4['dstport']))
137
138                 result[traffickey]['outer_l4'].update({
139                     'srcport': src_port,
140                     'dstport': dst_port,
141                     'srcportmask': src_port_mask,
142                     'dstportmask': dst_port_mask,
143                     'count': outer_l4.get('count', 1),
144                     'seed': outer_l4.get('seed', 1),
145                 })
146
147         return result
148
149     def _ixia_traffic_generate(self, traffic, ixia_obj, traffic_gen):
150         ixia_obj.update_frame(traffic, self.config.duration)
151         ixia_obj.update_ip_packet(traffic)
152         ixia_obj.update_l4(traffic)
153         self._update_traffic_tracking_options(traffic_gen)
154         ixia_obj.start_traffic()
155
156     def _update_traffic_tracking_options(self, traffic_gen):
157         traffic_gen.update_tracking_options()
158
159     def update_traffic_profile(self, traffic_generator):
160         def port_generator():
161             for vld_id, intfs in sorted(traffic_generator.networks.items()):
162                 if not vld_id.startswith((self.UPLINK, self.DOWNLINK)):
163                     continue
164                 profile_data = self.params.get(vld_id)
165                 if not profile_data:
166                     continue
167                 self.profile_data = profile_data
168                 self.full_profile.update({vld_id: self.profile_data})
169                 for intf in intfs:
170                     yield traffic_generator.vnfd_helper.port_num(intf)
171
172         self.ports = [port for port in port_generator()]
173
174     def _get_next_rate(self):
175         rate = round(float(self.max_rate + self.min_rate)/2.0, self.RATE_ROUND)
176         return rate
177
178     def _get_framesize(self):
179         framesizes = []
180         traffic = self._get_ixia_traffic_profile(self.full_profile)
181         for v in traffic.values():
182             framesize = v['outer_l2']['framesize']
183             for size in (s for s, w in framesize.items() if int(w) != 0):
184                 framesizes.append(size)
185         if len(set(framesizes)) == 0:
186             return ''
187         elif len(set(framesizes)) == 1:
188             return framesizes[0]
189         return 'IMIX'
190
191     def execute_traffic(self, traffic_generator, ixia_obj=None, mac=None):
192         mac = {} if mac is None else mac
193         first_run = self.first_run
194         if self.first_run:
195             self.first_run = False
196             self.pg_id = 0
197             self.max_rate = self.rate
198             self.min_rate = 0.0
199         else:
200             self.rate = self._get_next_rate()
201
202         traffic = self._get_ixia_traffic_profile(self.full_profile, mac)
203         self._ixia_traffic_generate(traffic, ixia_obj, traffic_generator)
204         return first_run
205
206     # pylint: disable=unused-argument
207     def get_drop_percentage(self, samples, tol_min, tolerance, precision,
208                             resolution, first_run=False, tc_rfc2544_opts=None):
209         completed = False
210         drop_percent = 100.0
211         num_ifaces = len(samples)
212         duration = self.config.duration
213         in_packets_sum = sum(
214             [samples[iface]['in_packets'] for iface in samples])
215         out_packets_sum = sum(
216             [samples[iface]['out_packets'] for iface in samples])
217         rx_throughput = round(float(in_packets_sum) / duration, 3)
218         tx_throughput = round(float(out_packets_sum) / duration, 3)
219         packet_drop = abs(out_packets_sum - in_packets_sum)
220
221         try:
222             drop_percent = round(
223                 (packet_drop / float(out_packets_sum)) * 100,
224                 self.DROP_PERCENT_ROUND)
225         except ZeroDivisionError:
226             LOG.info('No traffic is flowing')
227
228         if first_run:
229             completed = True if drop_percent <= tolerance else False
230         if (first_run and
231                 self.rate_unit == tp_base.TrafficProfileConfig.RATE_FPS):
232             self.rate = float(out_packets_sum) / duration / num_ifaces
233
234         if drop_percent > tolerance:
235             self.max_rate = self.rate
236         elif drop_percent < tol_min:
237             self.min_rate = self.rate
238         else:
239             completed = True
240
241         last_rate = self.rate
242         next_rate = self._get_next_rate()
243         if abs(next_rate - self.rate) < resolution:
244             LOG.debug("rate=%s, next_rate=%s, resolution=%s", self.rate,
245                       next_rate, resolution)
246             # stop test if the difference between the rate transmission
247             # in two iterations is smaller than the value of the resolution
248             completed = True
249
250         LOG.debug("tolerance=%s, tolerance_precision=%s drop_percent=%s "
251                   "completed=%s", tolerance, precision, drop_percent,
252                   completed)
253
254         latency_ns_avg = float(
255             sum([samples[iface]['Store-Forward_Avg_latency_ns']
256             for iface in samples])) / num_ifaces
257         latency_ns_min = float(
258             sum([samples[iface]['Store-Forward_Min_latency_ns']
259             for iface in samples])) / num_ifaces
260         latency_ns_max = float(
261             sum([samples[iface]['Store-Forward_Max_latency_ns']
262             for iface in samples])) / num_ifaces
263
264         samples['Status'] = self.STATUS_FAIL
265         if round(drop_percent, precision) <= tolerance:
266             samples['Status'] = self.STATUS_SUCCESS
267
268         samples['TxThroughput'] = tx_throughput
269         samples['RxThroughput'] = rx_throughput
270         samples['DropPercentage'] = drop_percent
271         samples['latency_ns_avg'] = latency_ns_avg
272         samples['latency_ns_min'] = latency_ns_min
273         samples['latency_ns_max'] = latency_ns_max
274         samples['Rate'] = last_rate
275         samples['PktSize'] = self._get_framesize()
276
277         return completed, samples
278
279
280 class IXIARFC2544PppoeScenarioProfile(IXIARFC2544Profile):
281     """Class handles BNG PPPoE scenario tests traffic profile"""
282
283     def __init__(self, yaml_data):
284         super(IXIARFC2544PppoeScenarioProfile, self).__init__(yaml_data)
285         self.full_profile = collections.OrderedDict()
286
287     def _get_flow_groups_params(self):
288         flows_data = [key for key in self.params.keys()
289                       if key.split('_')[0] in [self.UPLINK, self.DOWNLINK]]
290         for i in range(len(flows_data)):
291             uplink = '_'.join([self.UPLINK, str(i)])
292             downlink = '_'.join([self.DOWNLINK, str(i)])
293             if uplink in flows_data:
294                 self.full_profile.update({uplink: self.params[uplink]})
295             if downlink in flows_data:
296                 self.full_profile.update({downlink: self.params[downlink]})
297
298     def update_traffic_profile(self, traffic_generator):
299
300         networks = collections.OrderedDict()
301
302         # Sort network interfaces pairs
303         for i in range(len(traffic_generator.networks)):
304             uplink = '_'.join([self.UPLINK, str(i)])
305             downlink = '_'.join([self.DOWNLINK, str(i)])
306             if uplink in traffic_generator.networks:
307                 networks[uplink] = traffic_generator.networks[uplink]
308             if downlink in traffic_generator.networks:
309                 networks[downlink] = traffic_generator.networks[downlink]
310
311         def port_generator():
312             for intfs in networks.values():
313                 for intf in intfs:
314                     yield traffic_generator.vnfd_helper.port_num(intf)
315
316         self._get_flow_groups_params()
317         self.ports = [port for port in port_generator()]
318
319     def _get_prio_flows_drop_percentage(self, stats):
320         drop_percent = 100
321         for prio_id in stats:
322             prio_flow = stats[prio_id]
323             sum_packet_drop = abs(prio_flow['out_packets'] - prio_flow['in_packets'])
324             try:
325                 drop_percent = round(
326                     (sum_packet_drop / float(prio_flow['out_packets'])) * 100,
327                     self.DROP_PERCENT_ROUND)
328             except ZeroDivisionError:
329                 LOG.info('No traffic is flowing')
330             prio_flow['DropPercentage'] = drop_percent
331         return stats
332
333     def _get_summary_pppoe_subs_counters(self, samples):
334         result = {}
335         keys = ['sessions_up',
336                 'sessions_down',
337                 'sessions_not_started',
338                 'sessions_total']
339         for key in keys:
340             result[key] = \
341                 sum([samples[port][key] for port in samples
342                      if key in samples[port]])
343         return result
344
345     def get_drop_percentage(self, samples, tol_min, tolerance, precision,
346                             resolution, first_run=False, tc_rfc2544_opts=None):
347         completed = False
348         sum_drop_percent = 100
349         num_ifaces = len(samples)
350         duration = self.config.duration
351         last_rate = self.rate
352         priority_stats = samples.pop('priority_stats')
353         priority_stats = self._get_prio_flows_drop_percentage(priority_stats)
354         summary_subs_stats = self._get_summary_pppoe_subs_counters(samples)
355         in_packets_sum = sum(
356             [samples[iface]['in_packets'] for iface in samples])
357         out_packets_sum = sum(
358             [samples[iface]['out_packets'] for iface in samples])
359         rx_throughput = round(float(in_packets_sum) / duration, 3)
360         tx_throughput = round(float(out_packets_sum) / duration, 3)
361         sum_packet_drop = abs(out_packets_sum - in_packets_sum)
362
363         try:
364             sum_drop_percent = round(
365                 (sum_packet_drop / float(out_packets_sum)) * 100,
366                 self.DROP_PERCENT_ROUND)
367         except ZeroDivisionError:
368             LOG.info('No traffic is flowing')
369
370         latency_ns_avg = float(
371             sum([samples[iface]['Store-Forward_Avg_latency_ns']
372                  for iface in samples])) / num_ifaces
373         latency_ns_min = float(
374             sum([samples[iface]['Store-Forward_Min_latency_ns']
375                  for iface in samples])) / num_ifaces
376         latency_ns_max = float(
377             sum([samples[iface]['Store-Forward_Max_latency_ns']
378                  for iface in samples])) / num_ifaces
379
380         samples['TxThroughput'] = tx_throughput
381         samples['RxThroughput'] = rx_throughput
382         samples['DropPercentage'] = sum_drop_percent
383         samples['latency_ns_avg'] = latency_ns_avg
384         samples['latency_ns_min'] = latency_ns_min
385         samples['latency_ns_max'] = latency_ns_max
386         samples['priority'] = priority_stats
387         samples['Rate'] = last_rate
388         samples['PktSize'] = self._get_framesize()
389         samples.update(summary_subs_stats)
390
391         if tc_rfc2544_opts:
392             priority = tc_rfc2544_opts.get('priority')
393             if priority:
394                 drop_percent = samples['priority'][priority]['DropPercentage']
395             else:
396                 drop_percent = sum_drop_percent
397         else:
398             drop_percent = sum_drop_percent
399
400         if first_run:
401             completed = True if drop_percent <= tolerance else False
402         if (first_run and
403                 self.rate_unit == tp_base.TrafficProfileConfig.RATE_FPS):
404             self.rate = float(out_packets_sum) / duration / num_ifaces
405
406         if drop_percent > tolerance:
407             self.max_rate = self.rate
408         elif drop_percent < tol_min:
409             self.min_rate = self.rate
410         else:
411             completed = True
412
413         next_rate = self._get_next_rate()
414         if abs(next_rate - self.rate) < resolution:
415             LOG.debug("rate=%s, next_rate=%s, resolution=%s", self.rate,
416                       next_rate, resolution)
417             # stop test if the difference between the rate transmission
418             # in two iterations is smaller than the value of the resolution
419             completed = True
420
421         LOG.debug("tolerance=%s, tolerance_precision=%s drop_percent=%s "
422                   "completed=%s", tolerance, precision, drop_percent,
423                   completed)
424
425         samples['Status'] = self.STATUS_FAIL
426         if round(drop_percent, precision) <= tolerance:
427             samples['Status'] = self.STATUS_SUCCESS
428
429         return completed, samples