diff options
author | Jerry Flood <jf9860@att.com> | 2017-04-12 09:46:44 -0400 |
---|---|---|
committer | Jerry Flood <jf9860@att.com> | 2017-04-12 10:36:32 -0400 |
commit | b88fc2d930537a47b12115a899322fb9cebbc0c1 (patch) | |
tree | b6bfa126823c1937159e0a6cb0fbcac6621d551d | |
parent | 26151066fb0c53c62cc5ecbe1637482be59c497c (diff) |
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 <jf9860@att.com>
-rw-r--r-- | loadtest/RunEte.py | 39 | ||||
-rw-r--r-- | loadtest/TestConfig.py | 51 | ||||
-rw-r--r-- | loadtest/TestController.py | 80 | ||||
-rw-r--r-- | loadtest/TestMain.py | 91 | ||||
-rw-r--r-- | loadtest/__init__.py | 0 | ||||
-rw-r--r-- | setup.py | 6 |
6 files changed, 264 insertions, 3 deletions
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 + [<seconds to wait>, [<list of ete tags to run after the wait]], + ''' + profile = [ + [0, ["health"]], + [5, ["instantiate", "distribute"]], + [300, ["distribute"]], + [300, ["distribute"]], + [300, ["distribute"]], + [300, ["distribute"]], + [300, ["distribute"]], + ] + + duration=10 + cyclelength=60 + + def __init__(self, duration=10, cyclelength=1800, json=None): + ''' + Constructor + ''' + self.duration = duration + self.cyclelength = cyclelength + running_time = 0 + for p in self.profile: + secs = p[0] + running_time = running_time + secs + if (running_time < cyclelength): + last = cyclelength - running_time + self.profile.append([last, []]) + + def to_string(self): + pstring = 'Cycle length is {} seconds'.format(self.cyclelength) + pstring = '{}\nDuration is {} seconds'.format(pstring, self.duration) + running_time = 0 + for p in self.profile: + secs = p[0] + running_time = running_time + secs + for ete in p[1]: + pstring = "{0}\n{1:08d} : {2:08d} : {3}".format(pstring, secs, running_time, ete) + if (len(p[1]) == 0): + pstring = "{0}\n{1:08d} : {2:08d} : {3}".format(pstring, secs, running_time, "") + return pstring + + diff --git a/loadtest/TestController.py b/loadtest/TestController.py new file mode 100644 index 0000000..751b13a --- /dev/null +++ b/loadtest/TestController.py @@ -0,0 +1,80 @@ +''' +Created on Apr 7, 2017 + +@author: jf9860 +''' +import time +import os +from loadtest.RunEte import RunEte +from loadtest.TestConfig import TestConfig +import logging + +class TestController(object): + ''' + classdocs + ''' + + threads = {} + threadid = 0 + soaksubfolder = 'soak_' + str(os.getpid()) + test_number = 0 + + def __init__(self, options): + ''' + Constructor + ''' + self.config = TestConfig(duration=options.duration) + logging.info(self.config.to_string()) + + def execute(self): + starttime = time.time() + endtime = starttime + self.config.duration + profileindex = 0 + currenttime = time.time() + logging.info("{}:{}:{}".format(starttime, endtime, currenttime)) + while currenttime < endtime: + if (profileindex >= 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 --- /dev/null +++ b/loadtest/__init__.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 |