Merge "Adding latency test for vfw"
[yardstick.git] / yardstick / network_services / traffic_profile / traffic_profile.py
1 # Copyright (c) 2016-2017 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 """ Trex Traffic Profile definitions """
15
16 from __future__ import absolute_import
17 import struct
18 import socket
19 import logging
20 from random import SystemRandom
21 import six
22
23 from yardstick.network_services.traffic_profile.base import TrafficProfile
24 from trex_stl_lib.trex_stl_client import STLStream
25 from trex_stl_lib.trex_stl_streams import STLFlowLatencyStats
26 from trex_stl_lib.trex_stl_streams import STLTXCont
27 from trex_stl_lib.trex_stl_streams import STLProfile
28 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmWrFlowVar
29 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVarRepeatableRandom
30 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVar
31 from trex_stl_lib.trex_stl_packet_builder_scapy import STLPktBuilder
32 from trex_stl_lib.trex_stl_packet_builder_scapy import STLScVmRaw
33 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFixIpv4
34 from trex_stl_lib import api as Pkt
35
36 SRC = 'src'
37 DST = 'dst'
38 ETHERNET = 'Ethernet'
39 IP = 'IP'
40 IPv6 = 'IPv6'
41 UDP = 'UDP'
42 DSCP = 'DSCP'
43 SRC_PORT = 'sport'
44 DST_PORT = 'dport'
45 TYPE_OF_SERVICE = 'tos'
46
47
48 class TrexProfile(TrafficProfile):
49     """ This class handles Trex Traffic profile generation and execution """
50
51     PROTO_MAP = {
52         ETHERNET: ('ether_packet', Pkt.Ether),
53         IP: ('ip_packet', Pkt.IP),
54         IPv6: ('ip6_packet', Pkt.IPv6),
55         UDP: ('udp_packet', Pkt.UDP),
56     }
57
58     def _general_single_action_partial(self, protocol):
59         def f(field):
60             def partial(value):
61                 kwargs = {
62                     field: value
63                 }
64                 self._set_proto_fields(protocol, **kwargs)
65             return partial
66         return f
67
68     def _ethernet_range_action_partial(self, direction, _):
69         def partial(min_value, max_value):
70             stl_vm_flow_var = STLVmFlowVar(name="mac_{}".format(direction),
71                                            min_value=1,
72                                            max_value=30,
73                                            size=4,
74                                            op='inc',
75                                            step=1)
76             self.vm_flow_vars.append(stl_vm_flow_var)
77             stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='mac_{}'.format(direction),
78                                                 pkt_offset='Ether.{}'.format(direction))
79             self.vm_flow_vars.append(stl_vm_wr_flow_var)
80         return partial
81
82     def _ip_range_action_partial(self, direction, count=1):
83         def partial(min_value, max_value):
84             stl_vm_flow_var = STLVmFlowVarRepeatableRandom(name="ip4_{}".format(direction),
85                                                            min_value=min_value,
86                                                            max_value=max_value,
87                                                            size=4,
88                                                            limit=int(count),
89                                                            seed=0x1235)
90             self.vm_flow_vars.append(stl_vm_flow_var)
91             stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='ip4_{}'.format(direction),
92                                                 pkt_offset='IP.{}'.format(direction))
93             self.vm_flow_vars.append(stl_vm_wr_flow_var)
94             stl_vm_fix_ipv4 = STLVmFixIpv4(offset="IP")
95             self.vm_flow_vars.append(stl_vm_fix_ipv4)
96         return partial
97
98     def _ip6_range_action_partial(self, direction, _):
99         def partial(min_value, max_value):
100             min_value, max_value = self._get_start_end_ipv6(min_value, max_value)
101             stl_vm_flow_var = STLVmFlowVar(name="ip6_{}".format(direction),
102                                            min_value=min_value,
103                                            max_value=max_value,
104                                            size=8,
105                                            op='random',
106                                            step=1)
107             self.vm_flow_vars.append(stl_vm_flow_var)
108             stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='ip6_{}'.format(direction),
109                                                 pkt_offset='IPv6.{}'.format(direction),
110                                                 offset_fixup=8)
111             self.vm_flow_vars.append(stl_vm_wr_flow_var)
112         return partial
113
114     def _dscp_range_action_partial(self, *_):
115         def partial(min_value, max_value):
116             stl_vm_flow_var = STLVmFlowVar(name="dscp",
117                                            min_value=min_value,
118                                            max_value=max_value,
119                                            size=2,
120                                            op='inc',
121                                            step=8)
122             self.vm_flow_vars.append(stl_vm_flow_var)
123             stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='dscp',
124                                                 pkt_offset='IP.tos')
125             self.vm_flow_vars.append(stl_vm_wr_flow_var)
126
127     def _udp_range_action_partial(self, field, count=1):
128         def partial(min_value, max_value):
129             stl_vm_flow_var = STLVmFlowVarRepeatableRandom(name="port_{}".format(field),
130                                                            min_value=min_value,
131                                                            max_value=max_value,
132                                                            size=2,
133                                                            limit=int(count),
134                                                            seed=0x1235)
135             self.vm_flow_vars.append(stl_vm_flow_var)
136             stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='port_{}'.format(field),
137                                                 pkt_offset=self.udp_sport)
138             self.vm_flow_vars.append(stl_vm_wr_flow_var)
139         return partial
140
141     def __init__(self, yaml_data):
142         super(TrexProfile, self).__init__(yaml_data)
143         self.flows = 100
144         self.pps = 100
145         self.pg_id = 0
146         self.first_run = True
147         self.streams = 1
148         self.profile_data = []
149         self.profile = None
150         self.base_pkt = None
151         self.fsize = None
152         self.trex_vm = None
153         self.vms = []
154         self.rate = None
155         self.ether_packet = None
156         self.ip_packet = None
157         self.ip6_packet = None
158         self.udp_packet = None
159         self.udp_dport = ''
160         self.udp_sport = ''
161         self.qinq_packet = None
162         self.qinq = False
163         self.vm_flow_vars = []
164         self.packets = []
165
166         self._map_proto_actions = {
167             # the tuple is (single value function, range value function, if the values should be
168             # converted to integer).
169             ETHERNET: (self._general_single_action_partial(ETHERNET),
170                        self._ethernet_range_action_partial,
171                        False,
172                        ),
173             IP: (self._general_single_action_partial(IP),
174                  self._ip_range_action_partial,
175                  False,
176                  ),
177             IPv6: (self._general_single_action_partial(IPv6),
178                    self._ip6_range_action_partial,
179                    False,
180                    ),
181             DSCP: (self._general_single_action_partial(IP),
182                    self._dscp_range_action_partial,
183                    True,
184                    ),
185             UDP: (self._general_single_action_partial(UDP),
186                   self._udp_range_action_partial,
187                   True,
188                   ),
189         }
190
191     def execute(self, traffic_generator):
192         """ Generate the stream and run traffic on the given ports """
193         pass
194
195     def _call_on_range(self, range, single_action, range_action, count=1, to_int=False):
196         def convert_to_int(val):
197             return int(val) if to_int else val
198
199         range_iter = iter(str(range).split('-'))
200         min_value = convert_to_int(next(range_iter))
201         try:
202             max_value = convert_to_int(next(range_iter))
203         except StopIteration:
204             single_action(min_value)
205         else:
206             range_action(min_value=min_value, max_value=max_value)
207
208     def _set_proto_addr(self, protocol, field, address, count=1):
209         single_action, range_action, to_int = self._map_proto_actions[protocol]
210         self._call_on_range(address,
211                             single_action(field),
212                             range_action(field, count),
213                             to_int=to_int,
214                             )
215
216     def _set_proto_fields(self, protocol, **kwargs):
217         _attr_name, _class = self.PROTO_MAP[protocol]
218
219         if not getattr(self, _attr_name):
220             setattr(self, _attr_name, _class())
221
222         _attr = getattr(self, _attr_name)
223         for key, value in six.iteritems(kwargs):
224             setattr(_attr, key, value)
225
226     def set_svlan_cvlan(self, svlan, cvlan):
227         """ set svlan & cvlan """
228         self.qinq = True
229         ether_params = {'type': 0x8100}
230         self._set_proto_fields(ETHERNET, **ether_params)
231         svlans = str(svlan['id']).split('-')
232         svlan_min = int(svlans[0])
233         svlan_max = int(svlans[1]) if len(svlans) == 2 else int(svlans[0])
234         if len(svlans) == 2:
235             svlan = self._get_random_value(svlan_min, svlan_max)
236         else:
237             svlan = svlan_min
238         cvlans = str(cvlan['id']).split('-')
239         cvlan_min = int(cvlans[0])
240         cvlan_max = int(cvlans[1]) if len(cvlans) == 2 else int(cvlans[0])
241         if len(cvlans) == 2:
242             cvlan = self._get_random_value(cvlan_min, cvlan_max)
243         else:
244             cvlan = cvlan_min
245         self.qinq_packet = Pkt.Dot1Q(vlan=svlan) / Pkt.Dot1Q(vlan=cvlan)
246
247     def set_qinq(self, qinq):
248         """ set qinq in packet """
249         self.set_svlan_cvlan(qinq['S-VLAN'], qinq['C-VLAN'])
250
251     def _set_outer_l2_fields(self, outer_l2):
252         """ setup outer l2 fields from traffic profile """
253         ether_params = {'type': 0x800}
254         self._set_proto_fields(ETHERNET, **ether_params)
255         if 'srcmac' in outer_l2:
256             self._set_proto_addr(ETHERNET, SRC, outer_l2['srcmac'])
257         if 'dstmac' in outer_l2:
258             self._set_proto_addr(ETHERNET, DST, outer_l2['dstmac'])
259         if 'QinQ' in outer_l2:
260             self.set_qinq(outer_l2['QinQ'])
261
262     def _set_outer_l3v4_fields(self, outer_l3v4):
263         """ setup outer l3v4 fields from traffic profile """
264         ip_params = {}
265         if 'proto' in outer_l3v4:
266             ip_params['proto'] = socket.getprotobyname(outer_l3v4['proto'])
267             if outer_l3v4['proto'] == 'tcp':
268                 self.udp_packet = Pkt.TCP()
269                 self.udp_dport = 'TCP.dport'
270                 self.udp_sport = 'TCP.sport'
271                 tcp_params = {'flags': '', 'window': 0}
272                 self._set_proto_fields(UDP, **tcp_params)
273         if 'ttl' in outer_l3v4:
274             ip_params['ttl'] = outer_l3v4['ttl']
275         self._set_proto_fields(IP, **ip_params)
276         if 'dscp' in outer_l3v4:
277             self._set_proto_addr(DSCP, TYPE_OF_SERVICE, outer_l3v4['dscp'])
278         if 'srcip4' in outer_l3v4:
279             self._set_proto_addr(IP, SRC, outer_l3v4['srcip4'], outer_l3v4['count'])
280         if 'dstip4' in outer_l3v4:
281             self._set_proto_addr(IP, DST, outer_l3v4['dstip4'], outer_l3v4['count'])
282
283     def _set_outer_l3v6_fields(self, outer_l3v6):
284         """ setup outer l3v6 fields from traffic profile """
285         ether_params = {'type': 0x86dd}
286         self._set_proto_fields(ETHERNET, **ether_params)
287         ip6_params = {}
288         if 'proto' in outer_l3v6:
289             ip6_params['proto'] = outer_l3v6['proto']
290             if outer_l3v6['proto'] == 'tcp':
291                 self.udp_packet = Pkt.TCP()
292                 self.udp_dport = 'TCP.dport'
293                 self.udp_sport = 'TCP.sport'
294                 tcp_params = {'flags': '', 'window': 0}
295                 self._set_proto_fields(UDP, **tcp_params)
296         if 'ttl' in outer_l3v6:
297             ip6_params['ttl'] = outer_l3v6['ttl']
298         if 'tc' in outer_l3v6:
299             ip6_params['tc'] = outer_l3v6['tc']
300         if 'hlim' in outer_l3v6:
301             ip6_params['hlim'] = outer_l3v6['hlim']
302         self._set_proto_fields(IPv6, **ip6_params)
303         if 'srcip6' in outer_l3v6:
304             self._set_proto_addr(IPv6, SRC, outer_l3v6['srcip6'])
305         if 'dstip6' in outer_l3v6:
306             self._set_proto_addr(IPv6, DST, outer_l3v6['dstip6'])
307
308     def _set_outer_l4_fields(self, outer_l4):
309         """ setup outer l4 fields from traffic profile """
310         if 'srcport' in outer_l4:
311             self._set_proto_addr(UDP, SRC_PORT, outer_l4['srcport'], outer_l4['count'])
312         if 'dstport' in outer_l4:
313             self._set_proto_addr(UDP, DST_PORT, outer_l4['dstport'], outer_l4['count'])
314
315     def generate_imix_data(self, packet_definition):
316         """ generate packet size for a given traffic profile """
317         imix_count = {}
318         imix_data = {}
319         if not packet_definition:
320             return imix_count
321         imix = packet_definition.get('framesize')
322         if imix:
323             for size in imix:
324                 data = imix[size]
325                 imix_data[int(size[:-1])] = int(data)
326             imix_sum = sum(imix_data.values())
327             if imix_sum > 100:
328                 raise SystemExit("Error in IMIX data")
329             elif imix_sum < 100:
330                 imix_data[64] = imix_data.get(64, 0) + (100 - imix_sum)
331
332             avg_size = 0.0
333             for size in imix_data:
334                 count = int(imix_data[size])
335                 if count:
336                     avg_size += round(size * count / 100, 2)
337                     pps = round(self.pps * count / 100, 0)
338                     imix_count[size] = pps
339             self.rate = round(1342177280 / avg_size, 0) * 2
340             logging.debug("Imax: %s rate: %s", imix_count, self.rate)
341         return imix_count
342
343     def get_streams(self, profile_data):
344         """ generate trex stream
345         :param profile_data:
346         :type profile_data:
347         """
348         self.streams = []
349         self.pps = self.params['traffic_profile'].get('frame_rate', 100)
350         for packet_name in profile_data:
351             outer_l2 = profile_data[packet_name].get('outer_l2')
352             imix_data = self.generate_imix_data(outer_l2)
353             if not imix_data:
354                 imix_data = {64: self.pps}
355             self.generate_vm(profile_data[packet_name])
356             for size in imix_data:
357                 self._generate_streams(size, imix_data[size])
358         self._generate_profile()
359         return self.profile
360
361     def generate_vm(self, packet_definition):
362         """ generate  trex vm with flows setup """
363         self.ether_packet = Pkt.Ether()
364         self.ip_packet = Pkt.IP()
365         self.ip6_packet = None
366         self.udp_packet = Pkt.UDP()
367         self.udp_dport = 'UDP.dport'
368         self.udp_sport = 'UDP.sport'
369         self.qinq = False
370         self.vm_flow_vars = []
371         outer_l2 = packet_definition.get('outer_l2', None)
372         outer_l3v4 = packet_definition.get('outer_l3v4', None)
373         outer_l3v6 = packet_definition.get('outer_l3v6', None)
374         outer_l4 = packet_definition.get('outer_l4', None)
375         if outer_l2:
376             self._set_outer_l2_fields(outer_l2)
377         if outer_l3v4:
378             self._set_outer_l3v4_fields(outer_l3v4)
379         if outer_l3v6:
380             self._set_outer_l3v6_fields(outer_l3v6)
381         if outer_l4:
382             self._set_outer_l4_fields(outer_l4)
383         self.trex_vm = STLScVmRaw(self.vm_flow_vars)
384
385     def generate_packets(self):
386         """ generate packets from trex TG """
387         base_pkt = self.base_pkt
388         size = self.fsize - 4
389         pad = max(0, size - len(base_pkt)) * 'x'
390         self.packets = [STLPktBuilder(pkt=base_pkt / pad,
391                                       vm=vm) for vm in self.vms]
392
393     def _create_single_packet(self, size=64):
394         size = size - 4
395         ether_packet = self.ether_packet
396         ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet
397         udp_packet = self.udp_packet
398         if self.qinq:
399             qinq_packet = self.qinq_packet
400             base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet
401         else:
402             base_pkt = ether_packet / ip_packet / udp_packet
403         pad = max(0, size - len(base_pkt)) * 'x'
404         packet = STLPktBuilder(pkt=base_pkt / pad, vm=self.trex_vm)
405         return packet
406
407     def _create_single_stream(self, packet_size, pps, isg=0):
408         packet = self._create_single_packet(packet_size)
409         if self.pg_id:
410             self.pg_id += 1
411             stl_flow = STLFlowLatencyStats(pg_id=self.pg_id)
412             stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps),
413                                flow_stats=stl_flow)
414         else:
415             stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps))
416         return stream
417
418     def _generate_streams(self, packet_size, pps):
419         self.streams.append(self._create_single_stream(packet_size, pps))
420
421     def _generate_profile(self):
422         self.profile = STLProfile(self.streams)
423
424     @classmethod
425     def _get_start_end_ipv6(cls, start_ip, end_ip):
426         try:
427             ip1 = socket.inet_pton(socket.AF_INET6, start_ip)
428             ip2 = socket.inet_pton(socket.AF_INET6, end_ip)
429             hi1, lo1 = struct.unpack('!QQ', ip1)
430             hi2, lo2 = struct.unpack('!QQ', ip2)
431             if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2):
432                 raise SystemExit("IPv6: start_ip is greater then end_ip")
433             max_p1 = abs(int(lo1) - int(lo2))
434             base_p1 = lo1
435         except Exception as ex_error:
436             raise SystemExit(ex_error)
437         else:
438             return base_p1, max_p1 + base_p1
439
440     @classmethod
441     def _get_random_value(cls, min_port, max_port):
442         cryptogen = SystemRandom()
443         return cryptogen.randrange(min_port, max_port)