diff --git a/jmeter/loadLocalDeviceProperties.py b/jmeter/loadLocalDeviceProperties.py new file mode 100644 index 0000000..d237e50 --- /dev/null +++ b/jmeter/loadLocalDeviceProperties.py @@ -0,0 +1,67 @@ +import os +import json + +""" +Adds local properties to jmeter runtime. +Props added to user.properties can be referenced directly by Jmeter script +with no need of a separate CSV File Reader, making the test localization seamless. + +-- device location properties: + - country name + - country-code + - area + - locality + - ... + +-- Region-specific properties: + When adding a .properties file prefixed with the area, i.e {area}.properties the file would be appended to the devices only matching + the area in the filename: + Example : + usa.properties would be appended only to the devices located in US. + india.properties would be available only on devices in India + Useful for a data-driven test scripts where users from different regions would hit different hosts + +""" + +jmeter_path = "/opt/apache-jmeter/bin/" +worker_dir_path = "jmeterWorker" +device_location_file = "'~/.neocortix/device-location.json'" + + +def get_device_info(): + device_info = {} + with open(os.path.expanduser(device_location_file), "rt") as f: + try: + device_info = json.load(f) + except Exception: + print(f"Failed to load file {device_location_file} as JSON") + print("Missing localization properties!") + return device_info + + +def add_props_to_jmeter_properties(local_props): + # appends the properties to user.properties + with open(os.path.join(jmeter_path, "user.properties"), "a") as outfile: + outfile.write("\n") + + for key, value in local_props.items(): + outfile.write(f"{key}={value}\n") + + # appending all the local properties, matching the device location + # for more strict requirements, locality, country-code can be used, at defined hierarchy. + for area_range in ["country", "country-code", "area", "locality"]: + try: + local_properties_file = os.path.join(worker_dir_path, f"{local_props[area_range]}.properties") + except KeyError: + print(f"{area_range} data not available on the device!") + continue + if os.path.isfile(local_properties_file): + with open(local_properties_file, "r") as infile: + for line in infile.readlines(): + outfile.write(line + "\n") + + +if __name__ == "__main__": + localization_data = get_device_info() + localization_data["country"] = os.environ["CURRENT_LOCATION"] + add_props_to_jmeter_properties(localization_data) diff --git a/jmeter/localizedJmeterProcessor.py b/jmeter/localizedJmeterProcessor.py new file mode 100644 index 0000000..f9376d1 --- /dev/null +++ b/jmeter/localizedJmeterProcessor.py @@ -0,0 +1,76 @@ +import ncscli.batchRunner as batchRunner +import glob +import os + + +class LocalJMeterFrameProcessor(batchRunner.frameProcessor): + ''' + Defines installation and execution of a jmeter test, for a single region within a global context + + + ''' + + def __init__(self, current_instance_count, current_location, number_of_local_instances, number_of_global_instances, test_properties): + self.current_location = current_location + self.instance_begin_count = current_instance_count + self.local_instances = number_of_local_instances + self.number_of_global_instances = number_of_global_instances + self.test_properties = test_properties + + homeDirPath = '/root' + workerDirPath = 'jmeterWorker' + JMeterFilePath = workerDirPath+'/XXX.jmx' + JVM_ARGS ='-Xms30m -XX:MaxMetaspaceSize=64m -Dnashorn.args=--no-deprecation-warning' + # a shell command that uses python psutil to get a recommended java heap size + # computes available ram minus some number for safety, but not less than some minimum + clause = "python3 -c 'import psutil; print( max( 32000000, psutil.virtual_memory().available-400000000 ) )'" + + def installerCmd( self ): + cmd = 'free --mega -t 1>&2' + cmd += f" && {self._get_copy_jars_cmd()}" + cmd += " && JVM_ARGS='%s -Xmx$(%s)' /opt/apache-jmeter/bin/jmeter.sh --version" % (self.JVM_ARGS, self.clause) + + # tougher pretest + pretestFilePath = self.workerDirPath+'/pretest.jmx' + if os.path.isfile( pretestFilePath ): + cmd += " && cd %s && JVM_ARGS='%s -Xmx$(%s)' /opt/apache-jmeter/bin/jmeter -n -t %s/%s/pretest.jmx -l jmeterOut/pretest_results.csv -D httpclient4.time_to_live=1 -D httpclient.reset_state_on_thread_group_iteration=true" % ( + self.workerDirPath, self.JVM_ARGS, self.clause, self.homeDirPath, self.workerDirPath + ) + return cmd + + def frameOutFileName( self, frameNum ): + return 'jmeterOut_%03d' % frameNum + self.instance_begin_count + #return 'TestPlan_results_%03d.csv' % frameNum + + def frameCmd( self, frameNum ): + cmd = f"{self._get_id_config_command(frameNum)} && " + cmd += f"{self._get_split_files_cmd()} && " + cmd += f"""cd {self.workerDirPath} && mkdir -p jmeterOut && JVM_ARGS="{self.JVM_ARGS} -Xmx$({self.clause})" /opt/apache-jmeter/bin/jmeter.sh -n -t {self.homeDirPath}/{self.workerDirPath}/{self.JMeterFilePath} -l jmeterOut/TestPlan_results.csv -D httpclient4.time_to_live=1 -D httpclient.reset_state_on_thread_group_iteration=true""" + cmd += f" && mv jmeterOut ~/{self.frameOutFileName(frameNum)}" + return cmd + + def _get_copy_jars_cmd(self): + cmd = "" + if glob.glob(os.path.join( self.workerDirPath, '*.jar')): + cmd += ' && cp -p %s/*.jar /opt/apache-jmeter/lib/ext' % self.workerDirPath + return cmd + + def _get_update_properties_cmd(self): + cmd = f"{self._get_update_localized_properties_cmd()}" + return cmd + + def _get_update_localization_properties_cmd(self): + cmd = f"python3 {self.homeDirPath}/{self.workerDirPath}/loadLocalDeviceProperties.py" + return cmd + + def _get_id_config_command(self, ordered_instance_id): + cmd = f"""export GLOBAL_INSTANCE_ID={ordered_instance_id + self.instance_begin_count} && \ + export LOCAL_INSTANCE_ID={ordered_instance_id} && \ + export GLOBAL_INSTANCE_COUNT={self.number_of_global_instances} && \ + export LOCAL_INSTANCE_COUNT={self.local_instances} && \ + export CURRENT_LOCATION={self.current_location}""" + return cmd + + def _get_split_files_cmd(self): + cmd = f"python3 {self.homeDirPath}/{self.workerDirPath}/splitFiles.py" + return cmd \ No newline at end of file diff --git a/jmeter/runGlobalJmeter.py b/jmeter/runGlobalJmeter.py new file mode 100644 index 0000000..cd28286 --- /dev/null +++ b/jmeter/runGlobalJmeter.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +'''Loads configuration from test_plan.json and executes the Jmeter script''' +import argparse +import datetime +import glob +import logging +import math +import os +import shutil +import subprocess +import sys +import json +import ncscli.batchRunner as batchRunner +from localizedJmeterProcessor import LocalJMeterFrameProcessor as JMeterFrameProcessor + +logger = logging.getLogger(__name__) +logFmt = '%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s' +logDateFmt = '%Y/%m/%d %H:%M:%S' +formatter = logging.Formatter(fmt=logFmt, datefmt=logDateFmt ) +logging.basicConfig(format=logFmt, datefmt=logDateFmt) +logger.setLevel(logging.INFO) + +test_plan = {} + + +def scriptDirPath(): + '''returns the absolute path to the directory containing this script''' + return os.path.dirname(os.path.realpath(__file__)) + + +try: + with open("test_plan.json", "r") as test_plan_file: + test_plan = json.load(test_plan_file) +except Exception as e: + logger.error(f"Failed to load test plan file! exception : {e}") + sys.exit(1) + + +ap = argparse.ArgumentParser( description=__doc__, + fromfile_prefix_chars='@', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) +ap.add_argument( '--authToken', help='the NCS authorization token to use (or none, to use NCS_AUTH_TOKEN env var' ) +ap.add_argument( '--outDataDir', required=True, help='a path to the output data dir for this run (required)' ) +ap.add_argument( '--jtlFile', help='the file name of the jtl file produced by the test plan (if any)', + default='TestPlan_results.csv') +ap.add_argument( '--workerDir', help='the directory to upload to workers', + default='jmeterWorker' + ) +# for analysis and plotting +ap.add_argument( '--rampStepDuration', type=float, default=60, help='duration of ramp step, in seconds' ) +ap.add_argument( '--SLODuration', type=float, default=240, help='SLO duration, in seconds' ) +ap.add_argument( '--SLOResponseTimeMax', type=float, default=2.5, help='SLO RT threshold, in seconds' ) +# environmental +ap.add_argument( '--jmeterBinPath', help='path to the local jmeter.sh for generating html report' ) +ap.add_argument( '--cookie' ) +args = ap.parse_args() + + +workerDirPath = args.workerDir.rstrip( '/' ) # trailing slash could cause problems with rsync +if workerDirPath: + if not os.path.isdir( workerDirPath ): + logger.error( 'the workerDirPath "%s" is not a directory', workerDirPath ) + sys.exit( 1 ) + JMeterFrameProcessor.workerDirPath = workerDirPath +else: + logger.error( 'this version requires a workerDirPath' ) + sys.exit( 1 ) +logger.debug( 'workerDirPath: %s', workerDirPath ) + +jmxFilePath = test_plan["testFile"] +jmxFullerPath = os.path.join( workerDirPath, jmxFilePath ) +if not os.path.isfile( jmxFullerPath ): + logger.error( 'the jmx file "%s" was not found in %s', jmxFilePath, workerDirPath ) + sys.exit( 1 ) +logger.debug( 'using test plan "%s"', jmxFilePath ) + + +jmeterBinPath = args.jmeterBinPath +if not jmeterBinPath: + jmeterVersion = '5.4.1' # 5.3 and 5.4.1 have been tested, others may work as well + jmeterBinPath = scriptDirPath()+'/apache-jmeter-%s/bin/jmeter.sh' % jmeterVersion + + +# use given planDuration unless it is not positive, in which case extract from the jmx +planDuration = test_plan["testDuration"] +frameTimeLimit = max( round( planDuration * 1.5 ), planDuration+8*60 ) # some slop beyond the planned duration + +JMeterFrameProcessor.JMeterFilePath = jmxFilePath + + +device_requirements = test_plan["device_requirements"] +device_count = test_plan["device_count"] + +total_devices_required = 0 + + +for device_prop in device_count: + total_devices_required += device_prop["count"] + +"""" + + +HERE SHOULD BE THE CODE TO EXECUTE BATCHES IN PARALLEL + + +""" + +def executeBatch(frameProcessor, filters, timeLimit, nWorkers): + try: + rc = batchRunner.runBatch( + frameProcessor = frameProcessor, + commonInFilePath = frameProcessor.workerDirPath, + authToken = args.authToken or os.getenv( 'NCS_AUTH_TOKEN' ) or 'YourAuthTokenHere', + cookie = args.cookie, + encryptFiles=False, + timeLimit = timeLimit + 40*60, + instTimeLimit = 6*60, + frameTimeLimit = frameTimeLimit, + filter = filters, + outDataDir = outDataDir, + startFrame = 1, + endFrame = nWorkers, + nWorkers = nWorkers, + limitOneFramePerWorker = True, + autoscaleMax = 1 + ) + except KeyboardInterrupt: + print("Interruption occured") + return rc + +nFrames = args.nWorkers +#nWorkers = round( nFrames * 1.5 ) # old formula +nWorkers = math.ceil(nFrames*1.5) if nFrames <=10 else round( max( nFrames*1.12, nFrames + 5 * math.log10( nFrames ) ) ) + +dateTimeTag = datetime.datetime.now().strftime( '%Y-%m-%d_%H%M%S' ) +outDataDir = args.outDataDir + +try: + if (rc == 0) and os.path.isfile( outDataDir +'/recruitLaunched.json' ): + rampStepDuration = args.rampStepDuration + SLODuration = args.SLODuration + SLOResponseTimeMax = args.SLOResponseTimeMax + + rc2 = subprocess.call( [sys.executable, scriptDirPath()+'/plotJMeterOutput.py', + '--dataDirPath', outDataDir, + '--rampStepDuration', str(rampStepDuration), '--SLODuration', str(SLODuration), + '--SLOResponseTimeMax', str(SLOResponseTimeMax) + ], + stdout=subprocess.DEVNULL ) + if rc2: + logger.warning( 'plotJMeterOutput exited with returnCode %d', rc2 ) + + jtlFileName = args.jtlFile # make this match output file name from the .jmx (or empty if none) + if jtlFileName: + nameParts = os.path.splitext(jtlFileName) + mergedJtlFileName = nameParts[0]+'_merged_' + dateTimeTag + nameParts[1] + rc2 = subprocess.call( [sys.executable, scriptDirPath()+'/mergeBatchOutput.py', + '--dataDirPath', outDataDir, + '--csvPat', 'jmeterOut_%%03d/%s' % jtlFileName, + '--mergedCsv', mergedJtlFileName + ], stdout=subprocess.DEVNULL + ) + if rc2: + logger.warning( 'mergeBatchOutput.py exited with returnCode %d', rc2 ) + else: + if not os.path.isfile( jmeterBinPath ): + logger.info( 'no jmeter installed for producing reports (%s)', jmeterBinPath ) + else: + rcx = subprocess.call( [jmeterBinPath, + '-g', os.path.join( outDataDir, mergedJtlFileName ), + '-o', os.path.join( outDataDir, 'htmlReport' ) + ], stderr=subprocess.DEVNULL + ) + try: + shutil.move( 'jmeter.log', os.path.join( outDataDir, 'genHtml.log') ) + except Exception as exc: + logger.warning( 'could not move the jmeter.log file (%s) %s', type(exc), exc ) + if rcx: + logger.warning( 'jmeter reporting exited with returnCode %d', rcx ) + sys.exit( rc ) +except KeyboardInterrupt: + logger.warning( 'an interuption occurred') diff --git a/jmeter/splitFiles.py b/jmeter/splitFiles.py new file mode 100644 index 0000000..2db6774 --- /dev/null +++ b/jmeter/splitFiles.py @@ -0,0 +1,74 @@ +""" +Splits files between the machines - works for a row-separated files, like csv. +Assures uniqueness of datasets on the machine, without breaking the compatibility with the test scripts +Parition options are: +- REGIONAL: + File is partitioned between machines in a single region, i.e. US +- GLOBAL: + File is partitioned between all the launched machines +- UNIQUE_LOCAL: + File will be available only on a single machine from defined region +- UNIQUE_GLOBAL: + File will be available only on a single machine from all the regions + +""" +import os +import shutil +import sys +import json + +# file buffer size +BUF_SIZE = 1024 +TEST_PLAN_PATH = "test_plan.json" + +def splitFile(filename, contains_headers, partition_id, number_of_partitions): + file_copy = f"{filename}.bak" + shutil.copyfile(filename, file_copy) + with open (file_copy, "rt") as infile: + with open(filename, "wt") as outfile: + if contains_headers: + outfile.write(infile.readline(BUF_SIZE)) + index = 0 + while True: + line = infile.readline(BUF_SIZE) + if not line: + break + if number_of_partitions % index == partition_id: + outfile.write(f"{line}\n") + index = number_of_partitions % (index + 1) + +def read_file_properties(): + try: + with open(TEST_PLAN_PATH, "r") as test_plan: + file_properties = json.load(test_plan)["file_properties"] + return file_properties + except Exception as e: + print(f"Failed to load file properties for split, exception {e}") + sys.exit(1) + +def remove_file(filename): + os.remove(filename) + +if __name__ == "__main__": + + try: + global_instance_id = int(os.environ["GLOBAL_INSTANCE_ID"]) + local_instance_id = int(os.environ["LOCAL_INSTANCE_ID"]) + global_instance_count = int(os.environ["GLOBAL_INSTANCE_COUNT"]) + local_instance_count = int(os.environ["LOCAL_INSTANCE_COUNT"]) + current_location = os.environ["CURRENT_LOCATION"] + except ValueError: + print("Missing environment variables to split the files, or the values are not numbers!") + sys.exit(1) + + file_properties = read_file_properties() + for file in file_properties: + partition_scope = file["partition_scope"] + if partition_scope == "REGIONAL" and file["region"] == current_location: + splitFile(file["filename"], file["contains_headers", local_instance_id, local_instance_count - 1]) + elif partition_scope == "GLOBAL": + splitFile(file["filename"], file["contains_headers"], global_instance_id, global_instance_count - 1) + elif partition_scope == "UNIQUE_GLOBAL" and global_instance_id != 0: + remove_file(file["filename"]) + elif partition_scope == "UNIQUE_LOCAL" and local_instance_id !=0: + remove_file(file["filename"]) \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/Common_IDs.csv b/jmeterTest/GlobalJmeterTest/Common_IDs.csv new file mode 100644 index 0000000..54ce141 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/Common_IDs.csv @@ -0,0 +1,16 @@ +COMMON_ID +COMMON_ID_1 +COMMON_ID_2 +COMMON_ID_3 +COMMON_ID_4 +COMMON_ID_5 +COMMON_ID_6 +COMMON_ID_7 +COMMON_ID_8 +COMMON_ID_9 +COMMON_ID_10 +COMMON_ID_11 +COMMON_ID_12 +COMMON_ID_13 +COMMON_ID_14 +COMMON_ID_15 \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/GlobalPetStore.jmx b/jmeterTest/GlobalJmeterTest/GlobalPetStore.jmx new file mode 100644 index 0000000..6523149 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/GlobalPetStore.jmx @@ -0,0 +1,936 @@ + + + + + Sample Test plan for global testing with Jmeter + false + true + false + + + + + + + + A Sample Thread group, executed on all devices + continue + + false + -1 + + ${Threads} + ${RampUp} + false + + + true + + + + + + false + true + A sample bootstrap controller for accumulated results of the transaction + + + + + + + false + signonForm + + = + true + + + + + 443 + https + UTF-8 + /actions/Account.action + GET + true + false + true + false + + + + + + + + + Sec-Fetch-Mode + navigate + + + Sec-Fetch-Site + none + + + Accept-Language + ${__P(language,)}-${__P(country-code,)},${__P(language,)};q=0.9 + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + false + j_id + jsessionid=(.*?)"> + 1 + Notfound + + all + + + + + 10000 + + + + + false + true + Login step using region-specific user data + + + + + + + false + username + ${User_id2} + = + true + + + false + password + 123 + = + true + + + false + signon + Login + = + true + + + true + _sourcePage + kYG41kKUdhd0s9gfe_L_QCveO3k-0Duz3cTUpqfjvanLH6SqhBHXJyD69O2TTxh988WvlX-PlkB0_ZycFCCYCrrE1PU6FJgpPUNZBKmio1o= + = + true + + + false + __fp + V9vPkXbVVxfIrySRjEiiJ_nNmaxzIVuyAC0Nb1jFKEKG7mOwmEYVVMaNT71Qs0mE + = + true + + + + + 443 + https + UTF-8 + /actions/Account.action;jsessionid=${j_id} + POST + true + false + true + false + + + + Detected the start of a redirect chain + + + + Loads files from files including the region in the name - like "USERS_usa.csv" + , + + USERS_${__P(location,)}.csv + false + false + true + shareMode.all + false + User_id2,PASSWORD + + + + + + Sec-Fetch-Mode + navigate + + + Referer + ${scheme}://petstore.octoperf.com/actions/Account.action?signonForm= + + + Sec-Fetch-Site + same-origin + + + Origin + ${scheme}://petstore.octoperf.com + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Content-Type + application/x-www-form-urlencoded + + + Cache-Control + max-age=0 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + + + + + + 443 + https + UTF-8 + /actions/Catalog.action + GET + true + false + true + false + + + + + + + + + Sec-Fetch-Mode + navigate + + + Referer + ${scheme}://petstore.octoperf.com/actions/Account.action?signonForm= + + + Sec-Fetch-Site + same-origin + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Cache-Control + max-age=0 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + 10000 + + + + + + + 600 + + + + false + true + + + + + + + false + viewCategory + + = + true + + + false + categoryId + FISH + = + true + + + + + 443 + https + UTF-8 + /actions/Catalog.action + GET + true + false + true + false + + + + + + + + + Sec-Fetch-Mode + navigate + + + Referer + ${scheme}://petstore.octoperf.com/actions/Catalog.action + + + Sec-Fetch-Site + same-origin + + + Accept-Language + en-US,en;q=0.9 + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + 10000 + + + + + + false + true + + + + + + + false + viewCategory + + = + true + + + false + categoryId + DOGS + = + true + + + + + 443 + https + UTF-8 + /actions/Catalog.action + GET + true + false + true + false + + + + + + + + + Sec-Fetch-Mode + navigate + + + Referer + ${scheme}://petstore.octoperf.com/actions/Catalog.action?viewCategory=&categoryId=FISH + + + Sec-Fetch-Site + same-origin + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + 10000 + + + + + + false + true + + + + + + + false + viewCategory + + = + true + + + false + categoryId + CATS + = + true + + + + + 443 + https + UTF-8 + /actions/Catalog.action + GET + true + false + true + false + + + + + + + + + Sec-Fetch-Mode + navigate + + + Referer + ${scheme}://petstore.octoperf.com/actions/Catalog.action?viewCategory=&categoryId=DOGS + + + Sec-Fetch-Site + same-origin + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + 10000 + + + + + + + + + false + true + + + + + + + false + signoff + + = + true + + + + + 443 + https + UTF-8 + /actions/Account.action + GET + true + false + true + false + + + + Detected the start of a redirect chain + + + + + + Sec-Fetch-Mode + navigate + + + Referer + ${scheme}://petstore.octoperf.com/actions/Catalog.action?viewCategory=&categoryId=CATS + + + Sec-Fetch-Site + same-origin + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + + + + + + 443 + https + UTF-8 + /actions/Catalog.action + GET + true + false + true + false + + + + + + + + + Sec-Fetch-Mode + navigate + + + Referer + ${scheme}://petstore.octoperf.com/actions/Catalog.action?viewCategory=&categoryId=CATS + + + Sec-Fetch-Site + same-origin + + + Sec-Fetch-User + ?1 + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 + + + sec-ch-ua + "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99" + + + sec-ch-ua-mobile + ?0 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate, br + + + User-Agent + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 + + + Sec-Fetch-Dest + document + + + + + + + + + + + + ${TestHost} + + http + + + 6 + + + + + + + This thread group launches only on us-based devices + continue + + false + 1 + + ${__groovy("${__P(location, )}" == "usa" ? 1 : 0)} + ${RampUp} + false + + + true + + + + groovy + + + true + log.info("This message will only be available on US devices"); + +if ("usa".equals(props.get("location"))){ + log.info("This message will only be available on US devices"); + } + Sampler presenting the thread group only spawning on US devices + + + + + A Thread Group Executed only on a single device, regardless of the number of devices requested + continue + + false + 1 + + ${__groovy( Integer.parseInt(System.getenv("GLOBAL_INSTANCE_ID")) == 0 ? 1 : 0)} + 1 + false + + + true + + + + true + + + log.debug("This Sampler is only executed on a single device"); + groovy + + + + + false + true + false + + + + + false + false + + + + + + Accept-Language + ${__P(language,)}-${__P(country-code,)},${__P(language,)};q=0.9 + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + Threads + ${__P(Threads, 1)} + = + + + TestHost + ${__P(TestHost, petstore.octoperf.com) } + = + + + + + + + diff --git a/jmeterTest/GlobalJmeterTest/Global_IDS.csv b/jmeterTest/GlobalJmeterTest/Global_IDS.csv new file mode 100644 index 0000000..d1781e8 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/Global_IDS.csv @@ -0,0 +1,16 @@ +UNIQUE_ID +UNIQUE_ID_1 +UNIQUE_ID_2 +UNIQUE_ID_3 +UNIQUE_ID_4 +UNIQUE_ID_5 +UNIQUE_ID_6 +UNIQUE_ID_7 +UNIQUE_ID_8 +UNIQUE_ID_9 +UNIQUE_ID_10 +UNIQUE_ID_11 +UNIQUE_ID_12 +UNIQUE_ID_13 +UNIQUE_ID_14 +UNIQUE_ID_15 \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/USERS_india.csv b/jmeterTest/GlobalJmeterTest/USERS_india.csv new file mode 100644 index 0000000..cc40fe7 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/USERS_india.csv @@ -0,0 +1,11 @@ +USER_ID,PASSWORD +indian_user1,password +indian_user2,password +indian_user3,password +indian_user4,password +indian_user5,password +indian_user6,password +indian_user7,password +indian_user8,password +indian_user9,password +indian_user10,password \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/USERS_russia.csv b/jmeterTest/GlobalJmeterTest/USERS_russia.csv new file mode 100644 index 0000000..b085f22 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/USERS_russia.csv @@ -0,0 +1,11 @@ +USER_ID,PASSWORD +russian_user1,password +russian_user2,password +russian_user3,password +russian_user4,password +russian_user5,password +russian_user6,password +russian_user7,password +russian_user8,password +russian_user9,password +russian_user10,password \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/USERS_usa.csv b/jmeterTest/GlobalJmeterTest/USERS_usa.csv new file mode 100644 index 0000000..3fa68b2 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/USERS_usa.csv @@ -0,0 +1,11 @@ +USER_ID,PASSWORD +american_user1,password +american_user2,password +american_user3,password +american_user4,password +american_user5,password +american_user6,password +american_user7,password +american_user8,password +american_user9,password +american_user10,password \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/india.properties b/jmeterTest/GlobalJmeterTest/india.properties new file mode 100644 index 0000000..56dabc8 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/india.properties @@ -0,0 +1,4 @@ +# Here you can define properties unique to a region +# The properites file will be appended to user.properties and will be accessible by script + +lang=in \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/russia.properties b/jmeterTest/GlobalJmeterTest/russia.properties new file mode 100644 index 0000000..0d0fe85 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/russia.properties @@ -0,0 +1,5 @@ +# Here you can define properties unique to a region +# The properites file will be appended to user.properties and will be accessible by script + +lang=ru + diff --git a/jmeterTest/GlobalJmeterTest/test_plan.json b/jmeterTest/GlobalJmeterTest/test_plan.json new file mode 100644 index 0000000..5269673 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/test_plan.json @@ -0,0 +1,64 @@ + + +{ + "description" : [ + "Example test configuration for global test execution with parametrization", + "Supports spawning tests from multiple locations, with a fixed number of devices per region/country", + "Allows dataset files partitioning, allowing unique data distribution per device and per region" + ], + + "testFile" : "globalPetStore.jmx", + "testDuration" : "3600", + + "globalTestParameters" : [ + {"Threads" : 50}, + {"RampUp" : 600}, + {"TestHost" : "petstore.octoperf.com"} + ], + "device_requirements" : { + "dpr" : ">=48", + "dar" : ">90", + "ram" : ">=3800000000", + "storage" : ">=2000000000" + }, + "device_count" : [ + { + "region" : "usa", + "count" : 5, + "_comment" : "add other filters here" + }, + { + "region" : "india", + "count" : 2 + }, + { + "region" : "russia", + "count" : 2 + } + ], + "file_properties" : [ + { + "filename" : "USERS_russia.csv", + "partition_scope" : "REGIONAL", + "region" : "russia", + "contains_headers" : true + }, + { + "filename" : "USERS_usa.csv", + "partition_scope" : "REGIONAL", + "region" : "usa", + "contains_headers" : true + }, + { + "filename" : "USERS_india.csv", + "partition_scope" : "REGIONAL", + "region" : "india", + "contains_headers" : true + }, + { + "filename" : "Global_IDS.csv", + "partition_scope" : "GLOBAL", + "contains_headers" : true + } + ] +} \ No newline at end of file diff --git a/jmeterTest/GlobalJmeterTest/usa.properties b/jmeterTest/GlobalJmeterTest/usa.properties new file mode 100644 index 0000000..c144cb4 --- /dev/null +++ b/jmeterTest/GlobalJmeterTest/usa.properties @@ -0,0 +1,4 @@ +# Here you can define properties unique to a region +# The properites file will be appended to user.properties and will be accessible by script + +lang=en \ No newline at end of file diff --git a/ncscli/ncs.py b/ncscli/ncs.py index 42948dd..15a72e2 100755 --- a/ncscli/ncs.py +++ b/ncscli/ncs.py @@ -19,7 +19,7 @@ # third-party modules import requests -__version__ = '1.2.1' +__version__ = '1.2.2' logger = logging.getLogger(__name__) baseUrl = 'https://cloud.neocortix.com' diff --git a/setup.py b/setup.py index 8ecc0a3..ebf0a84 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ import setuptools import shutil -__version__ = '1.2.1' +__version__ = '1.2.2' with open("README.md", "r") as fh: long_description = fh.read()