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