trex_stl_client.py revision d0c838e0
1#!/router/bin/python
2
3# for API usage the path name must be full
4from .trex_stl_exceptions import *
5from .trex_stl_streams import *
6
7from .trex_stl_jsonrpc_client import JsonRpcClient, BatchMessage
8from . import trex_stl_stats
9
10from .trex_stl_port import Port
11from .trex_stl_types import *
12from .trex_stl_async_client import CTRexAsyncClient
13
14from .utils import parsing_opts, text_tables, common
15from .utils.common import list_intersect, list_difference, is_sub_list, PassiveTimer, is_valid_ipv4, is_valid_mac, list_remove_dup
16from .utils.text_opts import *
17from functools import wraps
18
19from collections import namedtuple
20from yaml import YAMLError
21import time
22import datetime
23import re
24import random
25import json
26import traceback
27import os.path
28
29
30############################     logger     #############################
31############################                #############################
32############################                #############################
33
34# logger API for the client
35class LoggerApi(object):
36    # verbose levels
37    VERBOSE_QUIET         = 0
38    VERBOSE_REGULAR_SYNC  = 1
39    VERBOSE_REGULAR       = 2
40    VERBOSE_HIGH          = 3
41
42    def __init__(self):
43        self.level = LoggerApi.VERBOSE_REGULAR
44
45    # implemented by specific logger
46    def write(self, msg, newline = True):
47        raise Exception("Implement this")
48
49    # implemented by specific logger
50    def flush(self):
51        raise Exception("Implement this")
52
53    def set_verbose (self, level):
54        if not level in range(self.VERBOSE_QUIET, self.VERBOSE_HIGH + 1):
55            raise ValueError("Bad value provided for logger")
56
57        self.level = level
58
59    def get_verbose (self):
60        return self.level
61
62
63    def check_verbose (self, level):
64        return (self.level >= level)
65
66
67    # simple log message with verbose
68    def log (self, msg, level = VERBOSE_REGULAR_SYNC, newline = True):
69        if not self.check_verbose(level):
70            return
71
72        self.write(msg, newline)
73
74    # logging that comes from async event
75    def async_log (self, msg, level = VERBOSE_REGULAR, newline = True):
76        self.log(msg, level, newline)
77
78
79    def pre_cmd (self, desc):
80        self.log(format_text('\n{:<60}'.format(desc), 'bold'), newline = False)
81        self.flush()
82
83    def post_cmd (self, rc):
84        if rc:
85            self.log(format_text("[SUCCESS]\n", 'green', 'bold'))
86        else:
87            self.log(format_text("[FAILED]\n", 'red', 'bold'))
88
89
90    def log_cmd (self, desc):
91        self.pre_cmd(desc)
92        self.post_cmd(True)
93
94
95    # supress object getter
96    def supress (self, level = VERBOSE_QUIET):
97        class Supress(object):
98            def __init__ (self, logger, level):
99                self.logger = logger
100                self.level = level
101
102            def __enter__ (self):
103                self.saved_level = self.logger.get_verbose()
104                self.logger.set_verbose(self.level)
105
106            def __exit__ (self, type, value, traceback):
107                self.logger.set_verbose(self.saved_level)
108
109        return Supress(self, level)
110
111
112
113# default logger - to stdout
114class DefaultLogger(LoggerApi):
115
116    def __init__ (self):
117        super(DefaultLogger, self).__init__()
118
119    def write (self, msg, newline = True):
120        if newline:
121            print(msg)
122        else:
123            print (msg),
124
125    def flush (self):
126        sys.stdout.flush()
127
128
129############################     async event hander     #############################
130############################                            #############################
131############################                            #############################
132
133# an event
134class Event(object):
135
136    def __init__ (self, origin, ev_type, msg):
137        self.origin = origin
138        self.ev_type = ev_type
139        self.msg = msg
140
141        self.ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
142
143    def __str__ (self):
144
145        prefix = "[{:^}][{:^}]".format(self.origin, self.ev_type)
146
147        return "{:<10} - {:18} - {:}".format(self.ts, prefix, format_text(self.msg, 'bold'))
148
149
150# handles different async events given to the client
151class EventsHandler(object):
152
153
154    def __init__ (self, client):
155        self.client = client
156        self.logger = self.client.logger
157
158        self.events = []
159
160    # public functions
161
162    def get_events (self, ev_type_filter = None):
163        if ev_type_filter:
164            return [ev for ev in self.events if ev.ev_type in listify(ev_type_filter)]
165        else:
166            return [ev for ev in self.events]
167
168
169    def clear_events (self):
170        self.events = []
171
172
173    def log_warning (self, msg, show = True):
174        self.__add_event_log('local', 'warning', msg, show)
175
176
177    # events called internally
178
179    def on_async_dead (self):
180        if self.client.connected:
181            msg = 'Lost connection to server'
182            self.client.connected = False
183            self.__add_event_log('local', 'info', msg, True)
184
185
186    def on_async_alive (self):
187        pass
188
189
190
191    def on_async_rx_stats_event (self, data, baseline):
192        self.client.flow_stats.update(data, baseline)
193
194    def on_async_latency_stats_event (self, data, baseline):
195        self.client.latency_stats.update(data, baseline)
196
197    # handles an async stats update from the subscriber
198    def on_async_stats_update(self, dump_data, baseline):
199        global_stats = {}
200        port_stats = {}
201
202        # filter the values per port and general
203        for key, value in dump_data.items():
204            # match a pattern of ports
205            m = re.search('(.*)\-(\d+)', key)
206            if m:
207                port_id = int(m.group(2))
208                field_name = m.group(1)
209                if port_id in self.client.ports:
210                    if not port_id in port_stats:
211                        port_stats[port_id] = {}
212                    port_stats[port_id][field_name] = value
213                else:
214                    continue
215            else:
216                # no port match - general stats
217                global_stats[key] = value
218
219        # update the general object with the snapshot
220        self.client.global_stats.update(global_stats, baseline)
221
222        # update all ports
223        for port_id, data in port_stats.items():
224            self.client.ports[port_id].port_stats.update(data, baseline)
225
226
227
228    # dispatcher for server async events (port started, port stopped and etc.)
229    def on_async_event (self, event_type, data):
230        # DP stopped
231        show_event = False
232
233        # port started
234        if (event_type == 0):
235            port_id = int(data['port_id'])
236            ev = "Port {0} has started".format(port_id)
237            self.__async_event_port_started(port_id)
238
239        # port stopped
240        elif (event_type == 1):
241            port_id = int(data['port_id'])
242            ev = "Port {0} has stopped".format(port_id)
243
244            # call the handler
245            self.__async_event_port_stopped(port_id)
246
247
248        # port paused
249        elif (event_type == 2):
250            port_id = int(data['port_id'])
251            ev = "Port {0} has paused".format(port_id)
252
253            # call the handler
254            self.__async_event_port_paused(port_id)
255
256        # port resumed
257        elif (event_type == 3):
258            port_id = int(data['port_id'])
259            ev = "Port {0} has resumed".format(port_id)
260
261            # call the handler
262            self.__async_event_port_resumed(port_id)
263
264        # port finished traffic
265        elif (event_type == 4):
266            port_id = int(data['port_id'])
267            ev = "Port {0} job done".format(port_id)
268
269            # call the handler
270            self.__async_event_port_job_done(port_id)
271            show_event = True
272
273        # port was acquired - maybe stolen...
274        elif (event_type == 5):
275            session_id = data['session_id']
276
277            port_id = int(data['port_id'])
278            who     = data['who']
279            force   = data['force']
280
281            # if we hold the port and it was not taken by this session - show it
282            if port_id in self.client.get_acquired_ports() and session_id != self.client.session_id:
283                show_event = True
284
285            # format the thief/us...
286            if session_id == self.client.session_id:
287                user = 'you'
288            elif who == self.client.username:
289                user = 'another session of you'
290            else:
291                user = "'{0}'".format(who)
292
293            if force:
294                ev = "Port {0} was forcely taken by {1}".format(port_id, user)
295            else:
296                ev = "Port {0} was taken by {1}".format(port_id, user)
297
298            # call the handler in case its not this session
299            if session_id != self.client.session_id:
300                self.__async_event_port_acquired(port_id, who)
301
302
303        # port was released
304        elif (event_type == 6):
305            port_id     = int(data['port_id'])
306            who         = data['who']
307            session_id  = data['session_id']
308
309            if session_id == self.client.session_id:
310                user = 'you'
311            elif who == self.client.username:
312                user = 'another session of you'
313            else:
314                user = "'{0}'".format(who)
315
316            ev = "Port {0} was released by {1}".format(port_id, user)
317
318            # call the handler in case its not this session
319            if session_id != self.client.session_id:
320                self.__async_event_port_released(port_id)
321
322        elif (event_type == 7):
323            port_id = int(data['port_id'])
324            ev = "port {0} job failed".format(port_id)
325            show_event = True
326
327        # port attr changed
328        elif (event_type == 8):
329
330            port_id = int(data['port_id'])
331
332            diff = self.__async_event_port_attr_changed(port_id, data['attr'])
333            if not diff:
334                return
335
336
337            ev = "port {0} attributes changed".format(port_id)
338            for key, (old_val, new_val) in diff.items():
339                ev += '\n  {key}: {old} -> {new}'.format(
340                        key = key,
341                        old = old_val.lower() if type(old_val) is str else old_val,
342                        new = new_val.lower() if type(new_val) is str else new_val)
343
344            show_event = True
345
346
347
348        # server stopped
349        elif (event_type == 100):
350            ev = "Server has stopped"
351            # to avoid any new messages on async
352            self.client.async_client.set_as_zombie()
353            self.__async_event_server_stopped()
354            show_event = True
355
356
357        else:
358            # unknown event - ignore
359            return
360
361
362        self.__add_event_log('server', 'info', ev, show_event)
363
364
365    # private functions
366
367    # on rare cases events may come on a non existent prot
368    # (server was re-run with different config)
369    def __async_event_port_job_done (self, port_id):
370        if port_id in self.client.ports:
371            self.client.ports[port_id].async_event_port_job_done()
372
373    def __async_event_port_stopped (self, port_id):
374        if port_id in self.client.ports:
375            self.client.ports[port_id].async_event_port_stopped()
376
377
378    def __async_event_port_started (self, port_id):
379        if port_id in self.client.ports:
380            self.client.ports[port_id].async_event_port_started()
381
382    def __async_event_port_paused (self, port_id):
383        if port_id in self.client.ports:
384            self.client.ports[port_id].async_event_port_paused()
385
386
387    def __async_event_port_resumed (self, port_id):
388        if port_id in self.client.ports:
389            self.client.ports[port_id].async_event_port_resumed()
390
391    def __async_event_port_acquired (self, port_id, who):
392        if port_id in self.client.ports:
393            self.client.ports[port_id].async_event_acquired(who)
394
395    def __async_event_port_released (self, port_id):
396        if port_id in self.client.ports:
397            self.client.ports[port_id].async_event_released()
398
399    def __async_event_server_stopped (self):
400        self.client.connected = False
401
402    def __async_event_port_attr_changed (self, port_id, attr):
403        if port_id in self.client.ports:
404            return self.client.ports[port_id].async_event_port_attr_changed(attr)
405
406    # add event to log
407    def __add_event_log (self, origin, ev_type, msg, show = False):
408
409        event = Event(origin, ev_type, msg)
410        self.events.append(event)
411        if show:
412            self.logger.async_log("\n\n{0}".format(str(event)))
413
414
415
416
417
418############################     RPC layer     #############################
419############################                   #############################
420############################                   #############################
421
422class CCommLink(object):
423    """Describes the connectivity of the stateless client method"""
424    def __init__(self, server="localhost", port=5050, virtual=False, client = None):
425        self.virtual = virtual
426        self.server = server
427        self.port = port
428        self.rpc_link = JsonRpcClient(self.server, self.port, client)
429
430    @property
431    def is_connected(self):
432        if not self.virtual:
433            return self.rpc_link.connected
434        else:
435            return True
436
437    def get_server (self):
438        return self.server
439
440    def get_port (self):
441        return self.port
442
443    def connect(self):
444        if not self.virtual:
445            return self.rpc_link.connect()
446
447    def disconnect(self):
448        if not self.virtual:
449            return self.rpc_link.disconnect()
450
451    def transmit(self, method_name, params = None, api_class = 'core'):
452        if self.virtual:
453            self._prompt_virtual_tx_msg()
454            _, msg = self.rpc_link.create_jsonrpc_v2(method_name, params, api_class)
455            print(msg)
456            return
457        else:
458            return self.rpc_link.invoke_rpc_method(method_name, params, api_class)
459
460    def transmit_batch(self, batch_list):
461        if self.virtual:
462            self._prompt_virtual_tx_msg()
463            print([msg
464                   for _, msg in [self.rpc_link.create_jsonrpc_v2(command.method, command.params, command.api_class)
465                                  for command in batch_list]])
466        else:
467            batch = self.rpc_link.create_batch()
468            for command in batch_list:
469                batch.add(command.method, command.params, command.api_class)
470            # invoke the batch
471            return batch.invoke()
472
473    def _prompt_virtual_tx_msg(self):
474        print("Transmitting virtually over tcp://{server}:{port}".format(server=self.server,
475                                                                         port=self.port))
476
477
478
479############################     client     #############################
480############################                #############################
481############################                #############################
482
483class STLClient(object):
484    """TRex Stateless client object - gives operations per TRex/user"""
485
486    # different modes for attaching traffic to ports
487    CORE_MASK_SPLIT = 1
488    CORE_MASK_PIN   = 2
489
490    def __init__(self,
491                 username = common.get_current_user(),
492                 server = "localhost",
493                 sync_port = 4501,
494                 async_port = 4500,
495                 verbose_level = LoggerApi.VERBOSE_QUIET,
496                 logger = None,
497                 virtual = False):
498        """
499        Configure the connection settings
500
501        :parameters:
502             username : string
503                the user name, for example imarom
504
505              server  : string
506                the server name or ip
507
508              sync_port : int
509                the RPC port
510
511              async_port : int
512                the ASYNC port
513
514        .. code-block:: python
515
516            # Example
517
518            # connect to local TRex server
519            c = STLClient()
520
521            # connect to remote server trex-remote-server
522            c = STLClient(server = "trex-remote-server" )
523
524            c = STLClient(server = "10.0.0.10" )
525
526            # verbose mode
527            c = STLClient(server = "10.0.0.10", verbose_level = LoggerApi.VERBOSE_HIGH )
528
529            # change user name
530            c = STLClient(username = "root",server = "10.0.0.10", verbose_level = LoggerApi.VERBOSE_HIGH )
531
532            c.connect()
533
534            c.disconnect()
535
536        """
537
538        self.username   = username
539
540        # init objects
541        self.ports = {}
542        self.server_version = {}
543        self.system_info = {}
544        self.session_id = random.getrandbits(32)
545        self.connected = False
546
547        # API classes
548        self.api_vers = [ {'type': 'core', 'major': 2, 'minor': 3 } ]
549        self.api_h = {'core': None}
550
551        # logger
552        self.logger = DefaultLogger() if not logger else logger
553
554        # initial verbose
555        self.logger.set_verbose(verbose_level)
556
557        # low level RPC layer
558        self.comm_link = CCommLink(server,
559                                   sync_port,
560                                   virtual,
561                                   self)
562
563        # async event handler manager
564        self.event_handler = EventsHandler(self)
565
566        # async subscriber level
567        self.async_client = CTRexAsyncClient(server,
568                                             async_port,
569                                             self)
570
571
572
573
574        # stats
575        self.connection_info = {"username":   username,
576                                "server":     server,
577                                "sync_port":  sync_port,
578                                "async_port": async_port,
579                                "virtual":    virtual}
580
581
582        self.global_stats = trex_stl_stats.CGlobalStats(self.connection_info,
583                                                        self.server_version,
584                                                        self.ports,
585                                                        self.event_handler)
586
587        self.flow_stats = trex_stl_stats.CRxStats(self.ports)
588
589        self.latency_stats = trex_stl_stats.CLatencyStats(self.ports)
590
591        self.util_stats = trex_stl_stats.CUtilStats(self)
592
593        self.xstats = trex_stl_stats.CXStats(self)
594
595        self.stats_generator = trex_stl_stats.CTRexInfoGenerator(self.global_stats,
596                                                                 self.ports,
597                                                                 self.flow_stats,
598                                                                 self.latency_stats,
599                                                                 self.util_stats,
600                                                                 self.xstats,
601                                                                 self.async_client.monitor)
602
603
604
605
606    ############# private functions - used by the class itself ###########
607
608    # some preprocessing for port argument
609    def __ports (self, port_id_list):
610
611        # none means all
612        if port_id_list == None:
613            return range(0, self.get_port_count())
614
615        # always list
616        if isinstance(port_id_list, int):
617            port_id_list = [port_id_list]
618
619        if not isinstance(port_id_list, list):
620             raise ValueError("Bad port id list: {0}".format(port_id_list))
621
622        for port_id in port_id_list:
623            if not isinstance(port_id, int) or (port_id < 0) or (port_id > self.get_port_count()):
624                raise ValueError("Bad port id {0}".format(port_id))
625
626        return port_id_list
627
628
629    # sync ports
630    def __sync_ports (self, port_id_list = None, force = False):
631        port_id_list = self.__ports(port_id_list)
632
633        rc = RC()
634
635        for port_id in port_id_list:
636            rc.add(self.ports[port_id].sync())
637
638        return rc
639
640    # acquire ports, if port_list is none - get all
641    def __acquire (self, port_id_list = None, force = False, sync_streams = True):
642        port_id_list = self.__ports(port_id_list)
643
644        rc = RC()
645
646        for port_id in port_id_list:
647            rc.add(self.ports[port_id].acquire(force, sync_streams))
648
649        return rc
650
651    # release ports
652    def __release (self, port_id_list = None):
653        port_id_list = self.__ports(port_id_list)
654
655        rc = RC()
656
657        for port_id in port_id_list:
658            rc.add(self.ports[port_id].release())
659
660        return rc
661
662
663    def __add_streams(self, stream_list, port_id_list = None):
664
665        port_id_list = self.__ports(port_id_list)
666
667        rc = RC()
668
669        for port_id in port_id_list:
670            rc.add(self.ports[port_id].add_streams(stream_list))
671
672        return rc
673
674
675
676    def __remove_streams(self, stream_id_list, port_id_list = None):
677
678        port_id_list = self.__ports(port_id_list)
679
680        rc = RC()
681
682        for port_id in port_id_list:
683            rc.add(self.ports[port_id].remove_streams(stream_id_list))
684
685        return rc
686
687
688
689    def __remove_all_streams(self, port_id_list = None):
690        port_id_list = self.__ports(port_id_list)
691
692        rc = RC()
693
694        for port_id in port_id_list:
695            rc.add(self.ports[port_id].remove_all_streams())
696
697        return rc
698
699
700    def __get_stream(self, stream_id, port_id, get_pkt = False):
701
702        return self.ports[port_id].get_stream(stream_id)
703
704
705    def __get_all_streams(self, port_id, get_pkt = False):
706
707        return self.ports[port_id].get_all_streams()
708
709
710    def __get_stream_id_list(self, port_id):
711
712        return self.ports[port_id].get_stream_id_list()
713
714
715    def __start (self,
716                 multiplier,
717                 duration,
718                 port_id_list,
719                 force,
720                 core_mask):
721
722        port_id_list = self.__ports(port_id_list)
723
724        rc = RC()
725
726
727        for port_id in port_id_list:
728            rc.add(self.ports[port_id].start(multiplier,
729                                             duration,
730                                             force,
731                                             core_mask[port_id]))
732
733        return rc
734
735
736    def __resume (self, port_id_list = None, force = False):
737
738        port_id_list = self.__ports(port_id_list)
739        rc = RC()
740
741        for port_id in port_id_list:
742            rc.add(self.ports[port_id].resume())
743
744        return rc
745
746    def __pause (self, port_id_list = None, force = False):
747
748        port_id_list = self.__ports(port_id_list)
749        rc = RC()
750
751        for port_id in port_id_list:
752            rc.add(self.ports[port_id].pause())
753
754        return rc
755
756
757    def __stop (self, port_id_list = None, force = False):
758
759        port_id_list = self.__ports(port_id_list)
760        rc = RC()
761
762        for port_id in port_id_list:
763            rc.add(self.ports[port_id].stop(force))
764
765        return rc
766
767
768    def __update (self, mult, port_id_list = None, force = False):
769
770        port_id_list = self.__ports(port_id_list)
771        rc = RC()
772
773        for port_id in port_id_list:
774            rc.add(self.ports[port_id].update(mult, force))
775
776        return rc
777
778
779    def __push_remote (self, pcap_filename, port_id_list, ipg_usec, speedup, count, duration, is_dual):
780
781        port_id_list = self.__ports(port_id_list)
782        rc = RC()
783
784        for port_id in port_id_list:
785
786            # for dual, provide the slave handler as well
787            slave_handler = self.ports[port_id ^ 0x1].handler if is_dual else ""
788
789            rc.add(self.ports[port_id].push_remote(pcap_filename,
790                                                   ipg_usec,
791                                                   speedup,
792                                                   count,
793                                                   duration,
794                                                   is_dual,
795                                                   slave_handler))
796
797        return rc
798
799
800    def __validate (self, port_id_list = None):
801        port_id_list = self.__ports(port_id_list)
802
803        rc = RC()
804
805        for port_id in port_id_list:
806            rc.add(self.ports[port_id].validate())
807
808        return rc
809
810
811    def __resolve (self, port_id_list = None, retries = 0):
812        port_id_list = self.__ports(port_id_list)
813
814        rc = RC()
815
816        for port_id in port_id_list:
817            rc.add(self.ports[port_id].arp_resolve(retries))
818
819        return rc
820
821
822    def __set_port_attr (self, port_id_list = None, attr_dict = None):
823
824        port_id_list = self.__ports(port_id_list)
825        rc = RC()
826
827        for port_id, port_attr_dict in zip(port_id_list, attr_dict):
828            rc.add(self.ports[port_id].set_attr(**port_attr_dict))
829
830        return rc
831
832
833    def __set_rx_sniffer (self, port_id_list, base_filename, limit):
834        port_id_list = self.__ports(port_id_list)
835        rc = RC()
836
837        for port_id in port_id_list:
838            head, tail = os.path.splitext(base_filename)
839            filename = "{0}-{1}{2}".format(head, port_id, tail)
840            rc.add(self.ports[port_id].set_rx_sniffer(filename, limit))
841
842        return rc
843
844
845    def __remove_rx_sniffer (self, port_id_list):
846        port_id_list = self.__ports(port_id_list)
847        rc = RC()
848
849        for port_id in port_id_list:
850            rc.add(self.ports[port_id].remove_rx_sniffer())
851
852        return rc
853
854    def __set_rx_queue (self, port_id_list, size):
855        port_id_list = self.__ports(port_id_list)
856        rc = RC()
857
858        for port_id in port_id_list:
859            rc.add(self.ports[port_id].set_rx_queue(size))
860
861        return rc
862
863    def __remove_rx_queue (self, port_id_list):
864        port_id_list = self.__ports(port_id_list)
865        rc = RC()
866
867        for port_id in port_id_list:
868            rc.add(self.ports[port_id].remove_rx_queue())
869
870        return rc
871
872
873    # connect to server
874    def __connect(self):
875
876        # first disconnect if already connected
877        if self.is_connected():
878            self.__disconnect()
879
880        # clear this flag
881        self.connected = False
882
883        # connect sync channel
884        self.logger.pre_cmd("Connecting to RPC server on {0}:{1}".format(self.connection_info['server'], self.connection_info['sync_port']))
885        rc = self.comm_link.connect()
886        self.logger.post_cmd(rc)
887
888        if not rc:
889            return rc
890
891
892        # API sync
893        rc = self._transmit("api_sync", params = {'api_vers': self.api_vers}, api_class = None)
894        if not rc:
895            return rc
896
897        # decode
898        for api in rc.data()['api_vers']:
899            self.api_h[ api['type'] ] = api['api_h']
900
901
902        # version
903        rc = self._transmit("get_version")
904        if not rc:
905            return rc
906
907        self.server_version = rc.data()
908        self.global_stats.server_version = rc.data()
909
910        # cache system info
911        rc = self._transmit("get_system_info")
912        if not rc:
913            return rc
914
915        self.system_info = rc.data()
916        self.global_stats.system_info = rc.data()
917
918        # cache supported commands
919        rc = self._transmit("get_supported_cmds")
920        if not rc:
921            return rc
922
923        self.supported_cmds = sorted(rc.data())
924
925        # create ports
926        for port_id in range(self.system_info["port_count"]):
927            info = self.system_info['ports'][port_id]
928
929            self.ports[port_id] = Port(port_id,
930                                       self.username,
931                                       self.comm_link,
932                                       self.session_id,
933                                       info)
934
935
936        # sync the ports
937        rc = self.__sync_ports()
938        if not rc:
939            return rc
940
941
942        # connect async channel
943        self.logger.pre_cmd("Connecting to publisher server on {0}:{1}".format(self.connection_info['server'], self.connection_info['async_port']))
944        rc = self.async_client.connect()
945        self.logger.post_cmd(rc)
946
947        if not rc:
948            return rc
949
950        self.connected = True
951
952        return RC_OK()
953
954
955    # disconenct from server
956    def __disconnect(self, release_ports = True):
957        # release any previous acquired ports
958        if self.is_connected() and release_ports:
959            self.__release(self.get_acquired_ports())
960
961        self.comm_link.disconnect()
962        self.async_client.disconnect()
963
964        self.connected = False
965
966        return RC_OK()
967
968
969    # clear stats
970    def __clear_stats(self, port_id_list, clear_global, clear_flow_stats, clear_latency_stats, clear_xstats):
971
972        # we must be sync with the server
973        self.async_client.barrier()
974
975        for port_id in port_id_list:
976            self.ports[port_id].clear_stats()
977
978        if clear_global:
979            self.global_stats.clear_stats()
980
981        if clear_flow_stats:
982            self.flow_stats.clear_stats()
983
984        if clear_latency_stats:
985            self.latency_stats.clear_stats()
986
987        if clear_xstats:
988            self.xstats.clear_stats()
989
990        self.logger.log_cmd("Clearing stats on port(s) {0}:".format(port_id_list))
991
992        return RC
993
994
995    # get stats
996    def __get_stats (self, port_id_list):
997        stats = {}
998
999        stats['global'] = self.global_stats.get_stats()
1000
1001        total = {}
1002        for port_id in port_id_list:
1003            port_stats = self.ports[port_id].get_stats()
1004            stats[port_id] = port_stats
1005
1006            for k, v in port_stats.items():
1007                if not k in total:
1008                    total[k] = v
1009                else:
1010                    total[k] += v
1011
1012        stats['total'] = total
1013
1014        stats['flow_stats'] = self.flow_stats.get_stats()
1015        stats['latency'] = self.latency_stats.get_stats()
1016
1017        return stats
1018
1019
1020    def __decode_core_mask (self, ports, core_mask):
1021
1022        # predefined modes
1023        if isinstance(core_mask, int):
1024            if core_mask not in [self.CORE_MASK_PIN, self.CORE_MASK_SPLIT]:
1025                raise STLError("'core_mask' can be either CORE_MASK_PIN, CORE_MASK_SPLIT or a list of masks")
1026
1027            decoded_mask = {}
1028            for port in ports:
1029                # a pin mode was requested and we have
1030                # the second port from the group in the start list
1031                if (core_mask == self.CORE_MASK_PIN) and ( (port ^ 0x1) in ports ):
1032                    decoded_mask[port] = 0x55555555 if( port % 2) == 0 else 0xAAAAAAAA
1033                else:
1034                    decoded_mask[port] = None
1035
1036            return decoded_mask
1037
1038        # list of masks
1039        elif isinstance(core_mask, list):
1040            if len(ports) != len(core_mask):
1041                raise STLError("'core_mask' list must be the same length as 'ports' list")
1042
1043            decoded_mask = {}
1044            for i, port in enumerate(ports):
1045                decoded_mask[port] = core_mask[i]
1046
1047            return decoded_mask
1048
1049
1050
1051    ############ functions used by other classes but not users ##############
1052
1053    def _validate_port_list (self, port_id_list):
1054        # listfiy single int
1055        if isinstance(port_id_list, int):
1056            port_id_list = [port_id_list]
1057
1058        # should be a list
1059        if not isinstance(port_id_list, list):
1060            raise STLTypeError('port_id_list', type(port_id_list), list)
1061
1062        if not port_id_list:
1063            raise STLError('No ports provided')
1064
1065        valid_ports = self.get_all_ports()
1066        for port_id in port_id_list:
1067            if not port_id in valid_ports:
1068                raise STLError("Port ID '{0}' is not a valid port ID - valid values: {1}".format(port_id, valid_ports))
1069
1070        return list_remove_dup(port_id_list)
1071
1072
1073    # transmit request on the RPC link
1074    def _transmit(self, method_name, params = None, api_class = 'core'):
1075        return self.comm_link.transmit(method_name, params, api_class)
1076
1077    # transmit batch request on the RPC link
1078    def _transmit_batch(self, batch_list):
1079        return self.comm_link.transmit_batch(batch_list)
1080
1081    # stats
1082    def _get_formatted_stats(self, port_id_list, stats_mask = trex_stl_stats.COMPACT):
1083
1084        stats_opts = common.list_intersect(trex_stl_stats.ALL_STATS_OPTS, stats_mask)
1085
1086        stats_obj = OrderedDict()
1087        for stats_type in stats_opts:
1088            stats_obj.update(self.stats_generator.generate_single_statistic(port_id_list, stats_type))
1089
1090        return stats_obj
1091
1092    def _get_streams(self, port_id_list, streams_mask=set()):
1093
1094        streams_obj = self.stats_generator.generate_streams_info(port_id_list, streams_mask)
1095
1096        return streams_obj
1097
1098
1099    def _invalidate_stats (self, port_id_list):
1100        for port_id in port_id_list:
1101            self.ports[port_id].invalidate_stats()
1102
1103        self.global_stats.invalidate()
1104        self.flow_stats.invalidate()
1105
1106        return RC_OK()
1107
1108
1109    # remove all RX filters in a safe manner
1110    def _remove_rx_filters (self, ports, rx_delay_ms):
1111
1112        # get the enabled RX ports
1113        rx_ports = [port_id for port_id in ports if self.ports[port_id].has_rx_enabled()]
1114
1115        if not rx_ports:
1116            return RC_OK()
1117
1118        # block while any RX configured port has not yet have it's delay expired
1119        while any([not self.ports[port_id].has_rx_delay_expired(rx_delay_ms) for port_id in rx_ports]):
1120            time.sleep(0.01)
1121
1122        # remove RX filters
1123        rc = RC()
1124        for port_id in rx_ports:
1125            rc.add(self.ports[port_id].remove_rx_filters())
1126
1127        return rc
1128
1129
1130    #################################
1131    # ------ private methods ------ #
1132    @staticmethod
1133    def __get_mask_keys(ok_values={True}, **kwargs):
1134        masked_keys = set()
1135        for key, val in kwargs.items():
1136            if val in ok_values:
1137                masked_keys.add(key)
1138        return masked_keys
1139
1140    @staticmethod
1141    def __filter_namespace_args(namespace, ok_values):
1142        return {k: v for k, v in namespace.__dict__.items() if k in ok_values}
1143
1144
1145    # API decorator - double wrap because of argument
1146    def __api_check(connected = True):
1147
1148        def wrap (f):
1149            @wraps(f)
1150            def wrap2(*args, **kwargs):
1151                client = args[0]
1152
1153                func_name = f.__name__
1154
1155                # check connection
1156                if connected and not client.is_connected():
1157                    raise STLStateError(func_name, 'disconnected')
1158
1159                try:
1160                    ret = f(*args, **kwargs)
1161                except KeyboardInterrupt as e:
1162                    raise STLError("Interrupted by a keyboard signal (probably ctrl + c)")
1163
1164                return ret
1165            return wrap2
1166
1167        return wrap
1168
1169
1170
1171    ############################     API     #############################
1172    ############################             #############################
1173    ############################             #############################
1174    def __enter__ (self):
1175        self.connect()
1176        self.acquire(force = True)
1177        self.reset()
1178        return self
1179
1180    def __exit__ (self, type, value, traceback):
1181        if self.get_active_ports():
1182            self.stop(self.get_active_ports())
1183        self.disconnect()
1184
1185    ############################   Getters   #############################
1186    ############################             #############################
1187    ############################             #############################
1188
1189
1190    # return verbose level of the logger
1191    def get_verbose (self):
1192        """
1193        Get the verbose mode
1194
1195        :parameters:
1196          none
1197
1198        :return:
1199            Get the verbose mode as Bool
1200
1201        :raises:
1202          None
1203
1204        """
1205        return self.logger.get_verbose()
1206
1207    # is the client on read only mode ?
1208    def is_all_ports_acquired (self):
1209        """
1210         is_all_ports_acquired
1211
1212        :parameters:
1213          None
1214
1215        :return:
1216            Returns True if all ports are acquired
1217
1218        :raises:
1219          None
1220
1221        """
1222
1223        return (self.get_all_ports() == self.get_acquired_ports())
1224
1225
1226    # is the client connected ?
1227    def is_connected (self):
1228        """
1229
1230        :parameters:
1231          None
1232
1233        :return:
1234            is_connected
1235
1236        :raises:
1237          None
1238
1239        """
1240
1241        return self.connected and self.comm_link.is_connected
1242
1243
1244    # get connection info
1245    def get_connection_info (self):
1246        """
1247
1248        :parameters:
1249          None
1250
1251        :return:
1252            Connection dict
1253
1254        :raises:
1255          None
1256
1257        """
1258
1259        return self.connection_info
1260
1261
1262    # get supported commands by the server
1263    def get_server_supported_cmds(self):
1264        """
1265
1266        :parameters:
1267          None
1268
1269        :return:
1270            Connection dict
1271
1272        :raises:
1273          None
1274
1275        """
1276
1277        return self.supported_cmds
1278
1279    # get server version
1280    def get_server_version(self):
1281        """
1282
1283        :parameters:
1284          None
1285
1286        :return:
1287            Connection dict
1288
1289        :raises:
1290          None
1291
1292        """
1293
1294        return self.server_version
1295
1296    # get server system info
1297    def get_server_system_info(self):
1298        """
1299
1300        :parameters:
1301          None
1302
1303        :return:
1304            Connection dict
1305
1306        :raises:
1307          None
1308
1309        """
1310
1311        return self.system_info
1312
1313    # get port count
1314    def get_port_count(self):
1315        """
1316
1317        :parameters:
1318          None
1319
1320        :return:
1321            Connection dict
1322
1323        :raises:
1324          None
1325
1326        """
1327
1328        return len(self.ports)
1329
1330
1331    # returns the port object
1332    def get_port (self, port_id):
1333        port = self.ports.get(port_id, None)
1334        if (port != None):
1335            return port
1336        else:
1337            raise STLArgumentError('port id', port_id, valid_values = self.get_all_ports())
1338
1339
1340    # get all ports as IDs
1341    def get_all_ports (self):
1342        """
1343
1344        :parameters:
1345          None
1346
1347        :return:
1348            Connection dict
1349
1350        :raises:
1351          None
1352
1353        """
1354
1355        return list(self.ports)
1356
1357    # get all acquired ports
1358    def get_acquired_ports(self):
1359        return [port_id
1360                for port_id, port_obj in self.ports.items()
1361                if port_obj.is_acquired()]
1362
1363    # get all active ports (TX or pause)
1364    def get_active_ports(self, owned = True):
1365        if owned:
1366            return [port_id
1367                    for port_id, port_obj in self.ports.items()
1368                    if port_obj.is_active() and port_obj.is_acquired()]
1369        else:
1370            return [port_id
1371                    for port_id, port_obj in self.ports.items()
1372                    if port_obj.is_active()]
1373
1374
1375    def get_resolvable_ports (self):
1376         return [port_id
1377                for port_id, port_obj in self.ports.items()
1378                if port_obj.is_acquired() and port_obj.get_dst_addr()['ipv4'] is not None]
1379
1380
1381    # get paused ports
1382    def get_paused_ports (self, owned = True):
1383        if owned:
1384            return [port_id
1385                    for port_id, port_obj in self.ports.items()
1386                    if port_obj.is_paused() and port_obj.is_acquired()]
1387        else:
1388            return [port_id
1389                    for port_id, port_obj in self.ports.items()
1390                    if port_obj.is_paused()]
1391
1392
1393    # get all TX ports
1394    def get_transmitting_ports (self, owned = True):
1395        if owned:
1396            return [port_id
1397                    for port_id, port_obj in self.ports.items()
1398                    if port_obj.is_transmitting() and port_obj.is_acquired()]
1399        else:
1400            return [port_id
1401                    for port_id, port_obj in self.ports.items()
1402                    if port_obj.is_transmitting()]
1403
1404
1405    # get stats
1406    def get_stats (self, ports = None, sync_now = True):
1407        """
1408        Return dictionary containing statistics information gathered from the server.
1409
1410        :parameters:
1411
1412          ports - List of ports to retreive stats on.
1413                  If None, assume the request is for all acquired ports.
1414
1415          sync_now - Boolean - If true, create a call to the server to get latest stats, and wait for result to arrive. Otherwise, return last stats saved in client cache.
1416                            Downside of putting True is a slight delay (few 10th msecs) in getting the result. For practical uses, value should be True.
1417        :return:
1418            Statistics dictionary of dictionaries with the following format:
1419
1420            ===============================  ===============
1421            key                               Meaning
1422            ===============================  ===============
1423            :ref:`numbers (0,1,..<total>`    Statistcs per port number
1424            :ref:`total <total>`             Sum of port statistics
1425            :ref:`flow_stats <flow_stats>`   Per flow statistics
1426            :ref:`global <global>`           Global statistics
1427            :ref:`latency <latency>`         Per flow statistics regarding flow latency
1428            ===============================  ===============
1429
1430            Below is description of each of the inner dictionaries.
1431
1432            .. _total:
1433
1434            **total** and per port statistics contain dictionary with following format.
1435
1436            Most of the bytes counters (unless specified otherwise) are in L2 layer, including the Ethernet FCS. e.g. minimum packet size is 64 bytes
1437
1438            ===============================  ===============
1439            key                               Meaning
1440            ===============================  ===============
1441            ibytes                           Number of input bytes
1442            ierrors                          Number of input errors
1443            ipackets                         Number of input packets
1444            obytes                           Number of output bytes
1445            oerrors                          Number of output errors
1446            opackets                         Number of output packets
1447            rx_bps                           Receive bytes per second rate (L2 layer)
1448            rx_pps                           Receive packet per second rate
1449            tx_bps                           Transmit bytes per second rate (L2 layer)
1450            tx_pps                           Transmit packet per second rate
1451            ===============================  ===============
1452
1453            .. _flow_stats:
1454
1455            **flow_stats** contains :ref:`global dictionary <flow_stats_global>`, and dictionaries per packet group id (pg id). See structures below.
1456
1457            **per pg_id flow stat** dictionaries have following structure:
1458
1459            =================   ===============
1460            key                 Meaning
1461            =================   ===============
1462            rx_bps              Received bytes per second rate
1463            rx_bps_l1           Received bytes per second rate, including layer one
1464            rx_bytes            Total number of received bytes
1465            rx_pkts             Total number of received packets
1466            rx_pps              Received packets per second
1467            tx_bps              Transmit bytes per second rate
1468            tx_bps_l1           Transmit bytes per second rate, including layer one
1469            tx_bytes            Total number of sent bytes
1470            tx_pkts             Total number of sent packets
1471            tx_pps              Transmit packets per second rate
1472            =================   ===============
1473
1474            .. _flow_stats_global:
1475
1476            **global flow stats** dictionary has the following structure:
1477
1478            =================   ===============
1479            key                 Meaning
1480            =================   ===============
1481            rx_err              Number of flow statistics packets received that we could not associate to any pg_id. This can happen if latency on the used setup is large. See :ref:`wait_on_traffic <wait_on_traffic>` rx_delay_ms parameter for details.
1482            tx_err              Number of flow statistics packets transmitted that we could not associate to any pg_id. This is never expected. If you see this different than 0, please report.
1483            =================   ===============
1484
1485            .. _global:
1486
1487            **global**
1488
1489            =================   ===============
1490            key                 Meaning
1491            =================   ===============
1492            bw_per_core         Estimated byte rate Trex can support per core. This is calculated by extrapolation of current rate and load on transmitting cores.
1493            cpu_util            Estimate of the average utilization percentage of the transimitting cores
1494            queue_full          Total number of packets transmitted while the NIC TX queue was full. The packets will be transmitted, eventually, but will create high CPU%due to polling the queue.  This usually indicates that the rate we trying to transmit is too high for this port.
1495            rx_cpu_util         Estimate of the utilization percentage of the core handling RX traffic. Too high value of this CPU utilization could cause drop of latency streams.
1496            rx_drop_bps         Received bytes per second drop rate
1497            rx_bps              Received bytes per second rate
1498            rx_pps              Received packets per second rate
1499            tx_bps              Transmit bytes per second rate
1500            tx_pps              Transmit packets per second rate
1501            =================   ===============
1502
1503            .. _latency:
1504
1505            **latency** contains :ref:`global dictionary <lat_stats_global>`, and dictionaries per packet group id (pg id). Each one with the following structure.
1506
1507            **per pg_id latency stat** dictionaries have following structure:
1508
1509            ===========================          ===============
1510            key                                  Meaning
1511            ===========================          ===============
1512            :ref:`err_cntrs<err-cntrs>`          Counters describing errors that occured with this pg id
1513            :ref:`latency<lat_inner>`            Information regarding packet latency
1514            ===========================          ===============
1515
1516            Following are the inner dictionaries of latency
1517
1518            .. _err-cntrs:
1519
1520            **err-cntrs**
1521
1522            =================   ===============
1523            key                 Meaning (see better explanation below the table)
1524            =================   ===============
1525            dropped             How many packets were dropped (estimation)
1526            dup                 How many packets were duplicated.
1527            out_of_order        How many packets we received out of order.
1528            seq_too_high        How many events of packet with sequence number too high we saw.
1529            seq_too_low         How many events of packet with sequence number too low we saw.
1530            =================   ===============
1531
1532            For calculating packet error events, we add sequence number to each packet's payload. We decide what went wrong only according to sequence number
1533            of last packet received and that of the previous packet. 'seq_too_low' and 'seq_too_high' count events we see. 'dup', 'out_of_order' and 'dropped'
1534            are heuristics we apply to try and understand what happened. They will be accurate in common error scenarios.
1535            We describe few scenarios below to help understand this.
1536
1537            Scenario 1: Received packet with seq num 10, and another one with seq num 10. We increment 'dup' and 'seq_too_low' by 1.
1538
1539            Scenario 2: Received pacekt with seq num 10 and then packet with seq num 15. We assume 4 packets were dropped, and increment 'dropped' by 4, and 'seq_too_high' by 1.
1540            We expect next packet to arrive with sequence number 16.
1541
1542            Scenario 2 continue: Received packet with seq num 11. We increment 'seq_too_low' by 1. We increment 'out_of_order' by 1. We *decrement* 'dropped' by 1.
1543            (We assume here that one of the packets we considered as dropped before, actually arrived out of order).
1544
1545
1546            .. _lat_inner:
1547
1548            **latency**
1549
1550            =================   ===============
1551            key                 Meaning
1552            =================   ===============
1553            average             Average latency over the stream lifetime (usec).Low pass filter is applied to the last window average.It is computed each sampling period by following formula: <average> = <prev average>/2 + <last sampling period average>/2
1554            histogram           Dictionary describing logarithmic distribution histogram of packet latencies. Keys in the dictionary represent range of latencies (in usec). Values are the total number of packets received in this latency range. For example, an entry {100:13} would mean that we saw 13 packets with latency in the range between 100 and 200 usec.
1555            jitter              Jitter of latency samples, computed as described in :rfc:`3550#appendix-A.8`
1556            last_max            Maximum latency measured between last two data reads from server (0.5 sec window).
1557            total_max           Maximum latency measured over the stream lifetime (in usec).
1558            total_min           Minimum latency measured over the stream lifetime (in usec).
1559            =================   ===============
1560
1561            .. _lat_stats_global:
1562
1563            **global latency stats** dictionary has the following structure:
1564
1565            =================   ===============
1566            key                 Meaning
1567            =================   ===============
1568            old_flow            Number of latency statistics packets received that we could not associate to any pg_id. This can happen if latency on the used setup is large. See :ref:`wait_on_traffic <wait_on_traffic>` rx_delay_ms parameter for details.
1569            bad_hdr             Number of latency packets received with bad latency data. This can happen becuase of garbage packets in the network, or if the DUT causes packet corruption.
1570            =================   ===============
1571
1572        :raises:
1573          None
1574
1575        """
1576        # by default use all acquired ports
1577        ports = ports if ports is not None else self.get_acquired_ports()
1578        ports = self._validate_port_list(ports)
1579
1580        # check async barrier
1581        if not type(sync_now) is bool:
1582            raise STLArgumentError('sync_now', sync_now)
1583
1584
1585        # if the user requested a barrier - use it
1586        if sync_now:
1587            rc = self.async_client.barrier()
1588            if not rc:
1589                raise STLError(rc)
1590
1591        return self.__get_stats(ports)
1592
1593
1594    def get_events (self, ev_type_filter = None):
1595        """
1596        returns all the logged events
1597
1598        :parameters:
1599          ev_type_filter - 'info', 'warning' or a list of those
1600                           default: no filter
1601
1602        :return:
1603            logged events
1604
1605        :raises:
1606          None
1607
1608        """
1609        return self.event_handler.get_events(ev_type_filter)
1610
1611
1612    def get_warnings (self):
1613        """
1614        returns all the warnings logged events
1615
1616        :parameters:
1617          None
1618
1619        :return:
1620            warning logged events
1621
1622        :raises:
1623          None
1624
1625        """
1626        return self.get_events(ev_type_filter = 'warning')
1627
1628
1629    def get_info (self):
1630        """
1631        returns all the info logged events
1632
1633        :parameters:
1634          None
1635
1636        :return:
1637            warning logged events
1638
1639        :raises:
1640          None
1641
1642        """
1643        return self.get_events(ev_type_filter = 'info')
1644
1645
1646    # get port(s) info as a list of dicts
1647    @__api_check(True)
1648    def get_port_info (self, ports = None):
1649
1650        ports = ports if ports is not None else self.get_all_ports()
1651        ports = self._validate_port_list(ports)
1652
1653        return [self.ports[port_id].get_info() for port_id in ports]
1654
1655
1656    ############################   Commands   #############################
1657    ############################              #############################
1658    ############################              #############################
1659
1660
1661    def set_verbose (self, level):
1662        """
1663            Sets verbose level
1664
1665            :parameters:
1666                level : str
1667                    "high"
1668                    "low"
1669                    "normal"
1670
1671            :raises:
1672                None
1673
1674        """
1675        modes = {'low' : LoggerApi.VERBOSE_QUIET, 'normal': LoggerApi.VERBOSE_REGULAR, 'high': LoggerApi.VERBOSE_HIGH}
1676
1677        if not level in modes.keys():
1678            raise STLArgumentError('level', level)
1679
1680        self.logger.set_verbose(modes[level])
1681
1682
1683    @__api_check(False)
1684    def connect (self):
1685        """
1686
1687            Connects to the TRex server
1688
1689            :parameters:
1690                None
1691
1692            :raises:
1693                + :exc:`STLError`
1694
1695        """
1696
1697        rc = self.__connect()
1698        if not rc:
1699            raise STLError(rc)
1700
1701
1702    @__api_check(False)
1703    def disconnect (self, stop_traffic = True, release_ports = True):
1704        """
1705            Disconnects from the server
1706
1707            :parameters:
1708                stop_traffic : bool
1709                    Attempts to stop traffic before disconnecting.
1710                release_ports : bool
1711                    Attempts to release all the acquired ports.
1712
1713        """
1714
1715        # try to stop ports but do nothing if not possible
1716        if stop_traffic:
1717            try:
1718                self.stop()
1719            except STLError:
1720                pass
1721
1722
1723        self.logger.pre_cmd("Disconnecting from server at '{0}':'{1}'".format(self.connection_info['server'],
1724                                                                              self.connection_info['sync_port']))
1725        rc = self.__disconnect(release_ports)
1726        self.logger.post_cmd(rc)
1727
1728
1729
1730    @__api_check(True)
1731    def acquire (self, ports = None, force = False, sync_streams = True):
1732        """
1733            Acquires ports for executing commands
1734
1735            :parameters:
1736                ports : list
1737                    Ports on which to execute the command
1738
1739                force : bool
1740                    Force acquire the ports.
1741
1742                sync_streams: bool
1743                    sync with the server about the configured streams
1744
1745            :raises:
1746                + :exc:`STLError`
1747
1748        """
1749
1750        # by default use all ports
1751        ports = ports if ports is not None else self.get_all_ports()
1752        ports = self._validate_port_list(ports)
1753
1754        if force:
1755            self.logger.pre_cmd("Force acquiring ports {0}:".format(ports))
1756        else:
1757            self.logger.pre_cmd("Acquiring ports {0}:".format(ports))
1758
1759        rc = self.__acquire(ports, force, sync_streams)
1760
1761        self.logger.post_cmd(rc)
1762
1763        if not rc:
1764            # cleanup
1765            self.__release(ports)
1766            raise STLError(rc)
1767
1768        for port_id in ports:
1769            if not self.ports[port_id].is_resolved():
1770                self.logger.log(format_text('*** Warning - Port {0} destination is unresolved ***'.format(port_id), 'bold'))
1771
1772    @__api_check(True)
1773    def release (self, ports = None):
1774        """
1775            Release ports
1776
1777            :parameters:
1778                ports : list
1779                    Ports on which to execute the command
1780
1781            :raises:
1782                + :exc:`STLError`
1783
1784        """
1785
1786        ports = ports if ports is not None else self.get_acquired_ports()
1787        ports = self._validate_port_list(ports)
1788
1789        self.logger.pre_cmd("Releasing ports {0}:".format(ports))
1790        rc = self.__release(ports)
1791        self.logger.post_cmd(rc)
1792
1793        if not rc:
1794            raise STLError(rc)
1795
1796    def test (self):
1797        self.resolve()
1798        return
1799
1800        #rc = self.ports[0].resolve()
1801        #if not rc:
1802        #    raise STLError(rc)
1803        #return
1804
1805        self.reset(ports = [0])
1806
1807        attr = self.ports[0].get_ts_attr()
1808        src_ipv4 = attr['src_ipv4']
1809        src_mac  = attr['src_mac']
1810        dest     = attr['dest']
1811        print(src_ipv4, src_mac, dest)
1812        #self.set_port_attr(ports = [0, 1], ipv4 = ['5.5.5.5', '6.6.6.6'])
1813        return
1814
1815        self.set_rx_queue(ports = [0], size = 1000, rxf = 'all')
1816
1817        #base_pkt = Ether()/ARP()/('x'*50)
1818        base_pkt = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(psrc = '1.1.1.2',pdst = '1.1.1.1', hwsrc = 'a0:36:9f:20:e6:ce')
1819        #base_pkt = Ether(dst="ff:ff:ff:ff:ff:ff")/ICMP()
1820
1821        print('Sending ARP request on port 0:\n')
1822        base_pkt.show2()
1823
1824        # send some traffic
1825        x = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) )
1826
1827        self.add_streams(streams = [x], ports = [0])
1828        self.start(ports = [0], mult = '100%')
1829        self.wait_on_traffic(ports = [0])
1830        time.sleep(1)
1831
1832        pkts = self.get_rx_queue_pkts(ports = [0])
1833
1834        print('got back on port 0:\n')
1835        for pkt in pkts[0]:
1836            Ether(pkt).show2()
1837
1838        self.remove_rx_queue(ports = [1])
1839        self.set_port_attr(ports = [1], rxf = 'hw')
1840        #for pkt in pkts[1]:
1841        #    Ether(pkt).show2()
1842
1843
1844    @__api_check(True)
1845    def ping(self):
1846        """
1847            Pings the server
1848
1849            :parameters:
1850                 none
1851
1852            :raises:
1853                + :exc:`STLError`
1854
1855        """
1856
1857        self.logger.pre_cmd("Pinging the server on '{0}' port '{1}': ".format(self.connection_info['server'],
1858                                                                              self.connection_info['sync_port']))
1859        rc = self._transmit("ping", api_class = None)
1860
1861        self.logger.post_cmd(rc)
1862
1863        if not rc:
1864            raise STLError(rc)
1865
1866
1867    @__api_check(True)
1868    def ip_ping (self, src_port, dst_ipv4, pkt_size = 64, count = 5):
1869        """
1870            Pings an IP address
1871
1872            :parameters:
1873                 src_port - on which port_id to send the ICMP PING request
1874                 dst_ipv4 - which IP to ping
1875                 pkt_size - packet size to use
1876                 count    - how many times to ping
1877            :raises:
1878                + :exc:`STLError`
1879
1880        """
1881        self._validate_port_list(src_port)
1882
1883        self.logger.pre_cmd("Pinging {0} from port {1} with {2} bytes of data:".format(dst_ipv4,
1884                                                                                       src_port,
1885                                                                                       pkt_size))
1886
1887        # no async messages
1888        with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC):
1889            self.logger.log('')
1890            for i in range(count):
1891                rc = self.ports[src_port].ping(ping_ipv4 = dst_ipv4, pkt_size = pkt_size, retries = 0)
1892                if rc:
1893                    self.logger.log(rc.data())
1894                else:
1895                    raise STLError(rc)
1896                if i != (count - 1):
1897                    time.sleep(1)
1898
1899
1900
1901    @__api_check(True)
1902    def server_shutdown (self, force = False):
1903        """
1904            Sends the server a request for total shutdown
1905
1906            :parameters:
1907                force - shutdown server even if some ports are owned by another
1908                        user
1909
1910            :raises:
1911                + :exc:`STLError`
1912
1913        """
1914
1915        self.logger.pre_cmd("Sending shutdown request for the server")
1916
1917        rc = self._transmit("shutdown", params = {'force': force, 'user': self.username})
1918
1919        self.logger.post_cmd(rc)
1920
1921        if not rc:
1922            raise STLError(rc)
1923
1924
1925    @__api_check(True)
1926    def get_active_pgids(self):
1927        """
1928            Get active group IDs
1929
1930            :parameters:
1931                None
1932
1933
1934            :raises:
1935                + :exc:`STLError`
1936
1937        """
1938
1939        self.logger.pre_cmd( "Getting active packet group ids")
1940
1941        rc = self._transmit("get_active_pgids")
1942
1943        self.logger.post_cmd(rc)
1944
1945        if not rc:
1946            raise STLError(rc)
1947
1948    @__api_check(True)
1949    def get_util_stats(self):
1950        """
1951            Get utilization stats:
1952            History of TRex CPU utilization per thread (list of lists)
1953            MBUFs memory consumption per CPU socket.
1954
1955            :parameters:
1956                None
1957
1958            :raises:
1959                + :exc:`STLError`
1960
1961        """
1962        self.logger.pre_cmd('Getting Utilization stats')
1963        return self.util_stats.get_stats()
1964
1965    @__api_check(True)
1966    def get_xstats(self, port_id):
1967        print(port_id)
1968        """
1969            Get extended stats of port: all the counters as dict.
1970
1971            :parameters:
1972                port_id: int
1973
1974            :returns:
1975                Dict with names of counters as keys and values of uint64. Actual keys may vary per NIC.
1976
1977            :raises:
1978                + :exc:`STLError`
1979
1980        """
1981        self.logger.pre_cmd('Getting xstats')
1982        return self.xstats.get_stats(port_id)
1983
1984
1985    @__api_check(True)
1986    def reset(self, ports = None, restart = False):
1987        """
1988            Force acquire ports, stop the traffic, remove all streams and clear stats
1989
1990            :parameters:
1991                ports : list
1992                   Ports on which to execute the command
1993
1994                restart: bool
1995                   Restart the NICs (link down / up)
1996
1997            :raises:
1998                + :exc:`STLError`
1999
2000        """
2001
2002
2003        ports = ports if ports is not None else self.get_all_ports()
2004        ports = self._validate_port_list(ports)
2005
2006        # force take the port and ignore any streams on it
2007        self.acquire(ports, force = True, sync_streams = False)
2008        self.stop(ports, rx_delay_ms = 0)
2009        self.remove_all_streams(ports)
2010        self.clear_stats(ports)
2011        self.set_port_attr(ports,
2012                           promiscuous = False,
2013                           link_up = True if restart else None,
2014                           rxf = 'hw')
2015        self.remove_rx_sniffer(ports)
2016        self.remove_rx_queue(ports)
2017
2018
2019
2020    @__api_check(True)
2021    def remove_all_streams (self, ports = None):
2022        """
2023            remove all streams from port(s)
2024
2025            :parameters:
2026                ports : list
2027                    Ports on which to execute the command
2028
2029
2030            :raises:
2031                + :exc:`STLError`
2032
2033        """
2034
2035
2036        ports = ports if ports is not None else self.get_acquired_ports()
2037        ports = self._validate_port_list(ports)
2038
2039        self.logger.pre_cmd("Removing all streams from port(s) {0}:".format(ports))
2040        rc = self.__remove_all_streams(ports)
2041        self.logger.post_cmd(rc)
2042
2043        if not rc:
2044            raise STLError(rc)
2045
2046
2047    @__api_check(True)
2048    def add_streams (self, streams, ports = None):
2049        """
2050            Add a list of streams to port(s)
2051
2052            :parameters:
2053                ports : list
2054                    Ports on which to execute the command
2055                streams: list
2056                    Streams to attach (or profile)
2057
2058            :returns:
2059                List of stream IDs in order of the stream list
2060
2061            :raises:
2062                + :exc:`STLError`
2063
2064        """
2065
2066
2067        ports = ports if ports is not None else self.get_acquired_ports()
2068        ports = self._validate_port_list(ports)
2069
2070        if isinstance(streams, STLProfile):
2071            streams = streams.get_streams()
2072
2073        # transform single stream
2074        if not isinstance(streams, list):
2075            streams = [streams]
2076
2077        # check streams
2078        if not all([isinstance(stream, STLStream) for stream in streams]):
2079            raise STLArgumentError('streams', streams)
2080
2081        self.logger.pre_cmd("Attaching {0} streams to port(s) {1}:".format(len(streams), ports))
2082        rc = self.__add_streams(streams, ports)
2083        self.logger.post_cmd(rc)
2084
2085        if not rc:
2086            raise STLError(rc)
2087
2088        # return the stream IDs
2089        return rc.data()
2090
2091    @__api_check(True)
2092    def add_profile(self, filename, ports = None, **kwargs):
2093        """ |  Add streams from profile by its type. Supported types are:
2094            |  .py
2095            |  .yaml
2096            |  .pcap file that converted to profile automatically
2097
2098            :parameters:
2099                filename : string
2100                    filename (with path) of the profile
2101                ports : list
2102                    list of ports to add the profile (default: all acquired)
2103                kwargs : dict
2104                    forward those key-value pairs to the profile (tunables)
2105
2106            :returns:
2107                List of stream IDs in order of the stream list
2108
2109            :raises:
2110                + :exc:`STLError`
2111
2112        """
2113
2114        validate_type('filename', filename, basestring)
2115        profile = STLProfile.load(filename, **kwargs)
2116        return self.add_streams(profile.get_streams(), ports)
2117
2118
2119    @__api_check(True)
2120    def remove_streams (self, stream_id_list, ports = None):
2121        """
2122            Remove a list of streams from ports
2123
2124            :parameters:
2125                ports : list
2126                    Ports on which to execute the command
2127                stream_id_list: list
2128                    Stream id list to remove
2129
2130
2131            :raises:
2132                + :exc:`STLError`
2133
2134        """
2135
2136
2137        ports = ports if ports is not None else self.get_acquired_ports()
2138        ports = self._validate_port_list(ports)
2139
2140        # transform single stream
2141        if not isinstance(stream_id_list, list):
2142            stream_id_list = [stream_id_list]
2143
2144        # check streams
2145        for stream_id in stream_id_list:
2146            validate_type('stream_id', stream_id, int)
2147
2148        # remove streams
2149        self.logger.pre_cmd("Removing {0} streams from port(s) {1}:".format(len(stream_id_list), ports))
2150        rc = self.__remove_streams(stream_id_list, ports)
2151        self.logger.post_cmd(rc)
2152
2153        if not rc:
2154            raise STLError(rc)
2155
2156
2157    # common checks for start API
2158    def __pre_start_check (self, ports, force):
2159
2160        # verify link status
2161        ports_link_down = [port_id for port_id in ports if not self.ports[port_id].is_up()]
2162        if not force and ports_link_down:
2163            raise STLError("Port(s) %s - link DOWN - check the connection or specify 'force'" % ports_link_down)
2164
2165        # verify ports are stopped or force stop them
2166        active_ports = list(set(self.get_active_ports()).intersection(ports))
2167        if active_ports and not force:
2168            raise STLError("Port(s) {0} are active - please stop them or specify 'force'".format(active_ports))
2169
2170        # warn if ports are not resolved
2171        unresolved_ports = [port_id for port_id in ports if not self.ports[port_id].is_resolved()]
2172        if unresolved_ports and not force:
2173            raise STLError("Port(s) {0} have unresolved destination addresses - please resolve them or specify 'force'".format(unresolved_ports))
2174
2175
2176    @__api_check(True)
2177    def start (self,
2178               ports = None,
2179               mult = "1",
2180               force = False,
2181               duration = -1,
2182               total = False,
2183               core_mask = CORE_MASK_SPLIT):
2184        """
2185            Start traffic on port(s)
2186
2187            :parameters:
2188                ports : list
2189                    Ports on which to execute the command
2190
2191                mult : str
2192                    Multiplier in a form of pps, bps, or line util in %
2193                    Examples: "5kpps", "10gbps", "85%", "32mbps"
2194
2195                force : bool
2196                    If the ports are not in stopped mode or do not have sufficient bandwidth for the traffic, determines whether to stop the current traffic and force start.
2197                    True: Force start
2198                    False: Do not force start
2199
2200                duration : int
2201                    Limit the run time (seconds)
2202                    -1 = unlimited
2203
2204                total : bool
2205                    Determines whether to divide the configured bandwidth among the ports, or to duplicate the bandwidth for each port.
2206                    True: Divide bandwidth among the ports
2207                    False: Duplicate
2208
2209                core_mask: CORE_MASK_SPLIT, CORE_MASK_PIN or a list of masks (one per port)
2210                    Determine the allocation of cores per port
2211                    In CORE_MASK_SPLIT all the traffic will be divided equally between all the cores
2212                    associated with each port
2213                    In CORE_MASK_PIN, for each dual ports (a group that shares the same cores)
2214                    the cores will be divided half pinned for each port
2215
2216            :raises:
2217                + :exc:`STLError`
2218
2219        """
2220
2221        ports = ports if ports is not None else self.get_acquired_ports()
2222        ports = self._validate_port_list(ports)
2223
2224        validate_type('mult', mult, basestring)
2225        validate_type('force', force, bool)
2226        validate_type('duration', duration, (int, float))
2227        validate_type('total', total, bool)
2228        validate_type('core_mask', core_mask, (int, list))
2229
2230
2231        self.__pre_start_check(ports, force)
2232
2233        #########################
2234        # decode core mask argument
2235        decoded_mask = self.__decode_core_mask(ports, core_mask)
2236        #######################
2237
2238        # verify multiplier
2239        mult_obj = parsing_opts.decode_multiplier(mult,
2240                                                  allow_update = False,
2241                                                  divide_count = len(ports) if total else 1)
2242        if not mult_obj:
2243            raise STLArgumentError('mult', mult)
2244
2245
2246        # stop active ports if needed
2247        active_ports = list(set(self.get_active_ports()).intersection(ports))
2248        if active_ports and force:
2249            self.stop(active_ports)
2250
2251
2252        # start traffic
2253        self.logger.pre_cmd("Starting traffic on port(s) {0}:".format(ports))
2254        rc = self.__start(mult_obj, duration, ports, force, decoded_mask)
2255        self.logger.post_cmd(rc)
2256
2257        if not rc:
2258            raise STLError(rc)
2259
2260
2261    @__api_check(True)
2262    def stop (self, ports = None, rx_delay_ms = 10):
2263        """
2264            Stop port(s)
2265
2266            :parameters:
2267                ports : list
2268                    Ports on which to execute the command
2269
2270                rx_delay_ms : int
2271                    time to wait until RX filters are removed
2272                    this value should reflect the time it takes
2273                    packets which were transmitted to arrive
2274                    to the destination.
2275                    after this time the RX filters will be removed
2276
2277            :raises:
2278                + :exc:`STLError`
2279
2280        """
2281
2282        if ports is None:
2283            ports = self.get_active_ports()
2284            if not ports:
2285                return
2286
2287        ports = self._validate_port_list(ports)
2288
2289        self.logger.pre_cmd("Stopping traffic on port(s) {0}:".format(ports))
2290        rc = self.__stop(ports)
2291        self.logger.post_cmd(rc)
2292
2293        if not rc:
2294            raise STLError(rc)
2295
2296        # remove any RX filters
2297        rc = self._remove_rx_filters(ports, rx_delay_ms = rx_delay_ms)
2298        if not rc:
2299            raise STLError(rc)
2300
2301
2302    @__api_check(True)
2303    def update (self, ports = None, mult = "1", total = False, force = False):
2304        """
2305            Update traffic on port(s)
2306
2307            :parameters:
2308                ports : list
2309                    Ports on which to execute the command
2310
2311                mult : str
2312                    Multiplier in a form of pps, bps, or line util in %
2313                    Can also specify +/-
2314                    Examples: "5kpps+", "10gbps-", "85%", "32mbps", "20%+"
2315
2316                force : bool
2317                    If the ports are not in stopped mode or do not have sufficient bandwidth for the traffic, determines whether to stop the current traffic and force start.
2318                    True: Force start
2319                    False: Do not force start
2320
2321                total : bool
2322                    Determines whether to divide the configured bandwidth among the ports, or to duplicate the bandwidth for each port.
2323                    True: Divide bandwidth among the ports
2324                    False: Duplicate
2325
2326
2327            :raises:
2328                + :exc:`STLError`
2329
2330        """
2331
2332
2333        ports = ports if ports is not None else self.get_active_ports()
2334        ports = self._validate_port_list(ports)
2335
2336        validate_type('mult', mult, basestring)
2337        validate_type('force', force, bool)
2338        validate_type('total', total, bool)
2339
2340        # verify multiplier
2341        mult_obj = parsing_opts.decode_multiplier(mult,
2342                                                  allow_update = True,
2343                                                  divide_count = len(ports) if total else 1)
2344        if not mult_obj:
2345            raise STLArgumentError('mult', mult)
2346
2347
2348        # call low level functions
2349        self.logger.pre_cmd("Updating traffic on port(s) {0}:".format(ports))
2350        rc = self.__update(mult_obj, ports, force)
2351        self.logger.post_cmd(rc)
2352
2353        if not rc:
2354            raise STLError(rc)
2355
2356
2357
2358    @__api_check(True)
2359    def pause (self, ports = None):
2360        """
2361            Pause traffic on port(s). Works only for ports that are active, and only if all streams are in Continuous mode.
2362
2363            :parameters:
2364                ports : list
2365                    Ports on which to execute the command
2366
2367            :raises:
2368                + :exc:`STLError`
2369
2370        """
2371
2372
2373        ports = ports if ports is not None else self.get_transmitting_ports()
2374        ports = self._validate_port_list(ports)
2375
2376        self.logger.pre_cmd("Pausing traffic on port(s) {0}:".format(ports))
2377        rc = self.__pause(ports)
2378        self.logger.post_cmd(rc)
2379
2380        if not rc:
2381            raise STLError(rc)
2382
2383    @__api_check(True)
2384    def resume (self, ports = None):
2385        """
2386            Resume traffic on port(s)
2387
2388            :parameters:
2389                ports : list
2390                    Ports on which to execute the command
2391
2392            :raises:
2393                + :exc:`STLError`
2394
2395        """
2396
2397
2398        ports = ports if ports is not None else self.get_paused_ports()
2399        ports = self._validate_port_list(ports)
2400
2401
2402        self.logger.pre_cmd("Resume traffic on port(s) {0}:".format(ports))
2403        rc = self.__resume(ports)
2404        self.logger.post_cmd(rc)
2405
2406        if not rc:
2407            raise STLError(rc)
2408
2409
2410    @__api_check(True)
2411    def push_remote (self,
2412                     pcap_filename,
2413                     ports = None,
2414                     ipg_usec = None,
2415                     speedup = 1.0,
2416                     count = 1,
2417                     duration = -1,
2418                     is_dual = False):
2419        """
2420            Push a remote server-reachable PCAP file
2421            the path must be fullpath accessible to the server
2422
2423            :parameters:
2424                pcap_filename : str
2425                    PCAP file name in full path and accessible to the server
2426
2427                ports : list
2428                    Ports on which to execute the command
2429
2430                ipg_usec : float
2431                    Inter-packet gap in microseconds
2432
2433                speedup : float
2434                    A factor to adjust IPG. effectively IPG = IPG / speedup
2435
2436                count: int
2437                    How many times to transmit the cap
2438
2439                duration: float
2440                    Limit runtime by duration in seconds
2441
2442                is_dual: bool
2443                    Inject from both directions.
2444                    requires ERF file with meta data for direction.
2445                    also requires that all the ports will be in master mode
2446                    with their adjacent ports as slaves
2447
2448            :raises:
2449                + :exc:`STLError`
2450
2451        """
2452        ports = ports if ports is not None else self.get_acquired_ports()
2453        ports = self._validate_port_list(ports)
2454
2455        validate_type('pcap_filename', pcap_filename, basestring)
2456        validate_type('ipg_usec', ipg_usec, (float, int, type(None)))
2457        validate_type('speedup',  speedup, (float, int))
2458        validate_type('count',  count, int)
2459        validate_type('duration', duration, (float, int))
2460        validate_type('is_dual', is_dual, bool)
2461
2462        # for dual mode check that all are masters
2463        if is_dual:
2464            if not pcap_filename.endswith('erf'):
2465                raise STLError("dual mode: only ERF format is supported for dual mode")
2466
2467            for port in ports:
2468                master = port
2469                slave = port ^ 0x1
2470
2471                if slave in ports:
2472                    raise STLError("dual mode: cannot provide adjacent ports ({0}, {1}) in a batch".format(master, slave))
2473
2474                if not slave in self.get_acquired_ports():
2475                    raise STLError("dual mode: adjacent port {0} must be owned during dual mode".format(slave))
2476
2477
2478        self.logger.pre_cmd("Pushing remote PCAP on port(s) {0}:".format(ports))
2479        rc = self.__push_remote(pcap_filename, ports, ipg_usec, speedup, count, duration, is_dual)
2480        self.logger.post_cmd(rc)
2481
2482        if not rc:
2483            raise STLError(rc)
2484
2485
2486    @__api_check(True)
2487    def push_pcap (self,
2488                   pcap_filename,
2489                   ports = None,
2490                   ipg_usec = None,
2491                   speedup = 1.0,
2492                   count = 1,
2493                   duration = -1,
2494                   force = False,
2495                   vm = None,
2496                   packet_hook = None,
2497                   is_dual = False):
2498        """
2499            Push a local PCAP to the server
2500            This is equivalent to loading a PCAP file to a profile
2501            and attaching the profile to port(s)
2502
2503            file size is limited to 1MB
2504
2505            :parameters:
2506                pcap_filename : str
2507                    PCAP filename (accessible locally)
2508
2509                ports : list
2510                    Ports on which to execute the command
2511
2512                ipg_usec : float
2513                    Inter-packet gap in microseconds
2514
2515                speedup : float
2516                    A factor to adjust IPG. effectively IPG = IPG / speedup
2517
2518                count: int
2519                    How many times to transmit the cap
2520
2521                duration: float
2522                    Limit runtime by duration in seconds
2523
2524                force: bool
2525                    Ignore file size limit - push any file size to the server
2526
2527                vm: list of VM instructions
2528                    VM instructions to apply for every packet
2529
2530                packet_hook : Callable or function
2531                    Will be applied to every packet
2532
2533                is_dual: bool
2534                    Inject from both directions.
2535                    requires ERF file with meta data for direction.
2536                    also requires that all the ports will be in master mode
2537                    with their adjacent ports as slaves
2538
2539            :raises:
2540                + :exc:`STLError`
2541
2542        """
2543        ports = ports if ports is not None else self.get_acquired_ports()
2544        ports = self._validate_port_list(ports)
2545
2546        validate_type('pcap_filename', pcap_filename, basestring)
2547        validate_type('ipg_usec', ipg_usec, (float, int, type(None)))
2548        validate_type('speedup',  speedup, (float, int))
2549        validate_type('count',  count, int)
2550        validate_type('duration', duration, (float, int))
2551        validate_type('vm', vm, (list, type(None)))
2552        validate_type('is_dual', is_dual, bool)
2553
2554
2555        # no support for > 1MB PCAP - use push remote
2556        if not force and os.path.getsize(pcap_filename) > (1024 * 1024):
2557            raise STLError("PCAP size of {:} is too big for local push - consider using remote push or provide 'force'".format(format_num(os.path.getsize(pcap_filename), suffix = 'B')))
2558
2559        if is_dual:
2560            for port in ports:
2561                master = port
2562                slave = port ^ 0x1
2563
2564                if slave in ports:
2565                    raise STLError("dual mode: please specify only one of adjacent ports ({0}, {1}) in a batch".format(master, slave))
2566
2567                if not slave in self.get_acquired_ports():
2568                    raise STLError("dual mode: adjacent port {0} must be owned during dual mode".format(slave))
2569
2570        # regular push
2571        if not is_dual:
2572
2573            # create the profile from the PCAP
2574            try:
2575                self.logger.pre_cmd("Converting '{0}' to streams:".format(pcap_filename))
2576                profile = STLProfile.load_pcap(pcap_filename,
2577                                               ipg_usec,
2578                                               speedup,
2579                                               count,
2580                                               vm = vm,
2581                                               packet_hook = packet_hook)
2582                self.logger.post_cmd(RC_OK)
2583            except STLError as e:
2584                self.logger.post_cmd(RC_ERR(e))
2585                raise
2586
2587
2588            self.remove_all_streams(ports = ports)
2589            id_list = self.add_streams(profile.get_streams(), ports)
2590
2591            return self.start(ports = ports, duration = duration)
2592
2593        else:
2594
2595            # create a dual profile
2596            split_mode = 'MAC'
2597
2598            try:
2599                self.logger.pre_cmd("Analyzing '{0}' for dual ports based on {1}:".format(pcap_filename, split_mode))
2600                profile_a, profile_b = STLProfile.load_pcap(pcap_filename,
2601                                                            ipg_usec,
2602                                                            speedup,
2603                                                            count,
2604                                                            vm = vm,
2605                                                            packet_hook = packet_hook,
2606                                                            split_mode = split_mode)
2607
2608                self.logger.post_cmd(RC_OK())
2609
2610            except STLError as e:
2611                self.logger.post_cmd(RC_ERR(e))
2612                raise
2613
2614            all_ports = ports + [p ^ 0x1 for p in ports if profile_b]
2615
2616            self.remove_all_streams(ports = all_ports)
2617
2618            for port in ports:
2619                master = port
2620                slave = port ^ 0x1
2621
2622                self.add_streams(profile_a.get_streams(), master)
2623                if profile_b:
2624                    self.add_streams(profile_b.get_streams(), slave)
2625
2626            return self.start(ports = all_ports, duration = duration)
2627
2628
2629
2630
2631
2632    @__api_check(True)
2633    def validate (self, ports = None, mult = "1", duration = -1, total = False):
2634        """
2635            Validate port(s) configuration
2636
2637            :parameters:
2638                ports : list
2639                    Ports on which to execute the command
2640
2641             mult : str
2642                    Multiplier in a form of pps, bps, or line util in %
2643                    Examples: "5kpps", "10gbps", "85%", "32mbps"
2644
2645            duration : int
2646                    Limit the run time (seconds)
2647                    -1 = unlimited
2648
2649            total : bool
2650                    Determines whether to divide the configured bandwidth among the ports, or to duplicate the bandwidth for each port.
2651                    True: Divide bandwidth among the ports
2652                    False: Duplicate
2653
2654            :raises:
2655                + :exc:`STLError`
2656
2657        """
2658
2659
2660        ports = ports if ports is not None else self.get_acquired_ports()
2661        ports = self._validate_port_list(ports)
2662
2663        validate_type('mult', mult, basestring)
2664        validate_type('duration', duration, (int, float))
2665        validate_type('total', total, bool)
2666
2667
2668        # verify multiplier
2669        mult_obj = parsing_opts.decode_multiplier(mult,
2670                                                  allow_update = True,
2671                                                  divide_count = len(ports) if total else 1)
2672        if not mult_obj:
2673            raise STLArgumentError('mult', mult)
2674
2675        self.logger.pre_cmd("Validating streams on port(s) {0}:".format(ports))
2676        rc = self.__validate(ports)
2677        self.logger.post_cmd(rc)
2678
2679        if not rc:
2680            raise STLError(rc)
2681
2682        for port in ports:
2683            self.ports[port].print_profile(mult_obj, duration)
2684
2685
2686    @__api_check(False)
2687    def clear_stats (self, ports = None, clear_global = True, clear_flow_stats = True, clear_latency_stats = True, clear_xstats = True):
2688        """
2689            Clear stats on port(s)
2690
2691            :parameters:
2692                ports : list
2693                    Ports on which to execute the command
2694
2695                clear_global : bool
2696                    Clear the global stats
2697
2698                clear_flow_stats : bool
2699                    Clear the flow stats
2700
2701                clear_latency_stats : bool
2702                    Clear the latency stats
2703
2704                clear_xstats : bool
2705                    Clear the extended stats
2706
2707            :raises:
2708                + :exc:`STLError`
2709
2710        """
2711
2712        ports = ports if ports is not None else self.get_all_ports()
2713        ports = self._validate_port_list(ports)
2714
2715        # verify clear global
2716        if not type(clear_global) is bool:
2717            raise STLArgumentError('clear_global', clear_global)
2718
2719        rc = self.__clear_stats(ports, clear_global, clear_flow_stats, clear_latency_stats, clear_xstats)
2720        if not rc:
2721            raise STLError(rc)
2722
2723
2724
2725    @__api_check(True)
2726    def is_traffic_active (self, ports = None):
2727        """
2728            Return if specified port(s) have traffic
2729
2730            :parameters:
2731                ports : list
2732                    Ports on which to execute the command
2733
2734
2735            :raises:
2736                + :exc:`STLTimeoutError` - in case timeout has expired
2737                + :exe:'STLError'
2738
2739        """
2740
2741        ports = ports if ports is not None else self.get_acquired_ports()
2742        ports = self._validate_port_list(ports)
2743
2744        return set(self.get_active_ports()).intersection(ports)
2745
2746
2747
2748    @__api_check(True)
2749    def wait_on_traffic (self, ports = None, timeout = None, rx_delay_ms = 10):
2750        """
2751            .. _wait_on_traffic:
2752
2753            Block until traffic on specified port(s) has ended
2754
2755            :parameters:
2756                ports : list
2757                    Ports on which to execute the command
2758
2759                timeout : int
2760                    timeout in seconds
2761                    default will be blocking
2762
2763                rx_delay_ms : int
2764                    Time to wait (in milliseconds) after last packet was sent, until RX filters used for
2765                    measuring flow statistics and latency are removed.
2766                    This value should reflect the time it takes packets which were transmitted to arrive
2767                    to the destination.
2768                    After this time, RX filters will be removed, and packets arriving for per flow statistics feature and latency flows will be counted as errors.
2769
2770            :raises:
2771                + :exc:`STLTimeoutError` - in case timeout has expired
2772                + :exe:'STLError'
2773
2774        """
2775
2776        ports = ports if ports is not None else self.get_acquired_ports()
2777        ports = self._validate_port_list(ports)
2778
2779
2780        timer = PassiveTimer(timeout)
2781
2782        # wait while any of the required ports are active
2783        while set(self.get_active_ports()).intersection(ports):
2784
2785            # make sure ASYNC thread is still alive - otherwise we will be stuck forever
2786            if not self.async_client.is_active():
2787                raise STLError("subscriber thread is dead")
2788
2789            time.sleep(0.01)
2790            if timer.has_expired():
2791                raise STLTimeoutError(timeout)
2792
2793        # remove any RX filters
2794        rc = self._remove_rx_filters(ports, rx_delay_ms = rx_delay_ms)
2795        if not rc:
2796            raise STLError(rc)
2797
2798
2799    @__api_check(True)
2800    def set_port_attr (self,
2801                       ports = None,
2802                       promiscuous = None,
2803                       link_up = None,
2804                       led_on = None,
2805                       flow_ctrl = None,
2806                       rxf = None,
2807                       ipv4 = None,
2808                       dest = None,
2809                       resolve = True):
2810        """
2811            Set port attributes
2812
2813            :parameters:
2814                promiscuous      - True or False
2815                link_up          - True or False
2816                led_on           - True or False
2817                flow_ctrl        - 0: disable all, 1: enable tx side, 2: enable rx side, 3: full enable
2818                rxf              - 'hw' for hardware rules matching packets only or 'all' all packets
2819                ipv4             - configure IPv4 address for port(s). for multiple ports should be a list
2820                                   of IPv4 addresses in the same length of the ports array
2821                dest             - configure destination address for port(s) in either IPv4 or MAC format.
2822                                   for multiple ports should be a list in the same length of the ports array
2823                resolve          - if true, in case a destination address is configured as IPv4 try to resolve it
2824            :raises:
2825                + :exe:'STLError'
2826
2827        """
2828
2829        ports = ports if ports is not None else self.get_acquired_ports()
2830        ports = self._validate_port_list(ports)
2831
2832        # check arguments
2833        validate_type('promiscuous', promiscuous, (bool, type(None)))
2834        validate_type('link_up', link_up, (bool, type(None)))
2835        validate_type('led_on', led_on, (bool, type(None)))
2836        validate_type('flow_ctrl', flow_ctrl, (int, type(None)))
2837        validate_choice('rxf', rxf, ['hw', 'all'])
2838
2839        # common attributes for all ports
2840        cmn_attr_dict = {}
2841
2842        cmn_attr_dict['promiscuous']     = promiscuous
2843        cmn_attr_dict['link_status']     = link_up
2844        cmn_attr_dict['led_status']      = led_on
2845        cmn_attr_dict['flow_ctrl_mode']  = flow_ctrl
2846        cmn_attr_dict['rx_filter_mode']  = rxf
2847
2848        # each port starts with a set of the common attributes
2849        attr_dict = [dict(cmn_attr_dict) for _ in ports]
2850
2851        # default value for IPv4 / dest is none for all ports
2852        if ipv4 is None:
2853            ipv4 = [None] * len(ports)
2854        if dest is None:
2855            dest = [None] * len(ports)
2856
2857        ipv4 = listify(ipv4)
2858        if len(ipv4) != len(ports):
2859            raise STLError("'ipv4' must be a list in the same length of ports - 'ports': {0}, 'ip': {1}".format(ports, ipv4))
2860
2861        dest = listify(dest)
2862        if len(dest) != len(ports):
2863            raise STLError("'dest' must be a list in the same length of ports - 'ports': {0}, 'dest': {1}".format(ports, dest))
2864
2865        # update each port attribute with ipv4
2866        for addr, port_attr in zip(ipv4, attr_dict):
2867            port_attr['ipv4'] = addr
2868
2869        # update each port attribute with dest
2870        for addr, port_attr in zip(dest, attr_dict):
2871            port_attr['dest'] = addr
2872
2873
2874        self.logger.pre_cmd("Applying attributes on port(s) {0}:".format(ports))
2875        rc = self.__set_port_attr(ports, attr_dict)
2876        self.logger.post_cmd(rc)
2877
2878        if not rc:
2879            raise STLError(rc)
2880
2881
2882        # automatic resolve
2883        if resolve:
2884            # find any port with a dest configured as IPv4
2885            resolve_ports = [port_id for port_id, port_dest in zip(ports, dest) if is_valid_ipv4(port_dest)]
2886
2887            if resolve_ports:
2888                self.resolve(ports = resolve_ports)
2889
2890
2891
2892
2893    @__api_check(True)
2894    def resolve (self, ports = None, retries = 0):
2895        """
2896            Resolves ports (ARP resolution)
2897
2898            :parameters:
2899                ports          - for which ports to apply a unique sniffer (each port gets a unique file)
2900                retires        - how many times to retry on each port (intervals of 100 milliseconds)
2901            :raises:
2902                + :exe:'STLError'
2903
2904        """
2905        # by default - resolve all the ports that are configured with IPv4 dest
2906        if ports is None:
2907            ports = self.get_resolvable_ports()
2908            if not ports:
2909                raise STLError('No ports configured with destination as IPv4')
2910
2911        active_ports = list(set(self.get_active_ports()).intersection(ports))
2912        if active_ports:
2913            raise STLError('Port(s) {0} are active, please stop them before resolving'.format(active_ports))
2914
2915        ports = self._validate_port_list(ports)
2916
2917        self.logger.pre_cmd("Resolving destination on port(s) {0}:".format(ports))
2918        with self.logger.supress():
2919            rc = self.__resolve(ports, retries)
2920
2921        self.logger.post_cmd(rc)
2922
2923        if rc:
2924            self.logger.log(rc)
2925
2926        if not rc:
2927            raise STLError(rc)
2928
2929
2930
2931
2932    @__api_check(True)
2933    def set_rx_sniffer (self, ports = None, base_filename = 'rx_capture', limit = 1000, rxf = None):
2934        """
2935            Sets RX sniffer for port(s) written to a PCAP file
2936
2937            :parameters:
2938                ports          - for which ports to apply a unique sniffer (each port gets a unique file)
2939                base_filename  - filename will be appended with '-<port_number>'
2940                limit          - limit how many packets will be written
2941                rxf            - RX filter mode to use: 'hw' or 'all'
2942            :raises:
2943                + :exe:'STLError'
2944
2945        """
2946        ports = ports if ports is not None else self.get_acquired_ports()
2947        ports = self._validate_port_list(ports)
2948
2949        # check arguments
2950        validate_type('base_filename', base_filename, basestring)
2951        validate_type('limit', limit, (int))
2952        if limit <= 0:
2953            raise STLError("'limit' must be a positive value")
2954
2955        # change RX filter mode if asked
2956        if rxf:
2957            self.set_port_attr(ports, rxf = rxf)
2958
2959        self.logger.pre_cmd("Setting RX sniffers on port(s) {0}:".format(ports))
2960        rc = self.__set_rx_sniffer(ports, base_filename, limit)
2961        self.logger.post_cmd(rc)
2962
2963
2964        if not rc:
2965            raise STLError(rc)
2966
2967
2968
2969    @__api_check(True)
2970    def remove_rx_sniffer (self, ports = None):
2971        """
2972            Removes RX sniffer from port(s)
2973
2974            :raises:
2975                + :exe:'STLError'
2976
2977        """
2978        ports = ports if ports is not None else self.get_acquired_ports()
2979        ports = self._validate_port_list(ports)
2980
2981        self.logger.pre_cmd("Removing RX sniffers on port(s) {0}:".format(ports))
2982        rc = self.__remove_rx_sniffer(ports)
2983        self.logger.post_cmd(rc)
2984
2985        if not rc:
2986            raise STLError(rc)
2987
2988
2989    @__api_check(True)
2990    def set_rx_queue (self, ports = None, size = 1000, rxf = None):
2991        """
2992            Sets RX queue for port(s)
2993            The queue is cyclic and will hold last 'size' packets
2994
2995            :parameters:
2996                ports          - for which ports to apply a queue
2997                size           - size of the queue
2998                rxf            - which RX filter to use on those ports: 'hw' or 'all'
2999            :raises:
3000                + :exe:'STLError'
3001
3002        """
3003        ports = ports if ports is not None else self.get_acquired_ports()
3004        ports = self._validate_port_list(ports)
3005
3006        # check arguments
3007        validate_type('size', size, (int))
3008        if size <= 0:
3009            raise STLError("'size' must be a positive value")
3010
3011        # change RX filter mode if asked
3012        if rxf:
3013            self.set_port_attr(ports, rxf = rxf)
3014
3015        self.logger.pre_cmd("Setting RX queue on port(s) {0}:".format(ports))
3016        rc = self.__set_rx_queue(ports, size)
3017        self.logger.post_cmd(rc)
3018
3019
3020        if not rc:
3021            raise STLError(rc)
3022
3023
3024
3025    @__api_check(True)
3026    def remove_rx_queue (self, ports = None):
3027        """
3028            Removes RX queue from port(s)
3029
3030            :parameters:
3031                ports          - for which ports to remove the RX queue
3032            :raises:
3033                + :exe:'STLError'
3034
3035        """
3036        ports = ports if ports is not None else self.get_acquired_ports()
3037        ports = self._validate_port_list(ports)
3038
3039        self.logger.pre_cmd("Removing RX queue on port(s) {0}:".format(ports))
3040        rc = self.__remove_rx_queue(ports)
3041        self.logger.post_cmd(rc)
3042
3043        if not rc:
3044            raise STLError(rc)
3045
3046
3047
3048    @__api_check(True)
3049    def get_rx_queue_pkts (self, ports = None):
3050        """
3051            Returns any packets queued on the RX side by the server
3052            return value is a dictonary per port
3053
3054            :parameters:
3055                ports          - for which ports to fetch
3056        """
3057
3058        ports = ports if ports is not None else self.get_acquired_ports()
3059        ports = self._validate_port_list(ports)
3060
3061        result = {}
3062        for port in ports:
3063            result[port] = self.ports[port].get_rx_queue_pkts()
3064
3065        return result
3066
3067
3068    def clear_events (self):
3069        """
3070            Clear all events
3071
3072            :parameters:
3073                None
3074
3075            :raises:
3076                None
3077
3078        """
3079        self.event_handler.clear_events()
3080
3081
3082    ############################   Line       #############################
3083    ############################   Commands   #############################
3084    ############################              #############################
3085
3086    # console decorator
3087    def __console(f):
3088        @wraps(f)
3089        def wrap(*args):
3090            client = args[0]
3091
3092            time1 = time.time()
3093
3094            try:
3095                rc = f(*args)
3096            except STLError as e:
3097                client.logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold'))
3098                return RC_ERR(e.brief())
3099
3100            # if got true - print time
3101            if rc:
3102                delta = time.time() - time1
3103                client.logger.log(format_time(delta) + "\n")
3104
3105            return rc
3106
3107        return wrap
3108
3109    @__console
3110    def ping_line (self, line):
3111        '''pings the server / specific IP'''
3112
3113        # no parameters - so ping server
3114        if not line:
3115            self.ping()
3116            return True
3117
3118        parser = parsing_opts.gen_parser(self,
3119                                         "ping",
3120                                         self.ping_line.__doc__,
3121                                         parsing_opts.SOURCE_PORT,
3122                                         parsing_opts.PING_IPV4,
3123                                         parsing_opts.PKT_SIZE,
3124                                         parsing_opts.PING_COUNT)
3125
3126        opts = parser.parse_args(line.split())
3127        if not opts:
3128            return opts
3129
3130        # IP ping
3131        self.ip_ping(opts.source_port, opts.ping_ipv4, opts.pkt_size, opts.count)
3132
3133
3134    @__console
3135    def shutdown_line (self, line):
3136        '''shutdown the server'''
3137        parser = parsing_opts.gen_parser(self,
3138                                         "shutdown",
3139                                         self.shutdown_line.__doc__,
3140                                         parsing_opts.FORCE)
3141
3142        opts = parser.parse_args(line.split())
3143        if not opts:
3144            return opts
3145
3146        self.server_shutdown(force = opts.force)
3147        return RC_OK()
3148
3149    @__console
3150    def connect_line (self, line):
3151        '''Connects to the TRex server and acquire ports'''
3152        parser = parsing_opts.gen_parser(self,
3153                                         "connect",
3154                                         self.connect_line.__doc__,
3155                                         parsing_opts.PORT_LIST_WITH_ALL,
3156                                         parsing_opts.FORCE)
3157
3158        opts = parser.parse_args(line.split(), default_ports = self.get_all_ports())
3159        if not opts:
3160            return opts
3161
3162        self.connect()
3163        self.acquire(ports = opts.ports, force = opts.force)
3164
3165        return RC_OK()
3166
3167
3168    @__console
3169    def acquire_line (self, line):
3170        '''Acquire ports\n'''
3171
3172        # define a parser
3173        parser = parsing_opts.gen_parser(self,
3174                                         "acquire",
3175                                         self.acquire_line.__doc__,
3176                                         parsing_opts.PORT_LIST_WITH_ALL,
3177                                         parsing_opts.FORCE)
3178
3179        opts = parser.parse_args(line.split(), default_ports = self.get_all_ports())
3180        if not opts:
3181            return opts
3182
3183        # filter out all the already owned ports
3184        ports = list_difference(opts.ports, self.get_acquired_ports())
3185        if not ports:
3186            msg = "acquire - all of port(s) {0} are already acquired".format(opts.ports)
3187            self.logger.log(format_text(msg, 'bold'))
3188            return RC_ERR(msg)
3189
3190        self.acquire(ports = ports, force = opts.force)
3191
3192        return RC_OK()
3193
3194
3195    #
3196    @__console
3197    def release_line (self, line):
3198        '''Release ports\n'''
3199
3200        parser = parsing_opts.gen_parser(self,
3201                                         "release",
3202                                         self.release_line.__doc__,
3203                                         parsing_opts.PORT_LIST_WITH_ALL)
3204
3205        opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports())
3206        if not opts:
3207            return opts
3208
3209        ports = list_intersect(opts.ports, self.get_acquired_ports())
3210        if not ports:
3211            if not opts.ports:
3212                msg = "release - no acquired ports"
3213                self.logger.log(format_text(msg, 'bold'))
3214                return RC_ERR(msg)
3215            else:
3216                msg = "release - none of port(s) {0} are acquired".format(opts.ports)
3217                self.logger.log(format_text(msg, 'bold'))
3218                return RC_ERR(msg)
3219
3220
3221        self.release(ports = ports)
3222
3223        return RC_OK()
3224
3225
3226    @__console
3227    def reacquire_line (self, line):
3228        '''reacquire all the ports under your username which are not acquired by your session'''
3229
3230        parser = parsing_opts.gen_parser(self,
3231                                         "reacquire",
3232                                         self.reacquire_line.__doc__)
3233
3234        opts = parser.parse_args(line.split())
3235        if not opts:
3236            return opts
3237
3238        # find all the on-owned ports under your name
3239        my_unowned_ports = list_difference([k for k, v in self.ports.items() if v.get_owner() == self.username], self.get_acquired_ports())
3240        if not my_unowned_ports:
3241            msg = "reacquire - no unowned ports under '{0}'".format(self.username)
3242            self.logger.log(msg)
3243            return RC_ERR(msg)
3244
3245        self.acquire(ports = my_unowned_ports, force = True)
3246        return RC_OK()
3247
3248
3249    @__console
3250    def disconnect_line (self, line):
3251        self.disconnect()
3252
3253
3254    @__console
3255    def reset_line (self, line):
3256        '''Reset ports - if no ports are provided all acquired ports will be reset'''
3257
3258        parser = parsing_opts.gen_parser(self,
3259                                         "reset",
3260                                         self.reset_line.__doc__,
3261                                         parsing_opts.PORT_LIST_WITH_ALL,
3262                                         parsing_opts.PORT_RESTART)
3263
3264        opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True)
3265        if not opts:
3266            return opts
3267
3268        self.reset(ports = opts.ports, restart = opts.restart)
3269
3270        return RC_OK()
3271
3272
3273
3274    @__console
3275    def start_line (self, line):
3276        '''Start selected traffic on specified ports on TRex\n'''
3277        # define a parser
3278        parser = parsing_opts.gen_parser(self,
3279                                         "start",
3280                                         self.start_line.__doc__,
3281                                         parsing_opts.PORT_LIST_WITH_ALL,
3282                                         parsing_opts.TOTAL,
3283                                         parsing_opts.FORCE,
3284                                         parsing_opts.FILE_PATH,
3285                                         parsing_opts.DURATION,
3286                                         parsing_opts.TUNABLES,
3287                                         parsing_opts.MULTIPLIER_STRICT,
3288                                         parsing_opts.DRY_RUN,
3289                                         parsing_opts.CORE_MASK_GROUP)
3290
3291        opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True)
3292        if not opts:
3293            return opts
3294
3295        # core mask
3296        if opts.core_mask is not None:
3297            core_mask =  opts.core_mask
3298        else:
3299            core_mask = self.CORE_MASK_PIN if opts.pin_cores else self.CORE_MASK_SPLIT
3300
3301        # just for sanity - will be checked on the API as well
3302        self.__decode_core_mask(opts.ports, core_mask)
3303
3304        # for better use experience - check this first
3305        try:
3306            self.__pre_start_check(opts.ports, opts.force)
3307        except STLError as e:
3308            msg = e.brief()
3309            self.logger.log(format_text(msg, 'bold'))
3310            return RC_ERR(msg)
3311
3312
3313        # stop ports if needed
3314        active_ports = list_intersect(self.get_active_ports(), opts.ports)
3315        if active_ports and opts.force:
3316            self.stop(active_ports)
3317
3318
3319        # process tunables
3320        if type(opts.tunables) is dict:
3321            tunables = opts.tunables
3322        else:
3323            tunables = {}
3324
3325
3326        # remove all streams
3327        self.remove_all_streams(opts.ports)
3328
3329        # pack the profile
3330        try:
3331            for port in opts.ports:
3332
3333                profile = STLProfile.load(opts.file[0],
3334                                          direction = tunables.get('direction', port % 2),
3335                                          port_id = port,
3336                                          **tunables)
3337
3338                self.add_streams(profile.get_streams(), ports = port)
3339
3340        except STLError as e:
3341            error = 'Unknown error.'
3342            for line in e.brief().split('\n'):
3343                if line:
3344                    error = line
3345            msg = format_text("\nError loading profile '{0}'".format(opts.file[0]), 'bold')
3346            self.logger.log(msg + '\n')
3347            self.logger.log(e.brief() + "\n")
3348            return RC_ERR("%s: %s" % (msg, error))
3349
3350
3351        if opts.dry:
3352            self.validate(opts.ports, opts.mult, opts.duration, opts.total)
3353        else:
3354
3355            self.start(opts.ports,
3356                       opts.mult,
3357                       opts.force,
3358                       opts.duration,
3359                       opts.total,
3360                       core_mask)
3361
3362        return RC_OK()
3363
3364
3365
3366    @__console
3367    def stop_line (self, line):
3368        '''Stop active traffic on specified ports on TRex\n'''
3369        parser = parsing_opts.gen_parser(self,
3370                                         "stop",
3371                                         self.stop_line.__doc__,
3372                                         parsing_opts.PORT_LIST_WITH_ALL)
3373
3374        opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True)
3375        if not opts:
3376            return opts
3377
3378
3379        # find the relevant ports
3380        ports = list_intersect(opts.ports, self.get_active_ports())
3381        if not ports:
3382            if not opts.ports:
3383                msg = 'stop - no active ports'
3384            else:
3385                msg = 'stop - no active traffic on ports {0}'.format(opts.ports)
3386
3387            self.logger.log(msg)
3388            return RC_ERR(msg)
3389
3390        # call API
3391        self.stop(ports)
3392
3393        return RC_OK()
3394
3395
3396    @__console
3397    def update_line (self, line):
3398        '''Update port(s) speed currently active\n'''
3399        parser = parsing_opts.gen_parser(self,
3400                                         "update",
3401                                         self.update_line.__doc__,
3402                                         parsing_opts.PORT_LIST_WITH_ALL,
3403                                         parsing_opts.MULTIPLIER,
3404                                         parsing_opts.TOTAL,
3405                                         parsing_opts.FORCE)
3406
3407        opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True)
3408        if not opts:
3409            return opts
3410
3411
3412        # find the relevant ports
3413        ports = list_intersect(opts.ports, self.get_active_ports())
3414        if not ports:
3415            if not opts.ports:
3416                msg = 'update - no active ports'
3417            else:
3418                msg = 'update - no active traffic on ports {0}'.format(opts.ports)
3419
3420            self.logger.log(msg)
3421            return RC_ERR(msg)
3422
3423        self.update(ports, opts.mult, opts.total, opts.force)
3424
3425        return RC_OK()
3426
3427
3428    @__console
3429    def pause_line (self, line):
3430        '''Pause active traffic on specified ports on TRex\n'''
3431        parser = parsing_opts.gen_parser(self,
3432                                         "pause",
3433                                         self.pause_line.__doc__,
3434                                         parsing_opts.PORT_LIST_WITH_ALL)
3435
3436        opts = parser.parse_args(line.split(), default_ports = self.get_transmitting_ports(), verify_acquired = True)
3437        if not opts:
3438            return opts
3439
3440        # check for already paused case
3441        if opts.ports and is_sub_list(opts.ports, self.get_paused_ports()):
3442            msg = 'pause - all of port(s) {0} are already paused'.format(opts.ports)
3443            self.logger.log(msg)
3444            return RC_ERR(msg)
3445
3446        # find the relevant ports
3447        ports = list_intersect(opts.ports, self.get_transmitting_ports())
3448        if not ports:
3449            if not opts.ports:
3450                msg = 'pause - no transmitting ports'
3451            else:
3452                msg = 'pause - none of ports {0} are transmitting'.format(opts.ports)
3453
3454            self.logger.log(msg)
3455            return RC_ERR(msg)
3456
3457        self.pause(ports)
3458
3459        return RC_OK()
3460
3461
3462    @__console
3463    def resume_line (self, line):
3464        '''Resume active traffic on specified ports on TRex\n'''
3465        parser = parsing_opts.gen_parser(self,
3466                                         "resume",
3467                                         self.resume_line.__doc__,
3468                                         parsing_opts.PORT_LIST_WITH_ALL)
3469
3470        opts = parser.parse_args(line.split(), default_ports = self.get_paused_ports(), verify_acquired = True)
3471        if not opts:
3472            return opts
3473
3474        # find the relevant ports
3475        ports = list_intersect(opts.ports, self.get_paused_ports())
3476        if not ports:
3477            if not opts.ports:
3478                msg = 'resume - no paused ports'
3479            else:
3480                msg = 'resume - none of ports {0} are paused'.format(opts.ports)
3481
3482            self.logger.log(msg)
3483            return RC_ERR(msg)
3484
3485
3486        self.resume(ports)
3487
3488        # true means print time
3489        return RC_OK()
3490
3491
3492    @__console
3493    def clear_stats_line (self, line):
3494        '''Clear cached local statistics\n'''
3495        # define a parser
3496        parser = parsing_opts.gen_parser(self,
3497                                         "clear",
3498                                         self.clear_stats_line.__doc__,
3499                                         parsing_opts.PORT_LIST_WITH_ALL)
3500
3501        opts = parser.parse_args(line.split())
3502
3503        if not opts:
3504            return opts
3505
3506        self.clear_stats(opts.ports)
3507
3508        return RC_OK()
3509
3510
3511    @__console
3512    def show_stats_line (self, line):
3513        '''Get statistics from TRex server by port\n'''
3514        # define a parser
3515        parser = parsing_opts.gen_parser(self,
3516                                         "stats",
3517                                         self.show_stats_line.__doc__,
3518                                         parsing_opts.PORT_LIST_WITH_ALL,
3519                                         parsing_opts.STATS_MASK)
3520
3521        opts = parser.parse_args(line.split())
3522
3523        if not opts:
3524            return opts
3525
3526        # determine stats mask
3527        mask = self.__get_mask_keys(**self.__filter_namespace_args(opts, trex_stl_stats.ALL_STATS_OPTS))
3528        if not mask:
3529            # set to show all stats if no filter was given
3530            mask = trex_stl_stats.COMPACT
3531
3532        stats_opts = common.list_intersect(trex_stl_stats.ALL_STATS_OPTS, mask)
3533
3534        stats = self._get_formatted_stats(opts.ports, mask)
3535
3536
3537        # print stats to screen
3538        for stat_type, stat_data in stats.items():
3539            text_tables.print_table_with_header(stat_data.text_table, stat_type)
3540
3541
3542    @__console
3543    def show_streams_line(self, line):
3544        '''Get stream statistics from TRex server by port\n'''
3545        # define a parser
3546        parser = parsing_opts.gen_parser(self,
3547                                         "streams",
3548                                         self.show_streams_line.__doc__,
3549                                         parsing_opts.PORT_LIST_WITH_ALL,
3550                                         parsing_opts.STREAMS_MASK)
3551
3552        opts = parser.parse_args(line.split())
3553
3554        if not opts:
3555            return opts
3556
3557        streams = self._get_streams(opts.ports, set(opts.streams))
3558        if not streams:
3559            self.logger.log(format_text("No streams found with desired filter.\n", "bold", "magenta"))
3560
3561        else:
3562            # print stats to screen
3563            for stream_hdr, port_streams_data in streams.items():
3564                text_tables.print_table_with_header(port_streams_data.text_table,
3565                                                    header= stream_hdr.split(":")[0] + ":",
3566                                                    untouched_header= stream_hdr.split(":")[1])
3567
3568
3569
3570
3571    @__console
3572    def validate_line (self, line):
3573        '''Validates port(s) stream configuration\n'''
3574
3575        parser = parsing_opts.gen_parser(self,
3576                                         "validate",
3577                                         self.validate_line.__doc__,
3578                                         parsing_opts.PORT_LIST_WITH_ALL)
3579
3580        opts = parser.parse_args(line.split())
3581        if not opts:
3582            return opts
3583
3584        self.validate(opts.ports)
3585
3586
3587
3588
3589    @__console
3590    def push_line (self, line):
3591        '''Push a pcap file '''
3592        args = [self,
3593                "push",
3594                self.push_line.__doc__,
3595                parsing_opts.REMOTE_FILE,
3596                parsing_opts.PORT_LIST_WITH_ALL,
3597                parsing_opts.COUNT,
3598                parsing_opts.DURATION,
3599                parsing_opts.IPG,
3600                parsing_opts.SPEEDUP,
3601                parsing_opts.FORCE,
3602                parsing_opts.DUAL]
3603
3604        parser = parsing_opts.gen_parser(*(args + [parsing_opts.FILE_PATH_NO_CHECK]))
3605        opts = parser.parse_args(line.split(), verify_acquired = True)
3606
3607        if not opts:
3608            return opts
3609
3610        if not opts.remote:
3611            parser = parsing_opts.gen_parser(*(args + [parsing_opts.FILE_PATH]))
3612            opts = parser.parse_args(line.split(), verify_acquired = True)
3613
3614        if not opts:
3615            return opts
3616
3617        active_ports = list(set(self.get_active_ports()).intersection(opts.ports))
3618
3619        if active_ports:
3620            if not opts.force:
3621                msg = "Port(s) {0} are active - please stop them or add '--force'\n".format(active_ports)
3622                self.logger.log(format_text(msg, 'bold'))
3623                return RC_ERR(msg)
3624            else:
3625                self.stop(active_ports)
3626
3627
3628        if opts.remote:
3629            self.push_remote(opts.file[0],
3630                             ports     = opts.ports,
3631                             ipg_usec  = opts.ipg_usec,
3632                             speedup   = opts.speedup,
3633                             count     = opts.count,
3634                             duration  = opts.duration,
3635                             is_dual   = opts.dual)
3636
3637        else:
3638            self.push_pcap(opts.file[0],
3639                           ports     = opts.ports,
3640                           ipg_usec  = opts.ipg_usec,
3641                           speedup   = opts.speedup,
3642                           count     = opts.count,
3643                           duration  = opts.duration,
3644                           force     = opts.force,
3645                           is_dual   = opts.dual)
3646
3647
3648
3649        return RC_OK()
3650
3651
3652
3653    @__console
3654    def set_port_attr_line (self, line):
3655        '''Sets port attributes '''
3656
3657        parser = parsing_opts.gen_parser(self,
3658                                         "portattr",
3659                                         self.set_port_attr_line.__doc__,
3660                                         parsing_opts.PORT_LIST_WITH_ALL,
3661                                         parsing_opts.PROMISCUOUS,
3662                                         parsing_opts.LINK_STATUS,
3663                                         parsing_opts.LED_STATUS,
3664                                         parsing_opts.FLOW_CTRL,
3665                                         parsing_opts.SUPPORTED,
3666                                         parsing_opts.RX_FILTER_MODE,
3667                                         parsing_opts.IPV4,
3668                                         parsing_opts.DEST
3669                                         )
3670
3671        opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True)
3672        if not opts:
3673            return opts
3674
3675        opts.prom            = parsing_opts.ON_OFF_DICT.get(opts.prom)
3676        opts.link            = parsing_opts.UP_DOWN_DICT.get(opts.link)
3677        opts.led             = parsing_opts.ON_OFF_DICT.get(opts.led)
3678        opts.flow_ctrl       = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl)
3679
3680        # if no attributes - fall back to printing the status
3681        if not list(filter(lambda x:x is not None, [opts.prom, opts.link, opts.led, opts.flow_ctrl, opts.supp, opts.rx_filter_mode, opts.ipv4, opts.dest])):
3682            self.show_stats_line("--ps --port {0}".format(' '.join(str(port) for port in opts.ports)))
3683            return
3684
3685        if opts.supp:
3686            info = self.ports[0].get_formatted_info() # assume for now all ports are same
3687            print('')
3688            print('Supported attributes for current NICs:')
3689            print('  Promiscuous:   yes')
3690            print('  Link status:   %s' % info['link_change_supported'])
3691            print('  LED status:    %s' % info['led_change_supported'])
3692            print('  Flow control:  %s' % info['fc_supported'])
3693            print('')
3694        else:
3695             self.set_port_attr(opts.ports,
3696                                opts.prom,
3697                                opts.link,
3698                                opts.led,
3699                                opts.flow_ctrl,
3700                                opts.rx_filter_mode,
3701                                opts.ipv4,
3702                                opts.dest)
3703
3704
3705
3706
3707    @__console
3708    def set_rx_sniffer_line (self, line):
3709        '''Sets a port sniffer on RX channel in form of a PCAP file'''
3710
3711        parser = parsing_opts.gen_parser(self,
3712                                         "set_rx_sniffer",
3713                                         self.set_rx_sniffer_line.__doc__,
3714                                         parsing_opts.PORT_LIST_WITH_ALL,
3715                                         parsing_opts.OUTPUT_FILENAME,
3716                                         parsing_opts.LIMIT,
3717                                         parsing_opts.ALL_FILES)
3718
3719        opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True)
3720        if not opts:
3721            return opts
3722
3723        rxf = 'all' if opts.all else None
3724
3725        self.set_rx_sniffer(opts.ports, opts.output_filename, opts.limit, rxf)
3726
3727
3728    @__console
3729    def resolve_line (self, line):
3730        '''Performs a port ARP resolution'''
3731
3732        parser = parsing_opts.gen_parser(self,
3733                                         "resolve",
3734                                         self.resolve_line.__doc__,
3735                                         parsing_opts.PORT_LIST_WITH_ALL,
3736                                         parsing_opts.RETRIES)
3737
3738        opts = parser.parse_args(line.split(), default_ports = self.get_resolvable_ports(), verify_acquired = True)
3739        if not opts:
3740            return opts
3741
3742
3743        self.resolve(ports = opts.ports, retries = opts.retries)
3744
3745
3746
3747    @__console
3748    def show_profile_line (self, line):
3749        '''Shows profile information'''
3750
3751        parser = parsing_opts.gen_parser(self,
3752                                         "profile",
3753                                         self.show_profile_line.__doc__,
3754                                         parsing_opts.FILE_PATH)
3755
3756        opts = parser.parse_args(line.split())
3757        if not opts:
3758            return opts
3759
3760        info = STLProfile.get_info(opts.file[0])
3761
3762        self.logger.log(format_text('\nProfile Information:\n', 'bold'))
3763
3764        # general info
3765        self.logger.log(format_text('\nGeneral Information:', 'underline'))
3766        self.logger.log('Filename:         {:^12}'.format(opts.file[0]))
3767        self.logger.log('Stream count:     {:^12}'.format(info['stream_count']))
3768
3769        # specific info
3770        profile_type = info['type']
3771        self.logger.log(format_text('\nSpecific Information:', 'underline'))
3772
3773        if profile_type == 'python':
3774            self.logger.log('Type:             {:^12}'.format('Python Module'))
3775            self.logger.log('Tunables:         {:^12}'.format(str(['{0} = {1}'.format(k ,v) for k, v in info['tunables'].items()])))
3776
3777        elif profile_type == 'yaml':
3778            self.logger.log('Type:             {:^12}'.format('YAML'))
3779
3780        elif profile_type == 'pcap':
3781            self.logger.log('Type:             {:^12}'.format('PCAP file'))
3782
3783        self.logger.log("")
3784
3785
3786    @__console
3787    def get_events_line (self, line):
3788        '''shows events recieved from server\n'''
3789
3790        x = [parsing_opts.ArgumentPack(['-c','--clear'],
3791                                      {'action' : "store_true",
3792                                       'default': False,
3793                                       'help': "clear the events log"}),
3794
3795             parsing_opts.ArgumentPack(['-i','--info'],
3796                                      {'action' : "store_true",
3797                                       'default': False,
3798                                       'help': "show info events"}),
3799
3800             parsing_opts.ArgumentPack(['-w','--warn'],
3801                                      {'action' : "store_true",
3802                                       'default': False,
3803                                       'help': "show warning events"}),
3804
3805             ]
3806
3807
3808        parser = parsing_opts.gen_parser(self,
3809                                         "events",
3810                                         self.get_events_line.__doc__,
3811                                         *x)
3812
3813        opts = parser.parse_args(line.split())
3814        if not opts:
3815            return opts
3816
3817
3818        ev_type_filter = []
3819
3820        if opts.info:
3821            ev_type_filter.append('info')
3822
3823        if opts.warn:
3824            ev_type_filter.append('warning')
3825
3826        if not ev_type_filter:
3827            ev_type_filter = None
3828
3829        events = self.get_events(ev_type_filter)
3830        for ev in events:
3831            self.logger.log(ev)
3832
3833        if opts.clear:
3834            self.clear_events()
3835
3836    def generate_prompt (self, prefix = 'trex'):
3837        if not self.is_connected():
3838            return "{0}(offline)>".format(prefix)
3839
3840        elif not self.get_acquired_ports():
3841            return "{0}(read-only)>".format(prefix)
3842
3843        elif self.is_all_ports_acquired():
3844            return "{0}>".format(prefix)
3845
3846        else:
3847            return "{0} {1}>".format(prefix, self.get_acquired_ports())
3848
3849
3850
3851