trex_unit_test.py revision 7fbeb2f7
1#!/usr/bin/env python
2
3__copyright__ = "Copyright 2014"
4
5"""
6Name:
7     trex_unit_test.py
8
9
10Description:
11
12    This script creates the functionality to test the performance of the T-Rex traffic generator
13    The tested scenario is a T-Rex TG directly connected to a Cisco router.
14
15::
16
17    Topology:
18
19       -------                         --------
20      |       | Tx---1gig/10gig----Rx |        |
21      | T-Rex |                       | router |
22      |       | Rx---1gig/10gig----Tx |        |
23       -------                         --------
24
25"""
26
27import os
28import sys
29import outer_packages
30import nose
31from nose.plugins import Plugin
32import logging
33import CustomLogger
34import misc_methods
35from rednose import RedNose
36import termstyle
37from trex import CTRexScenario
38from client.trex_client import *
39from common.trex_exceptions import *
40from trex_stl_lib.api import *
41import trex
42import socket
43from pprint import pprint
44import subprocess
45import re
46import time
47
48def check_trex_path(trex_path):
49    if os.path.isfile('%s/trex_daemon_server' % trex_path):
50        return os.path.abspath(trex_path)
51
52def check_setup_path(setup_path):
53    if os.path.isfile('%s/config.yaml' % setup_path):
54        return os.path.abspath(setup_path)
55
56
57def get_trex_path():
58    latest_build_path = check_trex_path(os.getenv('TREX_UNDER_TEST'))    # TREX_UNDER_TEST is env var pointing to <trex-core>/scripts
59    if not latest_build_path:
60        latest_build_path = check_trex_path(os.path.join(os.pardir, os.pardir))
61    if not latest_build_path:
62        raise Exception('Could not determine trex_under_test folder, try setting env.var. TREX_UNDER_TEST')
63    return latest_build_path
64
65STATEFUL_STOP_COMMAND = './trex_daemon_server stop; sleep 1; ./trex_daemon_server stop; sleep 1'
66STATEFUL_RUN_COMMAND = 'rm /var/log/trex/trex_daemon_server.log; ./trex_daemon_server start; sleep 2; ./trex_daemon_server show'
67TREX_FILES = ('_t-rex-64', '_t-rex-64-o', '_t-rex-64-debug', '_t-rex-64-debug-o')
68
69def trex_remote_command(trex_data, command, background = False):
70    return misc_methods.run_remote_command(trex_data['trex_name'], ('cd %s; ' % CTRexScenario.scripts_path)+ command, background)
71
72# 1 = running, 0 - not running
73def check_trex_running(trex_data):
74    commands = []
75    for filename in TREX_FILES:
76        commands.append('ps -C %s > /dev/null' % filename)
77    (return_code, stdout, stderr) = trex_remote_command(trex_data, ' || '.join(commands))
78    return not return_code
79
80def kill_trex_process(trex_data):
81    (return_code, stdout, stderr) = trex_remote_command(trex_data, 'ps -u root --format comm,pid,cmd | grep _t-rex-64 | grep -v grep || true')
82    assert return_code == 0, 'last remote command failed'
83    if stdout:
84        for process in stdout.split('\n'):
85            try:
86                proc_name, pid, full_cmd = re.split('\s+', process, maxsplit=2)
87                if proc_name.find('t-rex-64') >= 0:
88                    print 'Killing remote process: %s' % full_cmd
89                    trex_remote_command(trex_data, 'kill %s' % pid)
90            except:
91                continue
92
93def address_to_ip(address):
94    for i in range(10):
95        try:
96            return socket.gethostbyname(address)
97        except:
98            continue
99    return socket.gethostbyname(address)
100
101
102class CTRexTestConfiguringPlugin(Plugin):
103    def options(self, parser, env = os.environ):
104        super(CTRexTestConfiguringPlugin, self).options(parser, env)
105        parser.add_option('--cfg', '--trex-scenario-config', action='store',
106                            dest='config_path',
107                            help='Specify path to folder with config.yaml and benchmark.yaml')
108        parser.add_option('--skip-clean', '--skip_clean', action='store_true',
109                            dest='skip_clean_config',
110                            help='Skip the clean configuration replace on the platform.')
111        parser.add_option('--load-image', '--load_image', action='store_true', default = False,
112                            dest='load_image',
113                            help='Install image specified in config file on router.')
114        parser.add_option('--log-path', '--log_path', action='store',
115                            dest='log_path',
116                            help='Specify path for the tests` log to be saved at. Once applied, logs capturing by nose will be disabled.') # Default is CURRENT/WORKING/PATH/trex_log/trex_log.log')
117        parser.add_option('--verbose-mode', '--verbose_mode', action="store_true", default = False,
118                            dest="verbose_mode",
119                            help="Print RPC command and router commands.")
120        parser.add_option('--server-logs', '--server_logs', action="store_true", default = False,
121                            dest="server_logs",
122                            help="Print server side (TRex and trex_daemon) logs per test.")
123        parser.add_option('--kill-running', '--kill_running', action="store_true", default = False,
124                            dest="kill_running",
125                            help="Kills running TRex process on remote server (useful for regression).")
126        parser.add_option('--func', '--functional', action="store_true", default = False,
127                            dest="functional",
128                            help="Run functional tests.")
129        parser.add_option('--stl', '--stateless', action="store_true", default = False,
130                            dest="stateless",
131                            help="Run stateless tests.")
132        parser.add_option('--stf', '--stateful', action="store_true", default = False,
133                            dest="stateful",
134                            help="Run stateful tests.")
135        parser.add_option('--copy', action="store_true", default = False,
136                            dest="copy",
137                            help="Copy TRex server to temp directory and run from there.")
138        parser.add_option('--no-ssh', '--no_ssh', action="store_true", default = False,
139                            dest="no_ssh",
140                            help="Flag wherever not to connect via ssh to run the daemons etc.")
141
142    def configure(self, options, conf):
143        self.functional = options.functional
144        self.stateless = options.stateless
145        self.stateful = options.stateful
146        self.copy = options.copy
147        self.collect_only = options.collect_only
148        if self.functional or self.collect_only:
149            return
150        if CTRexScenario.setup_dir and options.config_path:
151            raise Exception('Please either define --cfg or use env. variable SETUP_DIR, not both.')
152        if not options.config_path and CTRexScenario.setup_dir:
153            options.config_path = CTRexScenario.setup_dir
154        if options.config_path:
155            self.configuration = misc_methods.load_complete_config_file(os.path.join(options.config_path, 'config.yaml'))
156            self.configuration.trex['trex_name'] = address_to_ip(self.configuration.trex['trex_name'])
157            self.benchmark = misc_methods.load_benchmark_config_file(os.path.join(options.config_path, 'benchmark.yaml'))
158            self.enabled = True
159        else:
160            raise Exception('Please specify path to config.yaml using --cfg parameter or env. variable SETUP_DIR')
161        self.modes         = self.configuration.trex.get('modes', [])
162        self.kill_running  = options.kill_running
163        self.load_image    = options.load_image
164        self.verbose_mode  = options.verbose_mode
165        self.no_ssh        = options.no_ssh
166        self.clean_config  = False if options.skip_clean_config else True
167        self.server_logs   = options.server_logs
168        if options.log_path:
169            self.loggerPath = options.log_path
170
171    def begin (self):
172        # initialize CTRexScenario global testing class, to be used by all tests
173        CTRexScenario.configuration = self.configuration
174        CTRexScenario.benchmark     = self.benchmark
175        CTRexScenario.modes         = set(self.modes)
176        CTRexScenario.server_logs   = self.server_logs
177        if self.copy and not CTRexScenario.is_copied and not self.no_ssh:
178            new_path = '/tmp/trex_scripts'
179            (return_code, stdout, stderr) = trex_remote_command(CTRexScenario.configuration.trex,
180                                                                'mkdir -p %s; rsync -L -az %s/ %s' % (new_path, CTRexScenario.scripts_path, new_path))
181            if return_code:
182                print 'Failed copying'
183                sys.exit(-1)
184            CTRexScenario.scripts_path = new_path
185            CTRexScenario.is_copied = True
186        if self.functional or self.collect_only:
187            return
188        # launch TRex daemon on relevant setup
189        if not self.no_ssh:
190            if self.kill_running:
191                if self.stateful:
192                    trex_remote_command(trex_data, STATEFUL_STOP_COMMAND)
193                kill_trex_process(CTRexScenario.configuration.trex)
194                time.sleep(1)
195            elif check_trex_running(CTRexScenario.configuration.trex):
196                print 'TRex is already running'
197                sys.exit(-1)
198
199
200        if self.stateful:
201            if not self.no_ssh:
202                trex_remote_command(CTRexScenario.configuration.trex, STATEFUL_RUN_COMMAND)
203            CTRexScenario.trex = CTRexClient(trex_host = CTRexScenario.configuration.trex['trex_name'], verbose = self.verbose_mode)
204        elif self.stateless:
205            if not self.no_ssh:
206                trex_remote_command(CTRexScenario.configuration.trex, './t-rex-64 -i', background = True)
207            CTRexScenario.stl_trex = STLClient(username = 'TRexRegression',
208                                               server = CTRexScenario.configuration.trex['trex_name'],
209                                               verbose_level = self.verbose_mode)
210        if 'loopback' not in self.modes:
211            CTRexScenario.router_cfg = dict(config_dict      = self.configuration.router,
212                                            forceImageReload = self.load_image,
213                                            silent_mode      = not self.verbose_mode,
214                                            forceCleanConfig = self.clean_config,
215                                            tftp_config_dict = self.configuration.tftp)
216        try:
217            CustomLogger.setup_custom_logger('TRexLogger', self.loggerPath)
218        except AttributeError:
219            CustomLogger.setup_custom_logger('TRexLogger')
220
221    def finalize(self, result):
222        if self.functional or self.collect_only:
223            return
224        CTRexScenario.is_init = False
225        if not self.no_ssh:
226            if self.stateful:
227                trex_remote_command(CTRexScenario.configuration.trex, STATEFUL_STOP_COMMAND)
228            kill_trex_process(CTRexScenario.configuration.trex)
229
230
231def save_setup_info():
232    try:
233        if CTRexScenario.setup_name and CTRexScenario.trex_version:
234            setup_info = ''
235            for key, value in CTRexScenario.trex_version.items():
236                setup_info += '{0:8}: {1}\n'.format(key, value)
237            cfg = CTRexScenario.configuration
238            setup_info += 'Server: %s, Modes: %s' % (cfg.trex.get('trex_name'), cfg.trex.get('modes'))
239            if cfg.router:
240                setup_info += '\nRouter: Model: %s, Image: %s' % (cfg.router.get('model'), CTRexScenario.router_image)
241            with open('%s/report_%s.info' % (CTRexScenario.report_dir, CTRexScenario.setup_name), 'w') as f:
242                f.write(setup_info)
243    except Exception as err:
244        print 'Error saving setup info: %s ' % err
245
246
247def set_report_dir (report_dir):
248    if not os.path.exists(report_dir):
249        os.mkdir(report_dir)
250
251if __name__ == "__main__":
252
253    # setting defaults. By default we run all the test suite
254    specific_tests              = False
255    CTRexScenario.report_dir    = 'reports'
256    need_to_copy                = False
257    setup_dir                   = os.getenv('SETUP_DIR', '').rstrip('/')
258    CTRexScenario.setup_dir     = check_setup_path(setup_dir)
259    CTRexScenario.scripts_path  = get_trex_path()
260    if not CTRexScenario.setup_dir:
261        CTRexScenario.setup_dir = check_setup_path(os.path.join('setups', setup_dir))
262
263
264    nose_argv = ['', '-s', '-v', '--exe', '--rednose', '--detailed-errors']
265    if '--collect-only' in sys.argv: # this is a user trying simply to view the available tests. no need xunit.
266        CTRexScenario.is_test_list   = True
267        xml_arg                      = ''
268    else:
269        xml_name                     = 'unit_test.xml'
270        if CTRexScenario.setup_dir:
271            CTRexScenario.setup_name = os.path.basename(CTRexScenario.setup_dir)
272            xml_name = 'report_%s.xml' % CTRexScenario.setup_name
273        xml_arg= '--xunit-file=%s/%s' % (CTRexScenario.report_dir, xml_name)
274        set_report_dir(CTRexScenario.report_dir)
275
276    sys_args = sys.argv[:]
277    for i, arg in enumerate(sys.argv):
278        if 'log-path' in arg:
279            nose_argv += ['--nologcapture']
280        else:
281            for tests_type in CTRexScenario.test_types.keys():
282                if tests_type in arg:
283                    specific_tests = True
284                    CTRexScenario.test_types[tests_type].append(arg[arg.find(tests_type):])
285                    sys_args.remove(arg)
286
287    if not specific_tests:
288        for key in ('--func', '--functional'):
289            if key in sys_args:
290                CTRexScenario.test_types['functional_tests'].append('functional_tests')
291                sys_args.remove(key)
292        for key in ('--stf', '--stateful'):
293            if key in sys_args:
294                CTRexScenario.test_types['stateful_tests'].append('stateful_tests')
295                sys_args.remove(key)
296        for key in ('--stl', '--stateless'):
297            if key in sys_args:
298                CTRexScenario.test_types['stateless_tests'].append('stateless_tests')
299                sys_args.remove(key)
300        # Run all of the tests or just the selected ones
301        if not sum([len(x) for x in CTRexScenario.test_types.values()]):
302            for key in CTRexScenario.test_types.keys():
303                CTRexScenario.test_types[key].append(key)
304
305    nose_argv += sys_args
306
307    config_plugin = CTRexTestConfiguringPlugin()
308    red_nose = RedNose()
309    result = True
310    try:
311        if len(CTRexScenario.test_types['functional_tests']):
312            additional_args = ['--func'] + CTRexScenario.test_types['functional_tests']
313            if xml_arg:
314                additional_args += ['--with-xunit', xml_arg.replace('.xml', '_functional.xml')]
315            result = nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin])
316        if len(CTRexScenario.test_types['stateful_tests']):
317            additional_args = ['--stf'] + CTRexScenario.test_types['stateful_tests']
318            if xml_arg:
319                additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateful.xml')]
320            result = result and nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin])
321        if len(CTRexScenario.test_types['stateless_tests']):
322            additional_args = ['--stl', 'stateless_tests/stl_general_test.py:STLBasic_Test.test_connectivity'] + CTRexScenario.test_types['stateless_tests']
323            if xml_arg:
324                additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateless.xml')]
325            result = result and nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin])
326    finally:
327        save_setup_info()
328
329    if (result == True and not CTRexScenario.is_test_list):
330        print termstyle.green("""
331                 ..::''''::..
332               .;''        ``;.
333              ::    ::  ::    ::
334             ::     ::  ::     ::
335             ::     ::  ::     ::
336             :: .:' ::  :: `:. ::
337             ::  :          :  ::
338              :: `:.      .:' ::
339               `;..``::::''..;'
340                 ``::,,,,::''
341
342               ___  ___   __________
343              / _ \/ _ | / __/ __/ /
344             / ___/ __ |_\ \_\ \/_/
345            /_/  /_/ |_/___/___(_)
346
347        """)
348        sys.exit(0)
349    sys.exit(-1)
350
351
352
353
354
355
356
357
358
359