parsing_opts.py revision f34d418d
1import argparse
2from collections import namedtuple, OrderedDict
3from .common import list_intersect, list_difference, is_valid_ipv4, is_valid_ipv6, is_valid_mac, list_remove_dup
4from .text_opts import format_text
5from ..trex_stl_types import *
6from .constants import ON_OFF_DICT, UP_DOWN_DICT, FLOW_CTRL_DICT
7
8import sys
9import re
10import os
11import inspect
12
13
14ArgumentPack = namedtuple('ArgumentPack', ['name_or_flags', 'options'])
15ArgumentGroup = namedtuple('ArgumentGroup', ['type', 'args', 'options'])
16
17
18# list of available parsing options
19_constants = '''
20
21MULTIPLIER
22MULTIPLIER_STRICT
23PORT_LIST
24ALL_PORTS
25PORT_LIST_WITH_ALL
26FILE_PATH
27FILE_FROM_DB
28SERVER_IP
29STREAM_FROM_PATH_OR_FILE
30DURATION
31TIMEOUT
32FORCE
33DRY_RUN
34XTERM
35TOTAL
36FULL_OUTPUT
37IPG
38MIN_IPG
39SPEEDUP
40COUNT
41PROMISCUOUS
42MULTICAST
43LINK_STATUS
44LED_STATUS
45TUNABLES
46REMOTE_FILE
47LOCKED
48PIN_CORES
49CORE_MASK
50DUAL
51FLOW_CTRL
52SUPPORTED
53FILE_PATH_NO_CHECK
54
55OUTPUT_FILENAME
56LIMIT
57PORT_RESTART
58
59RETRIES
60
61SINGLE_PORT
62DST_MAC
63
64PING_IP
65PING_COUNT
66PKT_SIZE
67
68SERVICE_OFF
69
70TX_PORT_LIST
71RX_PORT_LIST
72
73SRC_IPV4
74DST_IPV4
75
76CAPTURE_ID
77
78SCAPY_PKT
79SHOW_LAYERS
80SCAPY_PKT_CMD
81
82GLOBAL_STATS
83PORT_STATS
84PORT_STATUS
85STREAMS_STATS
86STATS_MASK
87CPU_STATS
88MBUF_STATS
89EXTENDED_STATS
90EXTENDED_INC_ZERO_STATS
91
92STREAMS_MASK
93CORE_MASK_GROUP
94CAPTURE_PORTS_GROUP
95
96MONITOR_TYPE_VERBOSE
97MONITOR_TYPE_PIPE
98MONITOR_TYPE
99
100
101
102# ALL_STREAMS
103# STREAM_LIST_WITH_ALL
104
105# list of ArgumentGroup types
106MUTEX
107NON_MUTEX
108
109'''
110
111for index, line in enumerate(_constants.splitlines()):
112    var = line.strip().split()
113    if not var or '#' in var[0]:
114        continue
115    exec('%s = %s' % (var[0], index))
116
117
118def check_negative(value):
119    ivalue = int(value)
120    if ivalue < 0:
121        raise argparse.ArgumentTypeError("non positive value provided: '{0}'".format(value))
122    return ivalue
123
124def match_time_unit(val):
125    '''match some val against time shortcut inputs '''
126    match = re.match("^(\d+(\.\d+)?)([m|h]?)$", val)
127    if match:
128        digit = float(match.group(1))
129        unit = match.group(3)
130        if not unit:
131            return digit
132        elif unit == 'm':
133            return digit*60
134        else:
135            return digit*60*60
136    else:
137        raise argparse.ArgumentTypeError("Duration should be passed in the following format: \n"
138                                         "-d 100 : in sec \n"
139                                         "-d 10m : in min \n"
140                                         "-d 1h  : in hours")
141
142
143match_multiplier_help = """Multiplier should be passed in the following format:
144                          [number][<empty> | bps | kbps | mbps |  gbps | pps | kpps | mpps | %% ].
145
146                          no suffix will provide an absoulute factor and percentage
147                          will provide a percentage of the line rate. examples
148
149                          '-m 10',
150                          '-m 10kbps',
151                          '-m 10kbpsl1',
152                          '-m 10mpps',
153                          '-m 23%% '
154
155                          '-m 23%%' : is 23%% L1 bandwidth
156                          '-m 23mbps': is 23mbps in L2 bandwidth (including FCS+4)
157                          '-m 23mbpsl1': is 23mbps in L1 bandwidth
158
159                          """
160
161
162# decodes multiplier
163# if allow_update - no +/- is allowed
164# divide states between how many entities the
165# value should be divided
166def decode_multiplier(val, allow_update = False, divide_count = 1):
167
168    factor_table = {None: 1, 'k': 1e3, 'm': 1e6, 'g': 1e9}
169    pattern = "^(\d+(\.\d+)?)(((k|m|g)?(bpsl1|pps|bps))|%)?"
170
171    # do we allow updates ?  +/-
172    if not allow_update:
173        pattern += "$"
174        match = re.match(pattern, val)
175        op = None
176    else:
177        pattern += "([\+\-])?$"
178        match = re.match(pattern, val)
179        if match:
180            op  = match.group(7)
181        else:
182            op = None
183
184    result = {}
185
186    if not match:
187        return None
188
189    # value in group 1
190    value = float(match.group(1))
191
192    # decode unit as whole
193    unit = match.group(3)
194
195    # k,m,g
196    factor = match.group(5)
197
198    # type of multiplier
199    m_type = match.group(6)
200
201    # raw type (factor)
202    if not unit:
203        result['type'] = 'raw'
204        result['value'] = value
205
206    # percentage
207    elif unit == '%':
208        result['type'] = 'percentage'
209        result['value']  = value
210
211    elif m_type == 'bps':
212        result['type'] = 'bps'
213        result['value'] = value * factor_table[factor]
214
215    elif m_type == 'pps':
216        result['type'] = 'pps'
217        result['value'] = value * factor_table[factor]
218
219    elif m_type == 'bpsl1':
220        result['type'] = 'bpsl1'
221        result['value'] = value * factor_table[factor]
222
223
224    if op == "+":
225        result['op'] = "add"
226    elif op == "-":
227        result['op'] = "sub"
228    else:
229        result['op'] = "abs"
230
231    if result['op'] != 'percentage':
232        result['value'] = result['value'] / divide_count
233
234    return result
235
236
237
238def match_multiplier(val):
239    '''match some val against multiplier  shortcut inputs '''
240    result = decode_multiplier(val, allow_update = True)
241    if not result:
242        raise argparse.ArgumentTypeError(match_multiplier_help)
243
244    return val
245
246
247def match_multiplier_strict(val):
248    '''match some val against multiplier  shortcut inputs '''
249    result = decode_multiplier(val, allow_update = False)
250    if not result:
251        raise argparse.ArgumentTypeError(match_multiplier_help)
252
253    return val
254
255def hex_int (val):
256    pattern = r"0x[1-9a-fA-F][0-9a-fA-F]*"
257
258    if not re.match(pattern, val):
259        raise argparse.ArgumentTypeError("{0} is not a valid positive HEX formatted number".format(val))
260
261    return int(val, 16)
262
263
264def is_valid_file(filename):
265    if not os.path.isfile(filename):
266        raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename)
267
268    return filename
269
270# scapy decoder class for parsing opts
271class ScapyDecoder(object):
272    scapy_layers = None
273
274    @staticmethod
275    def init ():
276        # one time
277        if ScapyDecoder.scapy_layers:
278            return
279
280
281
282        import scapy.all
283        import scapy.layers.dhcp
284
285        raw = {}
286
287        # default layers
288        raw.update(scapy.all.__dict__)
289
290        # extended layers - add here
291        raw.update(scapy.layers.dhcp.__dict__)
292
293        ScapyDecoder.scapy_layers = {k: v for k, v in raw.items() if inspect.isclass(v) and issubclass(v, scapy.all.Packet)}
294
295
296    @staticmethod
297    def to_scapy(scapy_str):
298        ScapyDecoder.init()
299
300        try:
301            scapy_obj = eval(scapy_str, {'__builtins__': {}, 'True': True, 'False': False}, ScapyDecoder.scapy_layers)
302            len(scapy_obj)
303            return scapy_obj
304        except Exception as e:
305            raise argparse.ArgumentTypeError("invalid scapy expression: '{0}' - {1}".format(scapy_str, str(e)))
306
307
308    @staticmethod
309    def formatted_layers ():
310        ScapyDecoder.init()
311
312        output = ''
313        for k, v in sorted(ScapyDecoder.scapy_layers.items()):
314            name    = format_text("'{}'".format(k), 'bold')
315            descr   = v.name
316            #fields  = ", ".join(["'%s'" % f.name for f in v.fields_desc])
317            #print("{:<50} - {} ({})".format(name ,descr, fields))
318            output += "{:<50} - {}\n".format(name ,descr)
319
320        return output
321
322
323def check_ipv4_addr (ipv4_str):
324    if not is_valid_ipv4(ipv4_str):
325        raise argparse.ArgumentTypeError("invalid IPv4 address: '{0}'".format(ipv4_str))
326
327    return ipv4_str
328
329def check_ip_addr(addr):
330    if not (is_valid_ipv4(addr) or is_valid_ipv6(addr)):
331        raise argparse.ArgumentTypeError("invalid IPv4/6 address: '{0}'".format(addr))
332
333    return addr
334
335def check_pkt_size (pkt_size):
336    try:
337        pkt_size = int(pkt_size)
338    except ValueError:
339        raise argparse.ArgumentTypeError("invalid packet size type: '{0}'".format(pkt_size))
340
341    if (pkt_size < 64) or (pkt_size > 9216):
342        raise argparse.ArgumentTypeError("invalid packet size: '{0}' - valid range is 64 to 9216".format(pkt_size))
343
344    return pkt_size
345
346def check_mac_addr (addr):
347    if not is_valid_mac(addr):
348        raise argparse.ArgumentTypeError("not a valid MAC address: '{0}'".format(addr))
349
350    return addr
351
352
353def decode_tunables (tunable_str):
354    tunables = {}
355
356    # split by comma to tokens
357    tokens = tunable_str.split(',')
358
359    # each token is of form X=Y
360    for token in tokens:
361        m = re.search('(\S+)=(.+)', token)
362        if not m:
363            raise argparse.ArgumentTypeError("bad syntax for tunables: {0}".format(token))
364        val = m.group(2)           # string
365        if val.startswith(("'", '"')) and val.endswith(("'", '"')) and len(val) > 1: # need to remove the quotes from value
366            val = val[1:-1]
367        elif val.startswith('0x'): # hex
368            val = int(val, 16)
369        else:
370            try:
371                if '.' in val:     # float
372                    val = float(val)
373                else:              # int
374                    val = int(val)
375            except:
376                pass
377        tunables[m.group(1)] = val
378
379    return tunables
380
381
382
383OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
384                                 {'help': match_multiplier_help,
385                                  'dest': "mult",
386                                  'default': "1",
387                                  'type': match_multiplier}),
388
389              MULTIPLIER_STRICT: ArgumentPack(['-m', '--multiplier'],
390                               {'help': match_multiplier_help,
391                                  'dest': "mult",
392                                  'default': "1",
393                                  'type': match_multiplier_strict}),
394
395              TOTAL: ArgumentPack(['-t', '--total'],
396                                 {'help': "traffic will be divided between all ports specified",
397                                  'dest': "total",
398                                  'default': False,
399                                  'action': "store_true"}),
400
401              IPG: ArgumentPack(['-i', '--ipg'],
402                                {'help': "IPG value in usec between packets. default will be from the pcap",
403                                 'dest': "ipg_usec",
404                                 'default':  None,
405                                 'type': float}),
406
407              MIN_IPG: ArgumentPack(['--min-ipg'],
408                                {'help': "Minimal IPG value in usec between packets. Used to guard from too small IPGs.",
409                                 'dest': "min_ipg_usec",
410                                 'default':  None,
411                                 'type': float}),
412
413              SPEEDUP: ArgumentPack(['-s', '--speedup'],
414                                   {'help': "Factor to accelerate the injection. effectively means IPG = IPG / SPEEDUP",
415                                    'dest': "speedup",
416                                    'default':  1.0,
417                                    'type': float}),
418
419              COUNT: ArgumentPack(['-c', '--count'],
420                                  {'help': "How many times to perform action [default is 1, 0 means forever]",
421                                   'dest': "count",
422                                   'default':  1,
423                                   'type': int}),
424
425              PROMISCUOUS: ArgumentPack(['--prom'],
426                                        {'help': "Set port promiscuous on/off",
427                                         'choices': ON_OFF_DICT}),
428
429              MULTICAST: ArgumentPack(['--mult'],
430                                      {'help': "Set port multicast on/off",
431                                       'choices': ON_OFF_DICT}),
432
433              LINK_STATUS: ArgumentPack(['--link'],
434                                     {'help': 'Set link status up/down',
435                                      'choices': UP_DOWN_DICT}),
436
437              LED_STATUS: ArgumentPack(['--led'],
438                                   {'help': 'Set LED status on/off',
439                                    'choices': ON_OFF_DICT}),
440
441              FLOW_CTRL: ArgumentPack(['--fc'],
442                                   {'help': 'Set Flow Control type',
443                                    'dest': 'flow_ctrl',
444                                    'choices': FLOW_CTRL_DICT}),
445
446              SRC_IPV4: ArgumentPack(['--src'],
447                                     {'help': 'Configure source IPv4 address',
448                                      'dest': 'src_ipv4',
449                                      'required': True,
450                                      'type': check_ipv4_addr}),
451
452              DST_IPV4: ArgumentPack(['--dst'],
453                                     {'help': 'Configure destination IPv4 address',
454                                      'dest': 'dst_ipv4',
455                                      'required': True,
456                                      'type': check_ipv4_addr}),
457
458
459              DST_MAC: ArgumentPack(['--dst'],
460                                    {'help': 'Configure destination MAC address',
461                                     'dest': 'dst_mac',
462                                     'required': True,
463                                     'type': check_mac_addr}),
464
465              RETRIES: ArgumentPack(['-r', '--retries'],
466                                    {'help': 'retries count [default is zero]',
467                                     'dest': 'retries',
468                                     'default':  0,
469                                     'type': int}),
470
471
472              OUTPUT_FILENAME: ArgumentPack(['-o', '--output'],
473                                            {'help': 'Output PCAP filename',
474                                             'dest': 'output_filename',
475                                             'default': None,
476                                             'type': str}),
477
478
479              PORT_RESTART: ArgumentPack(['-r', '--restart'],
480                                         {'help': 'hard restart port(s)',
481                                          'dest': 'restart',
482                                          'default': False,
483                                          'action': 'store_true'}),
484
485              LIMIT: ArgumentPack(['-l', '--limit'],
486                                  {'help': 'Limit the packet count to be written to the file',
487                                   'dest': 'limit',
488                                   'default':  1000,
489                                   'type': int}),
490
491
492              SUPPORTED: ArgumentPack(['--supp'],
493                                   {'help': 'Show which attributes are supported by current NICs',
494                                    'default': None,
495                                    'action': 'store_true'}),
496
497              TUNABLES: ArgumentPack(['-t'],
498                                     {'help': "Sets tunables for a profile. Example: '-t fsize=100,pg_id=7'",
499                                      'metavar': 'T1=VAL[,T2=VAL ...]',
500                                      'dest': "tunables",
501                                      'default': None,
502                                      'action': 'merge',
503                                      'type': decode_tunables}),
504
505              PORT_LIST: ArgumentPack(['--port', '-p'],
506                                        {"nargs": '+',
507                                         'dest':'ports',
508                                         'metavar': 'PORTS',
509                                         'action': 'merge',
510                                         'type': int,
511                                         'help': "A list of ports on which to apply the command",
512                                         'default': []}),
513
514
515              SINGLE_PORT: ArgumentPack(['--port', '-p'],
516                                        {'dest':'ports',
517                                         'type': int,
518                                         'metavar': 'PORT',
519                                         'help': 'source port for the action',
520                                         'required': True}),
521
522              PING_IP: ArgumentPack(['-d'],
523                                      {'help': 'which IPv4/6 to ping',
524                                      'dest': 'ping_ip',
525                                      'required': True,
526                                      'type': check_ip_addr}),
527
528              PING_COUNT: ArgumentPack(['-n', '--count'],
529                                       {'help': 'How many times to ping [default is 5]',
530                                        'dest': 'count',
531                                        'default':  5,
532                                        'type': int}),
533
534              PKT_SIZE: ArgumentPack(['-s'],
535                                     {'dest':'pkt_size',
536                                      'help': 'packet size to use',
537                                      'default': 64,
538                                      'type': check_pkt_size}),
539
540
541              ALL_PORTS: ArgumentPack(['-a'],
542                                        {"action": "store_true",
543                                         "dest": "all_ports",
544                                         'help': "Set this flag to apply the command on all available ports",
545                                         'default': False},),
546
547              DURATION: ArgumentPack(['-d'],
548                                        {'action': "store",
549                                         'metavar': 'TIME',
550                                         'dest': 'duration',
551                                         'type': match_time_unit,
552                                         'default': -1.0,
553                                         'help': "Set duration time for job."}),
554
555              TIMEOUT: ArgumentPack(['-t'],
556                                        {'action': "store",
557                                         'metavar': 'TIMEOUT',
558                                         'dest': 'timeout',
559                                         'type': int,
560                                         'default': None,
561                                         'help': "Timeout for operation in seconds."}),
562
563              FORCE: ArgumentPack(['--force'],
564                                        {"action": "store_true",
565                                         'default': False,
566                                         'help': "Set if you want to stop active ports before appyling command."}),
567
568              REMOTE_FILE: ArgumentPack(['-r', '--remote'],
569                                        {"action": "store_true",
570                                         'default': False,
571                                         'help': "file path should be interpeted by the server (remote file)"}),
572
573              DUAL: ArgumentPack(['--dual'],
574                                 {"action": "store_true",
575                                  'default': False,
576                                  'help': "Transmit in a dual mode - requires ownership on the adjacent port"}),
577
578              FILE_PATH: ArgumentPack(['-f'],
579                                      {'metavar': 'FILE',
580                                       'dest': 'file',
581                                       'nargs': 1,
582                                       'required': True,
583                                       'type': is_valid_file,
584                                       'help': "File path to load"}),
585
586              FILE_PATH_NO_CHECK: ArgumentPack(['-f'],
587                                      {'metavar': 'FILE',
588                                       'dest': 'file',
589                                       'nargs': 1,
590                                       'required': True,
591                                       'type': str,
592                                       'help': "File path to load"}),
593
594              FILE_FROM_DB: ArgumentPack(['--db'],
595                                         {'metavar': 'LOADED_STREAM_PACK',
596                                          'help': "A stream pack which already loaded into console cache."}),
597
598              SERVER_IP: ArgumentPack(['--server'],
599                                      {'metavar': 'SERVER',
600                                       'help': "server IP"}),
601
602              DRY_RUN: ArgumentPack(['-n', '--dry'],
603                                    {'action': 'store_true',
604                                     'dest': 'dry',
605                                     'default': False,
606                                     'help': "Dry run - no traffic will be injected"}),
607
608              XTERM: ArgumentPack(['-x', '--xterm'],
609                                  {'action': 'store_true',
610                                   'dest': 'xterm',
611                                   'default': False,
612                                   'help': "Starts TUI in xterm window"}),
613
614              LOCKED: ArgumentPack(['-l', '--locked'],
615                                   {'action': 'store_true',
616                                    'dest': 'locked',
617                                    'default': False,
618                                    'help': "Locks TUI on legend mode"}),
619
620              FULL_OUTPUT: ArgumentPack(['--full'],
621                                         {'action': 'store_true',
622                                          'help': "Prompt full info in a JSON format"}),
623
624              GLOBAL_STATS: ArgumentPack(['-g'],
625                                         {'action': 'store_true',
626                                          'help': "Fetch only global statistics"}),
627
628              PORT_STATS: ArgumentPack(['-p'],
629                                       {'action': 'store_true',
630                                        'help': "Fetch only port statistics"}),
631
632              PORT_STATUS: ArgumentPack(['--ps'],
633                                        {'action': 'store_true',
634                                         'help': "Fetch only port status data"}),
635
636              STREAMS_STATS: ArgumentPack(['-s'],
637                                          {'action': 'store_true',
638                                           'help': "Fetch only streams stats"}),
639
640              CPU_STATS: ArgumentPack(['-c'],
641                                      {'action': 'store_true',
642                                       'help': "Fetch only CPU utilization stats"}),
643
644              MBUF_STATS: ArgumentPack(['-m'],
645                                       {'action': 'store_true',
646                                        'help': "Fetch only MBUF utilization stats"}),
647
648              EXTENDED_STATS: ArgumentPack(['-x'],
649                                       {'action': 'store_true',
650                                        'help': "Fetch xstats of port, excluding lines with zero values"}),
651
652              EXTENDED_INC_ZERO_STATS: ArgumentPack(['--xz'],
653                                       {'action': 'store_true',
654                                        'help': "Fetch xstats of port, including lines with zero values"}),
655
656              STREAMS_MASK: ArgumentPack(['--streams'],
657                                         {"nargs": '+',
658                                          'dest':'streams',
659                                          'metavar': 'STREAMS',
660                                          'type': int,
661                                          'help': "A list of stream IDs to query about. Default: analyze all streams",
662                                          'default': []}),
663
664
665              PIN_CORES: ArgumentPack(['--pin'],
666                                      {'action': 'store_true',
667                                       'dest': 'pin_cores',
668                                       'default': False,
669                                       'help': "Pin cores to interfaces - cores will be divided between interfaces (performance boot for symetric profiles)"}),
670
671              CORE_MASK: ArgumentPack(['--core_mask'],
672                                      {'action': 'store',
673                                       'nargs': '+',
674                                       'type': hex_int,
675                                       'dest': 'core_mask',
676                                       'default': None,
677                                       'help': "Core mask - only cores responding to the bit mask will be active"}),
678
679              SERVICE_OFF: ArgumentPack(['--off'],
680                                        {'action': 'store_false',
681                                         'dest': 'enabled',
682                                         'default': True,
683                                         'help': 'Deactivates services on port(s)'}),
684
685              TX_PORT_LIST: ArgumentPack(['--tx'],
686                                         {'nargs': '+',
687                                          'dest':'tx_port_list',
688                                          'metavar': 'TX',
689                                          'action': 'merge',
690                                          'type': int,
691                                          'help': 'A list of ports to capture on the TX side',
692                                          'default': []}),
693
694
695              RX_PORT_LIST: ArgumentPack(['--rx'],
696                                         {'nargs': '+',
697                                          'dest':'rx_port_list',
698                                          'metavar': 'RX',
699                                          'action': 'merge',
700                                          'type': int,
701                                          'help': 'A list of ports to capture on the RX side',
702                                          'default': []}),
703
704
705              MONITOR_TYPE_VERBOSE: ArgumentPack(['-v', '--verbose'],
706                                                 {'action': 'store_true',
707                                                  'dest': 'verbose',
708                                                  'default': False,
709                                                  'help': 'output to screen as verbose'}),
710
711              MONITOR_TYPE_PIPE: ArgumentPack(['-p', '--pipe'],
712                                              {'action': 'store_true',
713                                               'dest': 'pipe',
714                                               'default': False,
715                                               'help': 'forward packets to a pipe'}),
716
717
718              CAPTURE_ID: ArgumentPack(['-i', '--id'],
719                                  {'help': "capture ID to remove",
720                                   'dest': "capture_id",
721                                   'type': int,
722                                   'required': True}),
723
724
725              SCAPY_PKT: ArgumentPack(['-s'],
726                                      {'dest':'scapy_pkt',
727                                       'metavar': 'PACKET',
728                                       'type': ScapyDecoder.to_scapy,
729                                       'help': 'A scapy notation packet (e.g.: Ether()/IP())'}),
730
731              SHOW_LAYERS: ArgumentPack(['--layers', '-l'],
732                                        {'action': 'store_true',
733                                         'dest': 'layers',
734                                         'help': "Show all registered layers / inspect a specific layer"}),
735
736
737              SCAPY_PKT_CMD: ArgumentGroup(MUTEX, [SCAPY_PKT,
738                                                   SHOW_LAYERS],
739                                           {'required': True}),
740
741              # advanced options
742              PORT_LIST_WITH_ALL: ArgumentGroup(MUTEX, [PORT_LIST,
743                                                        ALL_PORTS],
744                                                {'required': False}),
745
746
747              STREAM_FROM_PATH_OR_FILE: ArgumentGroup(MUTEX, [FILE_PATH,
748                                                              FILE_FROM_DB],
749                                                      {'required': True}),
750              STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS,
751                                                PORT_STATS,
752                                                PORT_STATUS,
753                                                STREAMS_STATS,
754                                                CPU_STATS,
755                                                MBUF_STATS,
756                                                EXTENDED_STATS,
757                                                EXTENDED_INC_ZERO_STATS,],
758                                        {}),
759
760
761              CORE_MASK_GROUP:  ArgumentGroup(MUTEX, [PIN_CORES,
762                                                      CORE_MASK],
763                                              {'required': False}),
764
765              CAPTURE_PORTS_GROUP: ArgumentGroup(NON_MUTEX, [TX_PORT_LIST, RX_PORT_LIST], {}),
766
767
768              MONITOR_TYPE: ArgumentGroup(MUTEX, [MONITOR_TYPE_VERBOSE,
769                                                  MONITOR_TYPE_PIPE],
770                                          {'required': False}),
771
772              }
773
774class _MergeAction(argparse._AppendAction):
775    def __call__(self, parser, namespace, values, option_string=None):
776        items = getattr(namespace, self.dest)
777        if not items:
778            items = values
779        elif type(items) is list and type(values) is list:
780            items.extend(values)
781        elif type(items) is dict and type(values) is dict: # tunables are dict
782            items.update(values)
783        else:
784            raise Exception("Argparser 'merge' option should be used on dict or list.")
785
786        setattr(namespace, self.dest, items)
787
788class CCmdArgParser(argparse.ArgumentParser):
789
790    def __init__(self, stateless_client = None, *args, **kwargs):
791        super(CCmdArgParser, self).__init__(*args, **kwargs)
792        self.stateless_client = stateless_client
793        self.cmd_name = kwargs.get('prog')
794        self.register('action', 'merge', _MergeAction)
795
796
797
798    def add_arg_list (self, *args):
799        populate_parser(self, *args)
800
801
802    # a simple hook for add subparsers to add stateless client
803    def add_subparsers(self, *args, **kwargs):
804        sub = super(CCmdArgParser, self).add_subparsers(*args, **kwargs)
805
806        # save pointer to the original add parser method
807        add_parser = sub.add_parser
808        stateless_client = self.stateless_client
809
810        def add_parser_hook (self, *args, **kwargs):
811            parser = add_parser(self, *args, **kwargs)
812            parser.stateless_client = stateless_client
813            return parser
814
815        # override with the hook
816        sub.add_parser = add_parser_hook
817
818        return sub
819
820
821    # hook this to the logger
822    def _print_message(self, message, file=None):
823        self.stateless_client.logger.log(message)
824
825
826    def error(self, message):
827        self.print_usage()
828        self._print_message(('%s: error: %s\n') % (self.prog, message))
829        raise ValueError(message)
830
831    def has_ports_cfg (self, opts):
832        return hasattr(opts, "all_ports") or hasattr(opts, "ports")
833
834    def parse_args(self, args=None, namespace=None, default_ports=None, verify_acquired=False):
835        try:
836            opts = super(CCmdArgParser, self).parse_args(args, namespace)
837            if opts is None:
838                return RC_ERR("'{0}' - invalid arguments".format(self.cmd_name))
839
840            if not self.has_ports_cfg(opts):
841                return opts
842
843            opts.ports = listify(opts.ports)
844
845            # if all ports are marked or
846            if (getattr(opts, "all_ports", None) == True) or (getattr(opts, "ports", None) == []):
847                if default_ports is None:
848                    opts.ports = self.stateless_client.get_acquired_ports()
849                else:
850                    opts.ports = default_ports
851
852            opts.ports = list_remove_dup(opts.ports)
853
854            # so maybe we have ports configured
855            invalid_ports = list_difference(opts.ports, self.stateless_client.get_all_ports())
856            if invalid_ports:
857
858                if len(invalid_ports) > 1:
859                    msg = "{0}: port(s) {1} are not valid port IDs".format(self.cmd_name, invalid_ports)
860                else:
861                    msg = "{0}: port {1} is not a valid port ID".format(self.cmd_name, invalid_ports[0])
862
863                self.stateless_client.logger.log(format_text(msg, 'bold'))
864                return RC_ERR(msg)
865
866            # verify acquired ports
867            if verify_acquired:
868                acquired_ports = self.stateless_client.get_acquired_ports()
869
870                diff = list_difference(opts.ports, acquired_ports)
871                if diff:
872                    msg = "{0} - port(s) {1} are not acquired".format(self.cmd_name, diff)
873                    self.stateless_client.logger.log(format_text(msg, 'bold'))
874                    return RC_ERR(msg)
875
876                # no acquire ports at all
877                if not acquired_ports:
878                    msg = "{0} - no acquired ports".format(self.cmd_name)
879                    self.stateless_client.logger.log(format_text(msg, 'bold'))
880                    return RC_ERR(msg)
881
882
883            return opts
884
885        except ValueError as e:
886            return RC_ERR("'{0}' - {1}".format(self.cmd_name, str(e)))
887
888        except SystemExit:
889            # recover from system exit scenarios, such as "help", or bad arguments.
890            return RC_ERR("'{0}' - {1}".format(self.cmd_name, "no action"))
891
892
893    def formatted_error (self, msg):
894        self.print_usage()
895        self._print_message(('%s: error: %s\n') % (self.prog, msg))
896
897
898def get_flags (opt):
899    return OPTIONS_DB[opt].name_or_flags
900
901def populate_parser (parser, *args):
902    for param in args:
903        try:
904
905            if isinstance(param, int):
906                argument = OPTIONS_DB[param]
907            else:
908                argument = param
909
910            if isinstance(argument, ArgumentGroup):
911                if argument.type == MUTEX:
912                    # handle as mutually exclusive group
913                    group = parser.add_mutually_exclusive_group(**argument.options)
914                    for sub_argument in argument.args:
915                        group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags,
916                                           **OPTIONS_DB[sub_argument].options)
917
918                elif argument.type == NON_MUTEX:
919                    group = parser.add_argument_group(**argument.options)
920                    for sub_argument in argument.args:
921                        group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags,
922                                           **OPTIONS_DB[sub_argument].options)
923                else:
924                    # ignore invalid objects
925                    continue
926            elif isinstance(argument, ArgumentPack):
927                parser.add_argument(*argument.name_or_flags,
928                                    **argument.options)
929            else:
930                # ignore invalid objects
931                continue
932        except KeyError as e:
933            cause = e.args[0]
934            raise KeyError("The attribute '{0}' is missing as a field of the {1} option.\n".format(cause, param))
935
936def gen_parser(stateless_client, op_name, description, *args):
937    parser = CCmdArgParser(stateless_client, prog=op_name, conflict_handler='resolve',
938                           description=description)
939
940    populate_parser(parser, *args)
941    return parser
942
943
944if __name__ == "__main__":
945    pass
946