summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJerry Flood <jf9860@att.com>2017-04-12 09:46:44 -0400
committerJerry Flood <jf9860@att.com>2017-04-12 10:36:32 -0400
commitb88fc2d930537a47b12115a899322fb9cebbc0c1 (patch)
treeb6bfa126823c1937159e0a6cb0fbcac6621d551d
parent26151066fb0c53c62cc5ecbe1637482be59c497c (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.py39
-rw-r--r--loadtest/TestConfig.py51
-rw-r--r--loadtest/TestController.py80
-rw-r--r--loadtest/TestMain.py91
-rw-r--r--loadtest/__init__.py0
-rw-r--r--setup.py6
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
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