Merge "requirements: bump Ansible version to 2.3"
[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[field])
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 = {
160             SRC_PORT: '',
161             DST_PORT: '',
162         }
163         self.qinq_packet = None
164         self.qinq = False
165         self.vm_flow_vars = []
166         self.packets = []
167
168         self._map_proto_actions = {
169             # the tuple is (single value function, range value function, if the values should be
170             # converted to integer).
171             ETHERNET: (self._general_single_action_partial(ETHERNET),
172                        self._ethernet_range_action_partial,
173                        False,
174                        ),
175             IP: (self._general_single_action_partial(IP),
176                  self._ip_range_action_partial,
177                  False,
178                  ),
179             IPv6: (self._general_single_action_partial(IPv6),
180                    self._ip6_range_action_partial,
181                    False,
182                    ),
183             DSCP: (self._general_single_action_partial(IP),
184                    self._dscp_range_action_partial,
185                    True,
186                    ),
187             UDP: (self._general_single_action_partial(UDP),
188                   self._udp_range_action_partial,
189                   True,
190                   ),
191         }
192
193     def execute_traffic(self, traffic_generator):
194         """ Generate the stream and run traffic on the given ports """
195         raise NotImplementedError()
196
197     def _call_on_range(self, range, single_action, range_action, count=1, to_int=False):
198         def convert_to_int(val):
199             return int(val) if to_int else val
200
201         range_iter = iter(str(range).split('-'))
202         min_value = convert_to_int(next(range_iter))
203         try:
204             max_value = convert_to_int(next(range_iter))
205         except StopIteration:
206             single_action(min_value)
207         else:
208             range_action(min_value=min_value, max_value=max_value)
209
210     def _set_proto_addr(self, protocol, field, address, count=1):
211         single_action, range_action, to_int = self._map_proto_actions[protocol]
212         self._call_on_range(address,
213                             single_action(field),
214                             range_action(field, count),
215                             to_int=to_int,
216                             )
217
218     def _set_proto_fields(self, protocol, **kwargs):
219         _attr_name, _class = self.PROTO_MAP[protocol]
220
221         if not getattr(self, _attr_name):
222             setattr(self, _attr_name, _class())
223
224         _attr = getattr(self, _attr_name)
225         for key, value in six.iteritems(kwargs):
226             setattr(_attr, key, value)
227
228     def set_svlan_cvlan(self, svlan, cvlan):
229         """ set svlan & cvlan """
230         self.qinq = True
231         ether_params = {'type': 0x8100}
232         self._set_proto_fields(ETHERNET, **ether_params)
233         svlans = str(svlan['id']).split('-')
234         svlan_min = int(svlans[0])
235         svlan_max = int(svlans[1]) if len(svlans) == 2 else int(svlans[0])
236         if len(svlans) == 2:
237             svlan = self._get_random_value(svlan_min, svlan_max)
238         else:
239             svlan = svlan_min
240         cvlans = str(cvlan['id']).split('-')
241         cvlan_min = int(cvlans[0])
242         cvlan_max = int(cvlans[1]) if len(cvlans) == 2 else int(cvlans[0])
243         if len(cvlans) == 2:
244             cvlan = self._get_random_value(cvlan_min, cvlan_max)
245         else:
246             cvlan = cvlan_min
247         self.qinq_packet = Pkt.Dot1Q(vlan=svlan) / Pkt.Dot1Q(vlan=cvlan)
248
249     def set_qinq(self, qinq):
250         """ set qinq in packet """
251         self.set_svlan_cvlan(qinq['S-VLAN'], qinq['C-VLAN'])
252
253     def _set_outer_l2_fields(self, outer_l2):
254         """ setup outer l2 fields from traffic profile """
255         ether_params = {'type': 0x800}
256         self._set_proto_fields(ETHERNET, **ether_params)
257         if 'srcmac' in outer_l2:
258             self._set_proto_addr(ETHERNET, SRC, outer_l2['srcmac'])
259         if 'dstmac' in outer_l2:
260             self._set_proto_addr(ETHERNET, DST, outer_l2['dstmac'])
261         if 'QinQ' in outer_l2:
262             self.set_qinq(outer_l2['QinQ'])
263
264     def _set_outer_l3v4_fields(self, outer_l3v4):
265         """ setup outer l3v4 fields from traffic profile """
266         ip_params = {}
267         if 'proto' in outer_l3v4:
268             ip_params['proto'] = socket.getprotobyname(outer_l3v4['proto'])
269             if outer_l3v4['proto'] == 'tcp':
270                 self.udp_packet = Pkt.TCP()
271                 self.udp[DST_PORT] = 'TCP.dport'
272                 self.udp[SRC_PORT] = 'TCP.sport'
273                 tcp_params = {'flags': '', 'window': 0}
274                 self._set_proto_fields(UDP, **tcp_params)
275         if 'ttl' in outer_l3v4:
276             ip_params['ttl'] = outer_l3v4['ttl']
277         self._set_proto_fields(IP, **ip_params)
278         if 'dscp' in outer_l3v4:
279             self._set_proto_addr(DSCP, TYPE_OF_SERVICE, outer_l3v4['dscp'])
280         if 'srcip4' in outer_l3v4:
281             self._set_proto_addr(IP, SRC, outer_l3v4['srcip4'], outer_l3v4['count'])
282         if 'dstip4' in outer_l3v4:
283             self._set_proto_addr(IP, DST, outer_l3v4['dstip4'], outer_l3v4['count'])
284
285     def _set_outer_l3v6_fields(self, outer_l3v6):
286         """ setup outer l3v6 fields from traffic profile """
287         ether_params = {'type': 0x86dd}
288         self._set_proto_fields(ETHERNET, **ether_params)
289         ip6_params = {}
290         if 'proto' in outer_l3v6:
291             ip6_params['proto'] = outer_l3v6['proto']
292             if outer_l3v6['proto'] == 'tcp':
293                 self.udp_packet = Pkt.TCP()
294                 self.udp[DST_PORT] = 'TCP.dport'
295                 self.udp[SRC_PORT] = 'TCP.sport'
296                 tcp_params = {'flags': '', 'window': 0}
297                 self._set_proto_fields(UDP, **tcp_params)
298         if 'ttl' in outer_l3v6:
299             ip6_params['ttl'] = outer_l3v6['ttl']
300         if 'tc' in outer_l3v6:
301             ip6_params['tc'] = outer_l3v6['tc']
302         if 'hlim' in outer_l3v6:
303             ip6_params['hlim'] = outer_l3v6['hlim']
304         self._set_proto_fields(IPv6, **ip6_params)
305         if 'srcip6' in outer_l3v6:
306             self._set_proto_addr(IPv6, SRC, outer_l3v6['srcip6'])
307         if 'dstip6' in outer_l3v6:
308             self._set_proto_addr(IPv6, DST, outer_l3v6['dstip6'])
309
310     def _set_outer_l4_fields(self, outer_l4):
311         """ setup outer l4 fields from traffic profile """
312         if 'srcport' in outer_l4:
313             self._set_proto_addr(UDP, SRC_PORT, outer_l4['srcport'], outer_l4['count'])
314         if 'dstport' in outer_l4:
315             self._set_proto_addr(UDP, DST_PORT, outer_l4['dstport'], outer_l4['count'])
316
317     def generate_imix_data(self, packet_definition):
318         """ generate packet size for a given traffic profile """
319         imix_count = {}
320         imix_data = {}
321         if not packet_definition:
322             return imix_count
323         imix = packet_definition.get('framesize')
324         if imix:
325             for size in imix:
326                 data = imix[size]
327                 imix_data[int(size[:-1])] = int(data)
328             imix_sum = sum(imix_data.values())
329             if imix_sum > 100:
330                 raise SystemExit("Error in IMIX data")
331             elif imix_sum < 100:
332                 imix_data[64] = imix_data.get(64, 0) + (100 - imix_sum)
333
334             avg_size = 0.0
335             for size in imix_data:
336                 count = int(imix_data[size])
337                 if count:
338                     avg_size += round(size * count / 100, 2)
339                     pps = round(self.pps * count / 100, 0)
340                     imix_count[size] = pps
341             self.rate = round(1342177280 / avg_size, 0) * 2
342             logging.debug("Imax: %s rate: %s", imix_count, self.rate)
343         return imix_count
344
345     def get_streams(self, profile_data):
346         """ generate trex stream
347         :param profile_data:
348         :type profile_data:
349         """
350         self.streams = []
351         self.pps = self.params['traffic_profile'].get('frame_rate', 100)
352         for packet_name in profile_data:
353             outer_l2 = profile_data[packet_name].get('outer_l2')
354             imix_data = self.generate_imix_data(outer_l2)
355             if not imix_data:
356                 imix_data = {64: self.pps}
357             self.generate_vm(profile_data[packet_name])
358             for size in imix_data:
359                 self._generate_streams(size, imix_data[size])
360         self._generate_profile()
361         return self.profile
362
363     def generate_vm(self, packet_definition):
364         """ generate  trex vm with flows setup """
365         self.ether_packet = Pkt.Ether()
366         self.ip_packet = Pkt.IP()
367         self.ip6_packet = None
368         self.udp_packet = Pkt.UDP()
369         self.udp[DST_PORT] = 'UDP.dport'
370         self.udp[SRC_PORT] = 'UDP.sport'
371         self.qinq = False
372         self.vm_flow_vars = []
373         outer_l2 = packet_definition.get('outer_l2', None)
374         outer_l3v4 = packet_definition.get('outer_l3v4', None)
375         outer_l3v6 = packet_definition.get('outer_l3v6', None)
376         outer_l4 = packet_definition.get('outer_l4', None)
377         if outer_l2:
378             self._set_outer_l2_fields(outer_l2)
379         if outer_l3v4:
380             self._set_outer_l3v4_fields(outer_l3v4)
381         if outer_l3v6:
382             self._set_outer_l3v6_fields(outer_l3v6)
383         if outer_l4:
384             self._set_outer_l4_fields(outer_l4)
385         self.trex_vm = STLScVmRaw(self.vm_flow_vars)
386
387     def generate_packets(self):
388         """ generate packets from trex TG """
389         base_pkt = self.base_pkt
390         size = self.fsize - 4
391         pad = max(0, size - len(base_pkt)) * 'x'
392         self.packets = [STLPktBuilder(pkt=base_pkt / pad,
393                                       vm=vm) for vm in self.vms]
394
395     def _create_single_packet(self, size=64):
396         size = size - 4
397         ether_packet = self.ether_packet
398         ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet
399         udp_packet = self.udp_packet
400         if self.qinq:
401             qinq_packet = self.qinq_packet
402             base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet
403         else:
404             base_pkt = ether_packet / ip_packet / udp_packet
405         pad = max(0, size - len(base_pkt)) * 'x'
406         packet = STLPktBuilder(pkt=base_pkt / pad, vm=self.trex_vm)
407         return packet
408
409     def _create_single_stream(self, packet_size, pps, isg=0):
410         packet = self._create_single_packet(packet_size)
411         if self.pg_id:
412             self.pg_id += 1
413             stl_flow = STLFlowLatencyStats(pg_id=self.pg_id)
414             stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps),
415                                flow_stats=stl_flow)
416         else:
417             stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps))
418         return stream
419
420     def _generate_streams(self, packet_size, pps):
421         self.streams.append(self._create_single_stream(packet_size, pps))
422
423     def _generate_profile(self):
424         self.profile = STLProfile(self.streams)
425
426     @classmethod
427     def _get_start_end_ipv6(cls, start_ip, end_ip):
428         try:
429             ip1 = socket.inet_pton(socket.AF_INET6, start_ip)
430             ip2 = socket.inet_pton(socket.AF_INET6, end_ip)
431             hi1, lo1 = struct.unpack('!QQ', ip1)
432             hi2, lo2 = struct.unpack('!QQ', ip2)
433             if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2):
434                 raise SystemExit("IPv6: start_ip is greater then end_ip")
435             max_p1 = abs(int(lo1) - int(lo2))
436             base_p1 = lo1
437         except Exception as ex_error:
438             raise SystemExit(ex_error)
439         else:
440             return base_p1, max_p1 + base_p1
441
442     @classmethod
443     def _get_random_value(cls, min_port, max_port):
444         cryptogen = SystemRandom()
445         return cryptogen.randrange(min_port, max_port)