From b88fc2d930537a47b12115a899322fb9cebbc0c1 Mon Sep 17 00:00:00 2001 From: Jerry Flood Date: Wed, 12 Apr 2017 09:46:44 -0400 Subject: TEST-2 load/soak test utility Command line load/soak test utility to run multple ETE tests at once. Change-Id: I4b0224d4fbdf82964acaf7704a5e6d9802d8a0e1 Signed-off-by: Jerry Flood --- loadtest/RunEte.py | 39 ++++++++++++++++++++ loadtest/TestConfig.py | 51 ++++++++++++++++++++++++++ loadtest/TestController.py | 80 ++++++++++++++++++++++++++++++++++++++++ loadtest/TestMain.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++ loadtest/__init__.py | 0 setup.py | 6 +-- 6 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 loadtest/RunEte.py create mode 100644 loadtest/TestConfig.py create mode 100644 loadtest/TestController.py create mode 100644 loadtest/TestMain.py create mode 100644 loadtest/__init__.py diff --git a/loadtest/RunEte.py b/loadtest/RunEte.py new file mode 100644 index 0000000..5012e7d --- /dev/null +++ b/loadtest/RunEte.py @@ -0,0 +1,39 @@ +''' +Created on Apr 7, 2017 + +@author: jf9860 +''' +from threading import Thread +import subprocess +import os +from datetime import datetime +import logging + +class RunEte(Thread): + ''' + classdocs + ''' + robot_test = "" + robot_command = "runEteTag.sh" + soaksubfolder = "" + test_number =0 + + def __init__(self, test_name, soaksubfolder, test_number): + ''' + Constructor + ''' + super(RunEte, self).__init__() + self.robot_test = test_name + self.soaksubfolder = soaksubfolder + self.test_number = test_number + + def run(self): + logging.info("{} ({}) started - {}".format(self.getName(), self.robot_test, str(datetime.now()))) + try: + ''' Add the '/' here so that the shell doesn't require a subfolder... ''' + env = dict(os.environ, SOAKSUBFOLDER=self.soaksubfolder + "/") + output = subprocess.check_output(["bash", self.robot_command, self.robot_test, self.test_number], shell=False, env=env) + logging.info("{} ({}) {}".format(self.getName(), self.robot_test, output)) + except Exception, e: + logging.error("{} ({}) Unexpected error {}".format(self.getName(), self.robot_test, repr(e))) + logging.info("{} ({}) ended - {}".format(self.getName(), self.robot_test, str(datetime.now()))) diff --git a/loadtest/TestConfig.py b/loadtest/TestConfig.py new file mode 100644 index 0000000..c22e875 --- /dev/null +++ b/loadtest/TestConfig.py @@ -0,0 +1,51 @@ +''' +Created on Apr 7, 2017 + +@author: jf9860 +''' +class TestConfig(object): + ''' + The profile defines a cycle of tests. Each entry is defined as + [, [= len(self.config.profile)): + profileindex = 0 + profile = self.config.profile[profileindex] + sleeptime = profile[0] + currenttime = time.time() + if ((currenttime + sleeptime) < endtime): + time.sleep(sleeptime) + self.schedule(profile) + profileindex = profileindex + 1 + currenttime = time.time() + else: + currenttime = endtime + + for threadname in self.threads: + logging.info("TestController waiting on " + threadname) + t = self.threads[threadname] + t.join() + logging.info("Soak test completed") + + def schedule(self, profile): + self.remove_completed_threads() + tests = profile[1] + for test in tests: + self.schedule_one(test) + + def schedule_one(self, test): + self.test_number = self.test_number + 1 + self.threadid = self.threadid + 1 + threadname = "RunEte_" + str(self.threadid) + ''' test for max threads ''' + t = RunEte(test, self.soaksubfolder, str(self.test_number)) + t.setName(threadname) + t.start() + self.threads[threadname] = t + + + def remove_completed_threads(self): + toremove = [] + for threadname in self.threads: + t = self.threads[threadname] + if (t.isAlive() == False): + toremove.append(threadname) + for threadname in toremove: + logging.info("Removing " + threadname) + del(self.threads[threadname]) \ No newline at end of file diff --git a/loadtest/TestMain.py b/loadtest/TestMain.py new file mode 100644 index 0000000..a9b57db --- /dev/null +++ b/loadtest/TestMain.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +loadtest.TestMain -- shortdesc + +loadtest.TestMain is a description + +It defines classes_and_methods + +@author: user_name + +@copyright: 2017 organization_name. All rights reserved. + +@license: license + +@contact: user_email +@deffield updated: Updated +''' + +import sys +import os + +from optparse import OptionParser, Values + +from loadtest.TestController import TestController + +__all__ = [] +__version__ = 0.1 +__date__ = '2017-04-07' +__updated__ = '2017-04-07' + +DEBUG = 1 +TESTRUN = 0 +PROFILE = 0 +import time +import logging + +def main(argv=None): + '''Command line options.''' + program_name = os.path.basename(sys.argv[0]) + program_version = "v0.1" + program_build_date = "%s" % __updated__ + + program_version_string = '%%prog %s (%s)' % (program_version, program_build_date) + #program_usage = '''usage: spam two eggs''' # optional - will be autogenerated by optparse + program_longdesc = '''''' # optional - give further explanation about what the program does + program_license = "Copyright 2017 user_name (organization_name) \ + Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0" + + if argv is None: + argv = sys.argv[1:] + try: + # setup option parser + parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license) + parser.add_option("-d", "--duration", dest="duration", help="duration of soak test in seconds [default: %default]", type=int) + parser.add_option("-l", "--logfile", dest="logfile", help="Full path soak log file name") + parser.set_defaults(duration="60", logfile="") + (opts, args) = parser.parse_args(argv) + + if (opts.logfile != ""): + logging.basicConfig(filename=opts.logfile, level=logging.DEBUG) + else: + logging.basicConfig(level=logging.DEBUG) + controller = TestController(opts) + controller.execute() + + except Exception, e: + indent = len(program_name) * " " + sys.stderr.write(program_name + ": " + repr(e) + "\n") + sys.stderr.write(indent + " for help use --help") + return 2 + + +if __name__ == "__main__": + if DEBUG: + print "debug" + if TESTRUN: + import doctest + doctest.testmod() + if PROFILE: + import cProfile + import pstats + profile_filename = 'loadtest.TestMain_profile.txt' + cProfile.run('main()', profile_filename) + statsfile = open("profile_stats.txt", "wb") + p = pstats.Stats(profile_filename, stream=statsfile) + stats = p.strip_dirs().sort_stats('cumulative') + stats.print_stats() + statsfile.close() + sys.exit(0) + sys.exit(main()) \ No newline at end of file diff --git a/loadtest/__init__.py b/loadtest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py index 677a2e1..9180e46 100644 --- a/setup.py +++ b/setup.py @@ -2,9 +2,9 @@ from setuptools import setup setup( name='python-openecomp-eteutils', # This is the name of your PyPI-package. - version='0.2', # Update the version number for new releases + version='0.2', # Update the version number for new releases description='Scripts written to be used during ete testing', # Info about script install_requires=['dnspython','paramiko', 'pyyaml', 'robotframework', 'deepdiff'], # what we need - packages=['eteutils'], # The name of your scipts package - package_dir={'eteutils': 'eteutils'} # The location of your scipts package + packages=['eteutils', 'loadtest'], # The name of your scipts package + package_dir={'eteutils': 'eteutils', 'loadtest' : 'loadtest'} # The location of your scipts package ) \ No newline at end of file -- cgit 1.2.3-korg