TEST-2 load/soak test utility 97/3397/3
authorJerry Flood <jf9860@att.com>
Wed, 12 Apr 2017 13:46:44 +0000 (09:46 -0400)
committerJerry Flood <jf9860@att.com>
Wed, 12 Apr 2017 14:36:32 +0000 (10:36 -0400)
Command line load/soak test utility to run multple ETE
tests at once.

Change-Id: I4b0224d4fbdf82964acaf7704a5e6d9802d8a0e1
Signed-off-by: Jerry Flood <jf9860@att.com>
loadtest/RunEte.py [new file with mode: 0644]
loadtest/TestConfig.py [new file with mode: 0644]
loadtest/TestController.py [new file with mode: 0644]
loadtest/TestMain.py [new file with mode: 0644]
loadtest/__init__.py [new file with mode: 0644]
setup.py

diff --git a/loadtest/RunEte.py b/loadtest/RunEte.py
new file mode 100644 (file)
index 0000000..5012e7d
--- /dev/null
@@ -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 (file)
index 0000000..c22e875
--- /dev/null
@@ -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 (file)
index 0000000..751b13a
--- /dev/null
@@ -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 (file)
index 0000000..a9b57db
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
index 677a2e1..9180e46 100644 (file)
--- 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