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