1#!/router/bin/python
2
3__copyright__ = "Copyright 2014"
4
5"""
6Name:
7     trex_general_test.py
8
9
10Description:
11
12    This script creates the functionality to test the performance of the TRex traffic generator
13    The tested scenario is a TRex TG directly connected to a Cisco router.
14
15::
16
17    Topology:
18
19       -------                         --------
20      |       | Tx---1gig/10gig----Rx |        |
21      | TRex  |                       | router |
22      |       | Rx---1gig/10gig----Tx |        |
23       -------                         --------
24
25"""
26from nose.plugins import Plugin
27from nose.plugins.skip import SkipTest
28import trex
29from trex import CTRexScenario
30import misc_methods
31import pprint
32import sys
33import os
34# from CPlatformUnderTest import *
35from CPlatform import *
36import termstyle
37import threading
38from .tests_exceptions import *
39from platform_cmd_link import *
40import unittest
41from glob import glob
42from datetime import datetime, timedelta
43
44def setUpModule(module):
45    pass
46
47def tearDownModule(module):
48    pass
49
50class CTRexGeneral_Test(unittest.TestCase):
51    """This class defines the general stateful testcase of the TRex traffic generator"""
52    def __init__ (self, *args, **kwargs):
53        sys.stdout.flush()
54        unittest.TestCase.__init__(self, *args, **kwargs)
55        if CTRexScenario.is_test_list:
56            return
57        # Point test object to scenario global object
58        self.configuration         = CTRexScenario.configuration
59        self.benchmark             = CTRexScenario.benchmark
60        self.trex                  = CTRexScenario.trex
61        self.stl_trex              = CTRexScenario.stl_trex
62        self.trex_crashed          = CTRexScenario.trex_crashed
63        self.modes                 = CTRexScenario.modes
64        self.GAManager             = CTRexScenario.GAManager
65        self.elk                   = CTRexScenario.elk
66        self.no_daemon             = CTRexScenario.no_daemon
67        self.skipping              = False
68        self.fail_reasons          = []
69        if not hasattr(self, 'unsupported_modes'):
70            self.unsupported_modes   = []
71        self.is_loopback           = True if 'loopback' in self.modes else False
72        self.is_virt_nics          = True if 'virt_nics' in self.modes else False
73        self.is_vf_nics            = True if 'vf_nics' in self.modes else False
74        self.is_VM                 = True if 'VM' in self.modes else False
75
76        if not CTRexScenario.is_init:
77            if self.trex and not self.no_daemon: # stateful
78                CTRexScenario.trex_version = self.trex.get_trex_version()
79            #update elk const object
80            if self.elk:
81                timediff  = timedelta(hours=2) # workaround to get IL timezone
82                date_str  = CTRexScenario.trex_version['Date'].strip()
83                timestamp = datetime.strptime(date_str, '%b %d %Y , %H:%M:%S') - timediff
84
85                img               = CTRexScenario.elk_info['info']['image']
86                img['sha']        = CTRexScenario.trex_version['Git SHA']
87                img['build_time'] = timestamp.strftime("%Y-%m-%d %H:%M:%S")
88                img['version']    = CTRexScenario.trex_version['Version']
89
90                setup = CTRexScenario.elk_info['info']['setup']
91                if self.is_loopback :
92                    setup['dut'] = 'loopback'
93                else:
94                    setup['dut'] = 'router'
95
96                if self.is_VM:
97                    setup['baremetal'] = False
98                    setup['hypervisor'] = 'ESXi'       #TBD
99                else:
100                    setup['baremetal'] = True
101            if not self.is_loopback:
102                # initilize the scenario based on received configuration, once per entire testing session
103                CTRexScenario.router = CPlatform(CTRexScenario.router_cfg['silent_mode'])
104                device_cfg           = CDeviceCfg()
105                device_cfg.set_platform_config(CTRexScenario.router_cfg['config_dict'])
106                device_cfg.set_tftp_config(CTRexScenario.router_cfg['tftp_config_dict'])
107                CTRexScenario.router.load_platform_data_from_file(device_cfg)
108                CTRexScenario.router.launch_connection(device_cfg)
109                if CTRexScenario.router_cfg['forceImageReload']:
110                    image_d = CTRexScenario.router.get_running_image_details();
111                    running_image = image_d['image']
112                    print('Current router image: %s' % running_image)
113                    if self.elk:
114                        setup['dut'] = image_d.get('model','router');
115                        print('Current router model : %s' % setup['dut'])
116                    needed_image = device_cfg.get_image_name()
117                    if not CTRexScenario.router.is_image_matches(needed_image):
118                        print('Setting router image: %s' % needed_image)
119                        CTRexScenario.router.config_tftp_server(device_cfg)
120                        CTRexScenario.router.load_platform_image(needed_image)
121                        CTRexScenario.router.set_boot_image(needed_image)
122                        CTRexScenario.router.reload_platform(device_cfg)
123                        CTRexScenario.router.launch_connection(device_cfg)
124                        running_image = CTRexScenario.router.get_running_image_details()['image'] # verify image
125                        if not CTRexScenario.router.is_image_matches(needed_image):
126                            self.fail('Unable to set router image: %s, current image is: %s' % (needed_image, running_image))
127                    else:
128                        print('Matches needed image: %s' % needed_image)
129                    CTRexScenario.router_image = running_image
130
131            if self.modes:
132                print(termstyle.green('\t!!!\tRunning with modes: %s, not suitable tests will be skipped.\t!!!' % list(self.modes)))
133
134            CTRexScenario.is_init = True
135            print(termstyle.green("Done instantiating TRex scenario!\n"))
136
137#           raise RuntimeError('CTRexScenario class is not initialized!')
138        self.router = CTRexScenario.router
139
140    def get_elk_obj (self):
141        obj=trex.copy_elk_info ()
142        return (obj);
143
144#   def assert_dict_eq (self, dict, key, val, error=''):
145#           v1 = int(dict[key]))
146#           self.assertEqual(v1, int(val), error)
147#
148#   def assert_dict_gt (self, d, key, val, error=''):
149#           v1 = int(dict[key])
150#           self.assert_gt(v1, int(val), error)
151
152    def assertEqual(self, v1, v2, s):
153        if v1 != v2:
154            error='ERROR '+str(v1)+' !=  '+str(v2)+ '   '+s;
155            self.fail(error)
156
157    def assert_gt(self, v1, v2, s):
158        if not v1 > v2:
159            error='ERROR {big} <  {small}      {str}'.format(big = v1, small = v2, str = s)
160            self.fail(error)
161
162    def check_results_eq (self,res,name,val):
163        if res is None:
164            self.fail('TRex results cannot be None !')
165            return
166
167        if name not in res:
168            self.fail('TRex results does not include key %s' % name)
169            return
170
171        if res[name] != float(val):
172            self.fail('TRex results[%s]==%f and not as expected %f ' % (name, res[name], val))
173
174    def check_CPU_benchmark (self, trex_res, err = 25, minimal_cpu = 10, maximal_cpu = 85):
175        cpu_util          = trex_res.get_avg_steady_state_value('trex-global.data.m_cpu_util_raw')
176        trex_tx_bps       = trex_res.get_avg_steady_state_value('trex-global.data.m_tx_bps')
177        trex_tx_pps       = trex_res.get_avg_steady_state_value('trex-global.data.m_tx_pps')
178        expected_norm_cpu = self.get_benchmark_param('bw_per_core')
179        cores             = self.get_benchmark_param('cores')
180        ports_count       = trex_res.get_ports_count()
181        total_dp_cores    = cores * (ports_count/2);
182        if not (cpu_util and ports_count and cores):
183            print("Can't calculate CPU benchmark, need to divide by zero: cpu util: %s, ports: %s, cores: %s" % (cpu_util, ports_count, cores))
184            test_norm_cpu = -1
185        else:
186            test_norm_cpu = trex_tx_bps / (cpu_util * ports_count * cores * 2.5e6)
187
188        if '1G' in self.modes:
189            minimal_cpu /= 10.0
190
191        if not self.is_virt_nics:
192            if cpu_util > maximal_cpu:
193                self.fail("CPU is too high (%s%%), probably queue full." % cpu_util )
194            #if cpu_util < minimal_cpu:
195            #    self.fail("CPU is too low (%s%%), can't verify performance in such low CPU%%." % cpu_util )
196
197        print("TRex CPU utilization: %g%%, norm_cpu is : %g Gb/core" % (round(cpu_util, 2), round(test_norm_cpu, 2)))
198        if test_norm_cpu < 0:
199            return
200
201        if not expected_norm_cpu:
202            expected_norm_cpu = 1
203
204        calc_error_precent = abs(100.0 * test_norm_cpu / expected_norm_cpu - 100)
205        print('Err percent: %s' % calc_error_precent)
206        #if calc_error_precent > err and cpu_util > 10:
207        #    self.fail('Excepted bw_per_core ratio: %s, got: %g' % (expected_norm_cpu, round(test_norm_cpu)))
208
209        trex_tx_gbps       = trex_tx_bps/1e9
210        trex_tx_mpps       = trex_tx_pps/1e6
211
212        trex_tx_gbps_pc  = trex_tx_gbps*100.0/(cpu_util*total_dp_cores);
213        trex_tx_mpps_pc  = trex_tx_mpps*100.0/(cpu_util*total_dp_cores)
214
215        trex_tx_pckt    = trex_res.get_last_value("trex-global.data.m_total_tx_pkts")
216        trex_drops      = trex_res.get_total_drops()
217        trex_drop_precent = trex_drops *100.0/trex_tx_pckt;
218
219        # report benchmarks
220        if self.elk:
221            elk_obj = self.get_elk_obj()
222            print("Reporting to elk")
223            elk_obj['test']={ "name" : self.get_name(),
224                        "type"  : "stateful",
225                        "cores" : total_dp_cores,
226                        "cpu%"  : cpu_util,
227                        "mpps" :  (trex_tx_mpps),
228                        "streams_count" :1,
229                        "mpps_pc" :  (trex_tx_mpps_pc),
230                        "gbps_pc" :  (trex_tx_gbps_pc),
231                        "gbps" :  (trex_tx_gbps),
232                        "kcps"  : (trex_res.get_last_value("trex-global.data.m_tx_cps")/1000.0),
233                        "avg-pktsize" : round((1000.0*trex_tx_gbps/(8.0*trex_tx_mpps))),
234                        "latecny" : { "min" : min(trex_res.get_min_latency().values()),
235                                      "max" : max(trex_res.get_max_latency().values()),
236                                      "avr" : max(trex_res.get_avg_latency().values()),
237                                      "jitter" : max(trex_res.get_jitter_latency().values()),
238                                      "max-win" : max(trex_res.get_avg_window_latency ().values()),
239                                      "drop-rate" :trex_drop_precent
240                                     }
241                };
242            pprint.pprint(elk_obj['test']);
243            self.elk.perf.push_data(elk_obj)
244
245
246    def check_results_gt (self, res, name, val):
247        if res is None:
248            self.fail('TRex results canot be None !')
249            return
250
251        if name not in res:
252            self.fail('TRex results does not include key %s' % name)
253            return
254
255        if res[name]< float(val):
256            self.fail('TRex results[%s]<%f and not as expected greater than %f ' % (name, res[name], val))
257
258    def check_for_trex_crash(self):
259        pass
260
261    def get_benchmark_param (self, param, sub_param = None, test_name = None,default=None):
262        if not test_name:
263            test_name = self.get_name()
264        if test_name not in self.benchmark:
265            if default ==None:
266               self.skip('No data in benchmark.yaml for test: %s, param: %s. Skipping.' % (test_name, param))
267            else:
268                return default
269        if sub_param:
270            return self.benchmark[test_name][param].get(sub_param)
271        else:
272            return self.benchmark[test_name].get(param)
273
274    def check_general_scenario_results (self, trex_res, check_latency = True):
275
276        try:
277            # check history size is enough
278            if len(trex_res._history) < 5:
279                self.fail('TRex results list is too short. Increase the test duration or check unexpected stopping.')
280
281            # check if test is valid
282            if not trex_res.is_done_warmup():
283                self.fail('TRex did not reach warm-up situtaion. Results are not valid.')
284
285            # check that BW is not much more than expected
286            trex_exp_bps = trex_res.get_expected_tx_rate().get('m_tx_expected_bps') / 1e6
287            trex_cur_bps = max(trex_res.get_value_list('trex-global.data.m_tx_bps')) / 1e6
288
289            assert trex_exp_bps > 0, 'Expected BPS is zero: %s' % trex_exp_bps
290
291            if trex_exp_bps * 1.05 + 10 < trex_cur_bps:
292                msg = 'Got BW (%gMbps) that is %g%% more than expected (%gMbps)!' % (round(trex_cur_bps, 2), round(100.0 * trex_cur_bps / trex_exp_bps - 100, 2), round(trex_exp_bps, 2))
293                print('WARNING: %s' % msg)
294                #self.fail(msg)
295
296            # check TRex number of drops
297            trex_tx_pckt    = trex_res.get_last_value("trex-global.data.m_total_tx_pkts")
298            trex_drops      = trex_res.get_total_drops()
299            trex_drop_rate  = trex_res.get_drop_rate()
300            if ( trex_drops > 0.001 * trex_tx_pckt) and (trex_drop_rate > 0.0):     # deliberately mask kickoff drops when TRex first initiated
301                self.fail('Number of packet drops larger than 0.1% of all traffic')
302
303            # check queue full, queue drop, allocation error
304            m_total_alloc_error = trex_res.get_last_value("trex-global.data.m_total_alloc_error")
305            m_total_queue_full = trex_res.get_last_value("trex-global.data.m_total_queue_full")
306            m_total_queue_drop = trex_res.get_last_value("trex-global.data.m_total_queue_drop")
307            self.assert_gt(1000, m_total_alloc_error, 'Got allocation errors. (%s), please review multiplier and templates configuration.' % m_total_alloc_error)
308            self.assert_gt(1000, m_total_queue_drop, 'Too much queue_drop (%s), please review multiplier.' % m_total_queue_drop)
309
310            if self.is_VM:
311                allowed_queue_full = 10000 + trex_tx_pckt / 100
312            else:
313                allowed_queue_full = 1000 + trex_tx_pckt / 1000
314            self.assert_gt(allowed_queue_full, m_total_queue_full, 'Too much queue_full (%s), please review multiplier.' % m_total_queue_full)
315
316            # # check TRex expected counters
317            #trex_exp_rate = trex_res.get_expected_tx_rate().get('m_tx_expected_bps')
318            #assert trex_exp_rate is not None
319            #trex_exp_gbps = trex_exp_rate/(10**9)
320
321            if check_latency:
322                # check that max latency does not exceed 1 msec
323                if self.configuration.trex['trex_name'] == '10.56.217.210': # temporary workaround for latency issue in kiwi02, remove it ASAP. http://trex-tgn.cisco.com/youtrack/issue/trex-194
324                    allowed_latency = 8000
325                elif self.is_VM or self.is_virt_nics:
326                    allowed_latency = 9999999
327                else: # no excuses, check 1ms
328                    allowed_latency = 1000
329                if max(trex_res.get_max_latency().values()) > allowed_latency:
330                    self.fail('LatencyError: Maximal latency exceeds %s (usec)' % allowed_latency)
331
332                # check that avg latency does not exceed 1 msec
333                if self.is_VM or self.is_virt_nics:
334                    allowed_latency = 9999999
335                else: # no excuses, check 1ms
336                    allowed_latency = 1000
337                if max(trex_res.get_avg_latency().values()) > allowed_latency:
338                    self.fail('LatencyError: Average latency exceeds %s (usec)' % allowed_latency)
339
340                ports_names = trex_res.get_last_value('trex-latecny-v2.data', 'port\-\d+')
341                if not ports_names:
342                    raise AbnormalResultError('Could not find ports info in TRex results, path: trex-latecny-v2.data.port-*')
343                for port_name in ports_names:
344                    path = 'trex-latecny-v2.data.%s.hist.cnt' % port_name
345                    lat_count = trex_res.get_last_value(path)
346                    if lat_count == 0:
347                        self.fail('LatencyError: Number of latency packets received on %s is 0' % port_name)
348
349            if not self.is_loopback:
350                # check router number of drops --> deliberately masked- need to be figured out!!!!!
351                pkt_drop_stats = self.router.get_drop_stats()
352#               assert pkt_drop_stats['total_drops'] < 20
353
354                # check for trex-router packet consistency
355                # TODO: check if it's ok
356                print('router drop stats: %s' % pkt_drop_stats)
357                print('TRex drop stats: %s' % trex_drops)
358                #self.assertEqual(pkt_drop_stats, trex_drops, "TRex's and router's drop stats don't match.")
359
360        except KeyError as e:
361            self.fail(e)
362            #assert False
363
364        # except AssertionError as e:
365        #     e.args += ('TRex has crashed!')
366        #     raise
367
368    @staticmethod
369    def unzip_client_package():
370        client_pkg_files = glob('%s/trex_client*.tar.gz' % CTRexScenario.scripts_path)
371        if not len(client_pkg_files):
372            raise Exception('Could not find client package')
373        if len(client_pkg_files) > 1:
374            raise Exception('Found more than one client packages')
375        if not os.path.exists('%s/trex_client' % CTRexScenario.scripts_path):
376            print('\nUnzipping package')
377            return_code, _, stderr = misc_methods.run_command("tar -xzf %s -C %s" % (client_pkg_files[0], CTRexScenario.scripts_path))
378            if return_code:
379                raise Exception('Could not untar the client package: %s' % stderr)
380        else:
381            print('\nClient package is untarred')
382
383    # We encountered error, don't fail the test immediately
384    def fail(self, reason = 'Unknown error'):
385        print('Error: %s' % reason)
386        self.fail_reasons.append(reason)
387
388    # skip running of the test, counts as 'passed' but prints 'skipped'
389    def skip(self, message = 'Unknown reason'):
390        print('Skip: %s' % message)
391        self.skipping = True
392        raise SkipTest(message)
393
394    # get name of currently running test
395    def get_name(self):
396        return self._testMethodName
397
398    def setUp(self):
399        test_setup_modes_conflict = self.modes & set(self.unsupported_modes)
400        if test_setup_modes_conflict:
401            self.skip("The test can't run with following modes of given setup: %s " % test_setup_modes_conflict)
402        if not self.stl_trex and not self.trex.is_idle():
403            print('Warning: TRex is not idle at setUp, trying to stop it.')
404            self.trex.force_kill(confirm = False)
405        if not self.is_loopback:
406            print('')
407            if not self.stl_trex and CTRexScenario.router_cfg['forceCleanConfig']:
408                self.router.load_clean_config()
409            self.router.clear_counters()
410            self.router.clear_packet_drop_stats()
411
412    ########################################################################
413    ####                DO NOT ADD TESTS TO THIS FILE                   ####
414    ####    Added tests here will held once for EVERY test sub-class    ####
415    ########################################################################
416
417    # masked example to such test. uncomment to watch how it affects #
418#   def test_isInitialized(self):
419#       assert CTRexScenario.is_init == True
420    def tearDown(self):
421        if not self.stl_trex and not self.trex.is_idle():
422            print('Warning: TRex is not idle at tearDown, trying to stop it.')
423            self.trex.force_kill(confirm = False)
424        if not self.skipping:
425            # print server logs of test run
426            if self.trex and CTRexScenario.server_logs and not self.no_daemon:
427                try:
428                    print(termstyle.green('\n>>>>>>>>>>>>>>> Daemon log <<<<<<<<<<<<<<<'))
429                    daemon_log = self.trex.get_trex_daemon_log()
430                    log_size = len(daemon_log)
431                    print(''.join(daemon_log[CTRexScenario.daemon_log_lines:]))
432                    CTRexScenario.daemon_log_lines = log_size
433                except Exception as e:
434                    print("Can't get TRex daemon log:", e)
435                try:
436                    print(termstyle.green('>>>>>>>>>>>>>>>> Trex log <<<<<<<<<<<<<<<<'))
437                    print(''.join(self.trex.get_trex_log()))
438                except Exception as e:
439                    print("Can't get TRex log:", e)
440            if len(self.fail_reasons):
441                sys.stdout.flush()
442                raise Exception('Test failed. Reasons:\n%s' % '\n'.join(self.fail_reasons))
443        sys.stdout.flush()
444
445    def check_for_trex_crash(self):
446        pass
447