trex_stl_streams.py revision 32b6b284
1#!/router/bin/python
2
3from .trex_stl_exceptions import *
4from .trex_stl_types import verify_exclusive_arg, validate_type
5from .trex_stl_packet_builder_interface import CTrexPktBuilderInterface
6from .trex_stl_packet_builder_scapy import STLPktBuilder, Ether, IP, UDP, TCP, RawPcapReader
7from collections import OrderedDict, namedtuple
8
9from scapy.utils import ltoa
10import random
11import yaml
12import base64
13import string
14import traceback
15import copy
16import imp
17
18# base class for TX mode
19class STLTXMode(object):
20    """ mode rate speed """
21
22    def __init__ (self, pps = None, bps_L1 = None, bps_L2 = None, percentage = None):
23        """
24        Speed could be in packet per second (pps) or L2/L1 bps or port precent
25        only one of them is valid.
26        you can enter pps =10000 oe bps_L1=10
27
28        :parameters:
29            pps : float
30               packet per second
31
32            bps_L1 : float
33               bit per second L1 (with IPG)
34
35            bps_L2 : float
36               bit per second L2 (Ethernet-FCS)
37
38            percentage : float
39               link interface precent  0-100 e.g. 10 is 10%% of the port link setup
40
41        .. code-block:: python
42            :caption: STLTXMode Example
43
44                mode = STLTXCont(pps = 10)
45
46                mode = STLTXCont(bps_L1 = 10000000) #10mbps L1
47
48                mode = STLTXCont(bps_L2 = 10000000) #10mbps L2
49
50                mode = STLTXCont(percentage = 10)   #10%
51
52        """
53
54        args = [pps, bps_L1, bps_L2, percentage]
55
56        # default
57        if all([x is None for x in args]):
58            pps = 1.0
59        else:
60            verify_exclusive_arg(args)
61
62        self.fields = {'rate': {}}
63
64        if pps is not None:
65            validate_type('pps', pps, [float, int])
66
67            self.fields['rate']['type']  = 'pps'
68            self.fields['rate']['value'] = pps
69
70        elif bps_L1 is not None:
71            validate_type('bps_L1', bps_L1, [float, int])
72
73            self.fields['rate']['type']  = 'bps_L1'
74            self.fields['rate']['value'] = bps_L1
75
76        elif bps_L2 is not None:
77            validate_type('bps_L2', bps_L2, [float, int])
78
79            self.fields['rate']['type']  = 'bps_L2'
80            self.fields['rate']['value'] = bps_L2
81
82        elif percentage is not None:
83            validate_type('percentage', percentage, [float, int])
84            if not (percentage > 0 and percentage <= 100):
85                raise STLArgumentError('percentage', percentage)
86
87            self.fields['rate']['type']  = 'percentage'
88            self.fields['rate']['value'] = percentage
89
90
91
92    def to_json (self):
93        return self.fields
94
95
96# continuous mode
97class STLTXCont(STLTXMode):
98    """ continuous mode """
99
100    def __init__ (self, **kwargs):
101        """
102        continuous mode
103
104         see :class:`trex_stl_lib.trex_stl_streams.STLTXMode` for rate
105
106        .. code-block:: python
107            :caption: STLTXCont Example
108
109                   mode = STLTXCont(pps = 10)
110
111        """
112        super(STLTXCont, self).__init__(**kwargs)
113
114
115        self.fields['type'] = 'continuous'
116
117    @staticmethod
118    def __str__ ():
119        return "Continuous"
120
121# single burst mode
122class STLTXSingleBurst(STLTXMode):
123    """ Single burst mode """
124
125    def __init__ (self, total_pkts = 1, **kwargs):
126        """
127        single burst mode
128
129            :parameters:
130                 total_pkts : int
131                    how many packets for this burst
132
133         see :class:`trex_stl_lib.trex_stl_streams.STLTXMode` for rate
134
135        .. code-block:: python
136            :caption: STLTXSingleBurst Example
137
138                   mode = STLTXSingleBurst( pps = 10, total_pkts = 1)
139
140        """
141
142
143        if not isinstance(total_pkts, int):
144            raise STLArgumentError('total_pkts', total_pkts)
145
146        super(STLTXSingleBurst, self).__init__(**kwargs)
147
148        self.fields['type'] = 'single_burst'
149        self.fields['total_pkts'] = total_pkts
150
151    @staticmethod
152    def __str__ ():
153        return "Single Burst"
154
155# multi burst mode
156class STLTXMultiBurst(STLTXMode):
157    """ Multi burst mode """
158
159    def __init__ (self,
160                  pkts_per_burst = 1,
161                  ibg = 0.0,   # usec not SEC
162                  count = 1,
163                  **kwargs):
164        """
165        Multi burst mode
166
167        :parameters:
168
169             pkts_per_burst: int
170                how many packets per burst
171
172              ibg : float
173                inter burst gap in usec 1000,000.0 is 1 sec
174
175              count : int
176                how many bursts
177
178         see :class:`trex_stl_lib.trex_stl_streams.STLTXMode` for rate
179
180        .. code-block:: python
181            :caption: STLTXMultiBurst Example
182
183                   mode = STLTXMultiBurst(pps = 10, pkts_per_burst = 1,count 10, ibg=10.0)
184
185        """
186
187
188        if not isinstance(pkts_per_burst, int):
189            raise STLArgumentError('pkts_per_burst', pkts_per_burst)
190
191        if not isinstance(ibg, (int, float)):
192            raise STLArgumentError('ibg', ibg)
193
194        if not isinstance(count, int):
195            raise STLArgumentError('count', count)
196
197        super(STLTXMultiBurst, self).__init__(**kwargs)
198
199        self.fields['type'] = 'multi_burst'
200        self.fields['pkts_per_burst'] = pkts_per_burst
201        self.fields['ibg'] = ibg
202        self.fields['count'] = count
203
204    @staticmethod
205    def __str__ ():
206        return "Multi Burst"
207
208STLStreamDstMAC_CFG_FILE=0
209STLStreamDstMAC_PKT     =1
210STLStreamDstMAC_ARP     =2
211
212# RX stats class
213class STLFlowStats(object):
214    """ Define per stream stats
215
216        .. code-block:: python
217            :caption: STLFlowStats Example
218
219            flow_stats = STLFlowStats(pg_id = 7)
220
221    """
222
223    def __init__ (self, pg_id):
224        self.fields = {}
225
226        self.fields['enabled']         = True
227        self.fields['stream_id']       = pg_id
228        self.fields['seq_enabled']     = False
229        self.fields['latency_enabled'] = False
230
231
232    def to_json (self):
233        """ dump as json"""
234        return dict(self.fields)
235
236    @staticmethod
237    def defaults ():
238        return {'enabled' : False}
239
240class STLStream(object):
241    """ One stream object, include mode, Field Engine mode packet template and Rx stats
242
243        .. code-block:: python
244            :caption: STLStream Example
245
246
247            base_pkt =  Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)
248            pad = max(0, size - len(base_pkt)) * 'x'
249
250            STLStream( isg = 10.0, # star in delay
251                       name    ='S0',
252                       packet = STLPktBuilder(pkt = base_pkt/pad),
253                       mode = STLTXSingleBurst( pps = 10, total_pkts = 1),
254                       next = 'S1'), # point to next stream
255
256
257    """
258
259    def __init__ (self,
260                  name = None,
261                  packet = None,
262                  mode = STLTXCont(pps = 1),
263                  enabled = True,
264                  self_start = True,
265                  isg = 0.0,
266                  flow_stats = None,
267                  next = None,
268                  stream_id = None,
269                  action_count = 0,
270                  random_seed =0,
271                  mac_src_override_by_pkt=None,
272                  mac_dst_override_mode=None    #see  STLStreamDstMAC_xx
273                  ):
274        """
275        Stream object
276
277        :parameters:
278
279                  name  : string
280                       The name of the stream. Needed if this stream is dependent on another stream and another stream need to refer to this stream by its name.
281
282                  packet :  STLPktBuilder see :class:`trex_stl_lib.trex_stl_packet_builder_scapy.STLPktBuilder`
283                       The template packet and field engine program e.g. packet = STLPktBuilder(pkt = base_pkt/pad)
284
285                  mode :  :class:`trex_stl_lib.trex_stl_streams.STLTXCont` or :class:`trex_stl_lib.trex_stl_streams.STLTXSingleBurst`  or  :class:`trex_stl_lib.trex_stl_streams.STLTXMultiBurst`
286
287                  enabled : bool
288                      if the stream is enabled.
289
290                  self_start : bool
291                      In case it is False another stream will activate it
292
293                  isg : float
294                     Inter stream gap in usec. time to wait until stream will send the first packet
295
296                  flow_stats : :class:`trex_stl_lib.trex_stl_streams.STLFlowStats`
297                      Per stream statistic object see STLFlowStats
298
299                  next : string
300                      The name of the stream to activate
301
302                  stream_id :
303                        for HLTAPI usage
304
305                  action_count : uint16_t
306                        In case there is a next stream how many loops until stopping. Default is zero, which mean unlimited
307
308                  random_seed: uint16_t
309                       If given the seed for this stream will be this value. Good in case you need a deterministic random value
310
311                  mac_src_override_by_pkt : bool
312                        Template packet will set src MAC
313
314                  mac_dst_override_mode=None : STLStreamDstMAC_xx
315                        Template packet will set dst MAC
316        """
317
318
319        # type checking
320        validate_type('mode', mode, STLTXMode)
321        validate_type('packet', packet, (type(None), CTrexPktBuilderInterface))
322        validate_type('enabled', enabled, bool)
323        validate_type('self_start', self_start, bool)
324        validate_type('isg', isg, (int, float))
325        validate_type('stream_id', stream_id, (type(None), int))
326        validate_type('random_seed',random_seed,int);
327
328        if (type(mode) == STLTXCont) and (next != None):
329            raise STLError("continuous stream cannot have a next stream ID")
330
331        # tag for the stream and next - can be anything
332        self.name = name
333        self.next = next
334
335        self.mac_src_override_by_pkt = mac_src_override_by_pkt # save for easy construct code from stream object
336        self.mac_dst_override_mode = mac_dst_override_mode
337        self.id = stream_id
338
339
340        self.fields = {}
341
342        int_mac_src_override_by_pkt = 0;
343        int_mac_dst_override_mode   = 0;
344
345
346        if mac_src_override_by_pkt == None:
347            int_mac_src_override_by_pkt=0
348            if packet :
349                if packet.is_default_src_mac ()==False:
350                    int_mac_src_override_by_pkt=1
351
352        else:
353            int_mac_src_override_by_pkt = int(mac_src_override_by_pkt);
354
355        if mac_dst_override_mode == None:
356            int_mac_dst_override_mode   = 0;
357            if packet :
358                if packet.is_default_dst_mac ()==False:
359                    int_mac_dst_override_mode=STLStreamDstMAC_PKT
360        else:
361            int_mac_dst_override_mode = int(mac_dst_override_mode);
362
363
364        self.fields['flags'] = (int_mac_src_override_by_pkt&1) +  ((int_mac_dst_override_mode&3)<<1)
365
366        self.fields['action_count'] = action_count
367
368        # basic fields
369        self.fields['enabled'] = enabled
370        self.fields['self_start'] = self_start
371        self.fields['isg'] = isg
372
373        if random_seed !=0 :
374            self.fields['random_seed'] = random_seed # optional
375
376        # mode
377        self.fields['mode'] = mode.to_json()
378        self.mode_desc      = str(mode)
379
380
381        # packet
382        self.fields['packet'] = {}
383        self.fields['vm'] = {}
384
385        if not packet:
386            packet = STLPktBuilder(pkt = Ether()/IP())
387
388        self.scapy_pkt_builder = packet
389        # packet builder
390        packet.compile()
391
392        # packet and VM
393        self.fields['packet'] = packet.dump_pkt()
394        self.fields['vm']     = packet.get_vm_data()
395
396        self.pkt = base64.b64decode(self.fields['packet']['binary'])
397
398        # this is heavy, calculate lazy
399        self.packet_desc = None
400
401        if not flow_stats:
402            self.fields['flow_stats'] = STLFlowStats.defaults()
403        else:
404            self.fields['flow_stats'] = flow_stats.to_json()
405
406
407    def __str__ (self):
408        s =  "Stream Name: {0}\n".format(self.name)
409        s += "Stream Next: {0}\n".format(self.next)
410        s += "Stream JSON:\n{0}\n".format(json.dumps(self.fields, indent = 4, separators=(',', ': '), sort_keys = True))
411        return s
412
413    def to_json (self):
414        """
415        return json format
416        """
417        return dict(self.fields)
418
419    def get_id (self):
420        """ Get the stream id after resolution  """
421        return self.id
422
423
424    def get_name (self):
425        """ Get the stream name """
426        return self.name
427
428    def get_next (self):
429        """ Get next stream object """
430        return self.next
431
432
433    def has_flow_stats (self):
434        """ Return True if stream was configured with flow stats """
435        return self.fields['flow_stats']['enabled']
436
437    def get_pkt (self):
438        """ Get packet as string """
439        return self.pkt
440
441    def get_pkt_len (self, count_crc = True):
442       """ Get packet number of bytes  """
443       pkt_len = len(self.get_pkt())
444       if count_crc:
445           pkt_len += 4
446
447       return pkt_len
448
449
450    def get_pkt_type (self):
451        """ Get packet description for example IP:UDP """
452        if self.packet_desc == None:
453            self.packet_desc = STLPktBuilder.pkt_layers_desc_from_buffer(self.get_pkt())
454
455        return self.packet_desc
456
457    def get_mode (self):
458        return self.mode_desc
459
460    @staticmethod
461    def get_rate_from_field (rate_json):
462        """ Get rate from json  """
463        t = rate_json['type']
464        v = rate_json['value']
465
466        if t == "pps":
467            return format_num(v, suffix = "pps")
468        elif t == "bps_L1":
469            return format_num(v, suffix = "bps (L1)")
470        elif t == "bps_L2":
471            return format_num(v, suffix = "bps (L2)")
472        elif t == "percentage":
473            return format_num(v, suffix = "%")
474
475    def get_rate (self):
476        return self.get_rate_from_field(self.fields['mode']['rate'])
477
478    def to_pkt_dump (self):
479        """ print packet description from scapy  """
480        if self.name:
481            print("Stream Name: ",self.name)
482        scapy_b = self.scapy_pkt_builder;
483        if scapy_b and isinstance(scapy_b,STLPktBuilder):
484            scapy_b.to_pkt_dump()
485        else:
486            print("Nothing to dump")
487
488
489
490    def to_yaml (self):
491        """ convert to YAML  """
492        y = {}
493
494        if self.name:
495            y['name'] = self.name
496
497        if self.next:
498            y['next'] = self.next
499
500        y['stream'] = copy.deepcopy(self.fields)
501
502        # some shortcuts for YAML
503        rate_type  = self.fields['mode']['rate']['type']
504        rate_value = self.fields['mode']['rate']['value']
505
506        y['stream']['mode'][rate_type] = rate_value
507        del y['stream']['mode']['rate']
508
509        return y
510
511    # returns the Python code (text) to build this stream, inside the code it will be in variable "stream"
512    def to_code (self):
513        """ convert to Python code as profile  """
514        packet = Ether(self.pkt)
515        layer = packet
516        while layer:                    # remove checksums
517            for chksum_name in ('cksum', 'chksum'):
518                if chksum_name in layer.fields:
519                    del layer.fields[chksum_name]
520            layer = layer.payload
521        packet.hide_defaults()          # remove fields with default values
522        payload = packet.getlayer('Raw')
523        packet_command = packet.command()
524        imports_arr = []
525        if 'MPLS(' in packet_command:
526            imports_arr.append('from scapy.contrib.mpls import MPLS')
527
528        imports = '\n'.join(imports_arr)
529        if payload:
530            payload.remove_payload() # fcs etc.
531            data = payload.fields.get('load', '')
532
533            good_printable = [c for c in string.printable if ord(c) not in range(32)]
534            good_printable.remove("'")
535
536            if type(data) is str:
537                new_data = ''.join([c if c in good_printable else r'\x{0:02x}'.format(ord(c)) for c in data])
538            else:
539                new_data = ''.join([chr(c) if chr(c) in good_printable else r'\x{0:02x}'.format(c) for c in data])
540
541            payload_start = packet_command.find("Raw(load=")
542            if payload_start != -1:
543                packet_command = packet_command[:payload_start-1]
544        layers = packet_command.split('/')
545
546        if payload:
547            if len(new_data) and new_data == new_data[0] * len(new_data):
548                layers.append("Raw(load='%s' * %s)" % (new_data[0], len(new_data)))
549            else:
550                layers.append("Raw(load='%s')" % new_data)
551
552        packet_code = 'packet = (' + (' / \n          ').join(layers) + ')'
553        vm_list = []
554        for inst in self.fields['vm']['instructions']:
555            if inst['type'] == 'flow_var':
556                vm_list.append("STLVmFlowVar(name='{name}', size={size}, op='{op}', init_value={init_value}, min_value={min_value}, max_value={max_value}, step={step})".format(**inst))
557            elif inst['type'] == 'write_flow_var':
558                vm_list.append("STLVmWrFlowVar(fv_name='{name}', pkt_offset={pkt_offset}, add_val={add_value}, is_big={is_big_endian})".format(**inst))
559            elif inst['type'] == 'write_mask_flow_var':
560                inst = copy.copy(inst)
561                inst['mask'] = hex(inst['mask'])
562                vm_list.append("STLVmWrMaskFlowVar(fv_name='{name}', pkt_offset={pkt_offset}, pkt_cast_size={pkt_cast_size}, mask={mask}, shift={shift}, add_value={add_value}, is_big={is_big_endian})".format(**inst))
563            elif inst['type'] == 'fix_checksum_ipv4':
564                vm_list.append("STLVmFixIpv4(offset={pkt_offset})".format(**inst))
565            elif inst['type'] == 'trim_pkt_size':
566                vm_list.append("STLVmTrimPktSize(fv_name='{name}')".format(**inst))
567            elif inst['type'] == 'tuple_flow_var':
568                inst = copy.copy(inst)
569                inst['ip_min'] = ltoa(inst['ip_min'])
570                inst['ip_max'] = ltoa(inst['ip_max'])
571                vm_list.append("STLVmTupleGen(name='{name}', ip_min='{ip_min}', ip_max='{ip_max}', port_min={port_min}, port_max={port_max}, limit_flows={limit_flows}, flags={flags})".format(**inst))
572        vm_code = 'vm = STLScVmRaw([' + ',\n                 '.join(vm_list) + '], split_by_field = %s)' % STLStream.__add_quotes(self.fields['vm'].get('split_by_var'))
573        stream_params_list = []
574        stream_params_list.append('packet = STLPktBuilder(pkt = packet, vm = vm)')
575        if default_STLStream.name != self.name:
576            stream_params_list.append('name = %s' % STLStream.__add_quotes(self.name))
577        if default_STLStream.fields['enabled'] != self.fields['enabled']:
578            stream_params_list.append('enabled = %s' % self.fields['enabled'])
579        if default_STLStream.fields['self_start'] != self.fields['self_start']:
580            stream_params_list.append('self_start = %s' % self.fields['self_start'])
581        if default_STLStream.fields['isg'] != self.fields['isg']:
582            stream_params_list.append('isg = %s' % self.fields['isg'])
583        if default_STLStream.fields['flow_stats'] != self.fields['flow_stats']:
584            stream_params_list.append('flow_stats = STLFlowStats(%s)' % self.fields['flow_stats']['stream_id'])
585        if default_STLStream.next != self.next:
586            stream_params_list.append('next = %s' % STLStream.__add_quotes(self.next))
587        if default_STLStream.id != self.id:
588            stream_params_list.append('stream_id = %s' % self.id)
589        if default_STLStream.fields['action_count'] != self.fields['action_count']:
590            stream_params_list.append('action_count = %s' % self.fields['action_count'])
591        if 'random_seed' in self.fields:
592            stream_params_list.append('random_seed = %s' % self.fields.get('random_seed', 0))
593        if default_STLStream.mac_src_override_by_pkt != self.mac_src_override_by_pkt:
594            stream_params_list.append('mac_src_override_by_pkt = %s' % self.mac_src_override_by_pkt)
595        if default_STLStream.mac_dst_override_mode != self.mac_dst_override_mode:
596            stream_params_list.append('mac_dst_override_mode = %s' % self.mac_dst_override_mode)
597
598        mode_args = ''
599        for key, value in self.fields['mode'].items():
600            if key not in ('rate', 'type'):
601                mode_args += '%s = %s, ' % (key, value)
602        mode_args += '%s = %s' % (self.fields['mode']['rate']['type'], self.fields['mode']['rate']['value'])
603        if self.mode_desc == STLTXCont.__str__():
604            stream_params_list.append('mode = STLTXCont(%s)' % mode_args)
605        elif self.mode_desc == STLTXSingleBurst().__str__():
606            stream_params_list.append('mode = STLTXSingleBurst(%s)' % mode_args)
607        elif self.mode_desc == STLTXMultiBurst().__str__():
608            stream_params_list.append('mode = STLTXMultiBurst(%s)' % mode_args)
609        else:
610            raise STLError('Could not determine mode: %s' % self.mode_desc)
611
612        stream = "stream = STLStream(" + ',\n                   '.join(stream_params_list) + ')'
613        return '\n'.join([imports, packet_code, vm_code, stream])
614
615    # add quoted for string, or leave as is if other type
616    @staticmethod
617    def __add_quotes(arg):
618        if type(arg) is str:
619            return "'%s'" % arg
620        return arg
621
622    # used to replace non-printable characters with hex
623    @staticmethod
624    def __replchars_to_hex(match):
625        return r'\x{0:02x}'.format(ord(match.group()))
626
627    def dump_to_yaml (self, yaml_file = None):
628        """ print as yaml  """
629        yaml_dump = yaml.dump([self.to_yaml()], default_flow_style = False)
630
631        # write to file if provided
632        if yaml_file:
633            with open(yaml_file, 'w') as f:
634                f.write(yaml_dump)
635
636        return yaml_dump
637
638class YAMLLoader(object):
639
640    def __init__ (self, yaml_file):
641        self.yaml_path = os.path.dirname(yaml_file)
642        self.yaml_file = yaml_file
643
644
645    def __parse_packet (self, packet_dict):
646
647        packet_type = set(packet_dict).intersection(['binary', 'pcap'])
648        if len(packet_type) != 1:
649            raise STLError("packet section must contain either 'binary' or 'pcap'")
650
651        if 'binary' in packet_type:
652            try:
653                pkt_str = base64.b64decode(packet_dict['binary'])
654            except TypeError:
655                raise STLError("'binary' field is not a valid packet format")
656
657            builder = STLPktBuilder(pkt_buffer = pkt_str)
658
659        elif 'pcap' in packet_type:
660            pcap = os.path.join(self.yaml_path, packet_dict['pcap'])
661
662            if not os.path.exists(pcap):
663                raise STLError("'pcap' - cannot find '{0}'".format(pcap))
664
665            builder = STLPktBuilder(pkt = pcap)
666
667        return builder
668
669
670    def __parse_mode (self, mode_obj):
671        if not mode_obj:
672            return None
673
674        rate_parser = set(mode_obj).intersection(['pps', 'bps_L1', 'bps_L2', 'percentage'])
675        if len(rate_parser) != 1:
676            raise STLError("'rate' must contain exactly one from 'pps', 'bps_L1', 'bps_L2', 'percentage'")
677
678        rate_type  = rate_parser.pop()
679        rate = {rate_type : mode_obj[rate_type]}
680
681        mode_type = mode_obj.get('type')
682
683        if mode_type == 'continuous':
684            mode = STLTXCont(**rate)
685
686        elif mode_type == 'single_burst':
687            defaults = STLTXSingleBurst()
688            mode = STLTXSingleBurst(total_pkts  = mode_obj.get('total_pkts', defaults.fields['total_pkts']),
689                                    **rate)
690
691        elif mode_type == 'multi_burst':
692            defaults = STLTXMultiBurst()
693            mode = STLTXMultiBurst(pkts_per_burst = mode_obj.get('pkts_per_burst', defaults.fields['pkts_per_burst']),
694                                   ibg            = mode_obj.get('ibg', defaults.fields['ibg']),
695                                   count          = mode_obj.get('count', defaults.fields['count']),
696                                   **rate)
697
698        else:
699            raise STLError("mode type can be 'continuous', 'single_burst' or 'multi_burst")
700
701
702        return mode
703
704
705
706    def __parse_flow_stats (self, flow_stats_obj):
707
708        # no such object
709        if not flow_stats_obj or flow_stats_obj.get('enabled') == False:
710            return None
711
712        pg_id = flow_stats_obj.get('stream_id')
713        if pg_id == None:
714            raise STLError("enabled RX stats section must contain 'stream_id' field")
715
716        return STLFlowStats(pg_id = pg_id)
717
718
719    def __parse_stream (self, yaml_object):
720        s_obj = yaml_object['stream']
721
722        # parse packet
723        packet = s_obj.get('packet')
724        if not packet:
725            raise STLError("YAML file must contain 'packet' field")
726
727        builder = self.__parse_packet(packet)
728
729
730        # mode
731        mode = self.__parse_mode(s_obj.get('mode'))
732
733        # rx stats
734        flow_stats = self.__parse_flow_stats(s_obj.get('flow_stats'))
735
736
737        defaults = default_STLStream
738        # create the stream
739        stream = STLStream(name       = yaml_object.get('name'),
740                           packet     = builder,
741                           mode       = mode,
742                           flow_stats   = flow_stats,
743                           enabled    = s_obj.get('enabled', defaults.fields['enabled']),
744                           self_start = s_obj.get('self_start', defaults.fields['self_start']),
745                           isg        = s_obj.get('isg', defaults.fields['isg']),
746                           next       = yaml_object.get('next'),
747                           action_count = s_obj.get('action_count', defaults.fields['action_count']),
748                           mac_src_override_by_pkt = s_obj.get('mac_src_override_by_pkt', 0),
749                           mac_dst_override_mode = s_obj.get('mac_src_override_by_pkt', 0)
750                           )
751
752        # hack the VM fields for now
753        if 'vm' in s_obj:
754            stream.fields['vm'].update(s_obj['vm'])
755
756        return stream
757
758
759    def parse (self):
760        with open(self.yaml_file, 'r') as f:
761            # read YAML and pass it down to stream object
762            yaml_str = f.read()
763
764            try:
765                objects = yaml.load(yaml_str)
766            except yaml.parser.ParserError as e:
767                raise STLError(str(e))
768
769            streams = [self.__parse_stream(object) for object in objects]
770
771            return streams
772
773
774# profile class
775class STLProfile(object):
776    """ Describe a list of streams
777
778        .. code-block:: python
779            :caption: STLProfile Example
780
781              profile =  STLProfile( [ STLStream( isg = 10.0, # star in delay
782                                        name    ='S0',
783                                        packet = STLPktBuilder(pkt = base_pkt/pad),
784                                        mode = STLTXSingleBurst( pps = 10, total_pkts = self.burst_size),
785                                        next = 'S1'), # point to next stream
786
787                             STLStream( self_start = False, # stream is  disabled enable trow S0
788                                        name    ='S1',
789                                        packet  = STLPktBuilder(pkt = base_pkt1/pad),
790                                        mode    = STLTXSingleBurst( pps = 10, total_pkts = self.burst_size),
791                                        next    = 'S2' ),
792
793                             STLStream(  self_start = False, # stream is  disabled enable trow S0
794                                         name   ='S2',
795                                         packet = STLPktBuilder(pkt = base_pkt2/pad),
796                                         mode = STLTXSingleBurst( pps = 10, total_pkts = self.burst_size )
797                                        )
798                            ]).get_streams()
799
800
801
802    """
803
804    def __init__ (self, streams = None):
805        """
806
807            :parameters:
808
809                  streams  : list of :class:`trex_stl_lib.trex_stl_streams.STLStream`
810                       a list of stream objects
811
812        """
813
814
815        if streams == None:
816            streams = []
817
818        if not type(streams) == list:
819            streams = [streams]
820
821        if not all([isinstance(stream, STLStream) for stream in streams]):
822            raise STLArgumentError('streams', streams, valid_values = STLStream)
823
824        self.streams = streams
825        self.meta = None
826
827
828    def get_streams (self):
829        """ Get the list of stream"""
830        return self.streams
831
832    def __str__ (self):
833        return '\n'.join([str(stream) for stream in self.streams])
834
835    def is_pauseable (self):
836        return all([x.get_mode() == "Continuous" for x in (self.get_streams())])
837
838    def needs_rx_caps (self):
839        return any([x.has_flow_stats() for x in self.get_streams()])
840
841    @staticmethod
842    def load_yaml (yaml_file):
843        """ load from YAML file a profile with number of streams"""
844
845        # check filename
846        if not os.path.isfile(yaml_file):
847            raise STLError("file '{0}' does not exists".format(yaml_file))
848
849        yaml_loader = YAMLLoader(yaml_file)
850        streams = yaml_loader.parse()
851
852        profile = STLProfile(streams)
853        profile.meta = {'type': 'yaml'}
854
855        return profile
856
857    @staticmethod
858    def get_module_tunables(module):
859        # remove self and variables
860        func = module.register().get_streams
861        argc = func.__code__.co_argcount
862        tunables = func.__code__.co_varnames[1:argc]
863
864        # fetch defaults
865        defaults = func.__defaults__
866        if len(defaults) != (argc - 1):
867            raise STLError("Module should provide default values for all arguments on get_streams()")
868
869        output = {}
870        for t, d in zip(tunables, defaults):
871            output[t] = d
872
873        return output
874
875
876    @staticmethod
877    def load_py (python_file, direction = 0, port_id = 0, **kwargs):
878        """ load from Python profile """
879
880        # check filename
881        if not os.path.isfile(python_file):
882            raise STLError("file '{0}' does not exists".format(python_file))
883
884        basedir = os.path.dirname(python_file)
885        sys.path.append(basedir)
886
887        try:
888            file    = os.path.basename(python_file).split('.')[0]
889            module = __import__(file, globals(), locals(), [], 0)
890            imp.reload(module) # reload the update
891
892            t = STLProfile.get_module_tunables(module)
893            for arg in kwargs:
894                if not arg in t:
895                    raise STLError("profile {0} does not support tunable '{1}' - supported tunables are: '{2}'".format(python_file, arg, t))
896
897            streams = module.register().get_streams(direction = direction,
898                                                    port_id = port_id,
899                                                    **kwargs)
900            profile = STLProfile(streams)
901
902            profile.meta = {'type': 'python',
903                            'tunables': t}
904
905            return profile
906
907        except Exception as e:
908            a, b, tb = sys.exc_info()
909            x =''.join(traceback.format_list(traceback.extract_tb(tb)[1:])) + a.__name__ + ": " + str(b) + "\n"
910
911            summary = "\nPython Traceback follows:\n\n" + x
912            raise STLError(summary)
913
914
915        finally:
916            sys.path.remove(basedir)
917
918
919    # loop_count = 0 means loop forever
920    @staticmethod
921    def load_pcap (pcap_file, ipg_usec = None, speedup = 1.0, loop_count = 1, vm = None):
922        """ Convert a pcap file with a number of packets to a list of connected streams
923
924        packet1->packet2->packet3 etc
925
926                :parameters:
927
928                  pcap_file  : string
929                       The name of the pcap file
930
931                  ipg_usec   : float
932                       The inter packet gap in usec. in case of None IPG is taken from pcap file
933
934                  speedup   : float
935                       By which factor to get IPG smaller so we will send pcap file in speedup
936
937                  loop_count : uint16_t
938                       how many loops to repeat the pcap file
939
940                  vm        :  list
941                        A list of Field engine instructions
942
943                 :return: STLProfile
944
945        """
946
947        # check filename
948        if not os.path.isfile(pcap_file):
949            raise STLError("file '{0}' does not exists".format(pcap_file))
950
951        # make sure IPG is not less than 1 usec
952        if ipg_usec is not None and ipg_usec < 1:
953            raise STLError("ipg_usec cannot be less than 1 usec: '{0}'".format(ipg_usec))
954
955        if loop_count < 0:
956            raise STLError("'loop_count' cannot be negative")
957
958        streams = []
959        last_ts_usec = 0
960
961        pkts = RawPcapReader(pcap_file).read_all()
962
963        for i, (cap, meta) in enumerate(pkts, start = 1):
964            # IPG - if not provided, take from cap
965            if ipg_usec == None:
966                ts_usec = (meta[0] * 1e6 + meta[1]) / float(speedup)
967            else:
968                ts_usec = (ipg_usec * i) / float(speedup)
969
970            # handle last packet
971            if i == len(pkts):
972                next = 1
973                action_count = loop_count
974            else:
975                next = i + 1
976                action_count = 0
977
978
979            streams.append(STLStream(name = i,
980                                     packet = STLPktBuilder(pkt_buffer = cap, vm = vm),
981                                     mode = STLTXSingleBurst(total_pkts = 1, percentage = 100),
982                                     self_start = True if (i == 1) else False,
983                                     isg = (ts_usec - last_ts_usec),  # seconds to usec
984                                     action_count = action_count,
985                                     next = next))
986
987            last_ts_usec = ts_usec
988
989
990        profile = STLProfile(streams)
991        profile.meta = {'type': 'pcap'}
992
993        return profile
994
995
996
997    @staticmethod
998    def load (filename, direction = 0, port_id = 0, **kwargs):
999        """ load a profile by its type supported type are
1000           * py
1001           * yaml
1002           * pcap file that converted to profile automaticly
1003
1004           :parameters:
1005              filename  : string as filename
1006              direction : profile's direction (if supported by the profile)
1007              port_id   : which port ID this profile is being loaded to
1008              kwargs    : forward those key-value pairs to the profile
1009
1010        """
1011
1012        x = os.path.basename(filename).split('.')
1013        suffix = x[1] if (len(x) == 2) else None
1014
1015        if suffix == 'py':
1016            profile = STLProfile.load_py(filename, direction, port_id, **kwargs)
1017
1018        elif suffix == 'yaml':
1019            profile = STLProfile.load_yaml(filename)
1020
1021        elif suffix in ['cap', 'pcap']:
1022            profile = STLProfile.load_pcap(filename, speedup = 1, ipg_usec = 1e6)
1023
1024        else:
1025            raise STLError("unknown profile file type: '{0}'".format(suffix))
1026
1027        profile.meta['stream_count'] = len(profile.get_streams()) if isinstance(profile.get_streams(), list) else 1
1028        return profile
1029
1030    @staticmethod
1031    def get_info (filename):
1032        profile = STLProfile.load(filename)
1033        return profile.meta
1034
1035    def dump_as_pkt (self):
1036        """ dump the profile as scapy packet. in case it is raw convert to scapy and dump it"""
1037        cnt=0;
1038        for stream in self.streams:
1039            print("=======================")
1040            print("Stream %d" % cnt)
1041            print("=======================")
1042            cnt = cnt +1
1043            stream.to_pkt_dump()
1044
1045    def dump_to_yaml (self, yaml_file = None):
1046        """ convert it to yaml """
1047        yaml_list = [stream.to_yaml() for stream in self.streams]
1048        yaml_str = yaml.dump(yaml_list, default_flow_style = False)
1049
1050        # write to file if provided
1051        if yaml_file:
1052            with open(yaml_file, 'w') as f:
1053                f.write(yaml_str)
1054
1055        return yaml_str
1056
1057    def dump_to_code (self, profile_file = None):
1058        """ convert it to Python native profile. yeah this is cool """
1059        profile_dump = '''# !!! Auto-generated code !!!
1060from trex_stl_lib.api import *
1061
1062class STLS1(object):
1063    def get_streams(self, direction = 0, **kwargs):
1064        streams = []
1065'''
1066        for stream in self.streams:
1067            profile_dump += ' '*8 + stream.to_code().replace('\n', '\n' + ' '*8) + '\n'
1068            profile_dump += ' '*8 + 'streams.append(stream)\n'
1069        profile_dump += '''
1070        return streams
1071
1072def register():
1073    return STLS1()
1074'''
1075        # write to file if provided
1076        if profile_file:
1077            with open(profile_file, 'w') as f:
1078                f.write(profile_dump)
1079
1080        return profile_dump
1081
1082
1083
1084    def __len__ (self):
1085        return len(self.streams)
1086
1087default_STLStream = STLStream()
1088