diff --git a/setup/dev/advanced.cfg b/setup/dev/advanced.cfg index dfaba00c63e..aee59851e3e 100644 --- a/setup/dev/advanced.cfg +++ b/setup/dev/advanced.cfg @@ -137,16 +137,10 @@ "port": 3306, "user": "cloud" }, - "logger": [ + "logger": { - "name": "TestClient", - "file": "/tmp/testclient.log" + "LogFolderPath": "/tmp/" }, - { - "name": "TestCase", - "file": "/tmp/testcase.log" - } - ], "globalConfig": [ { "name": "network.gc.wait", diff --git a/tools/marvin/marvin/cloudstackConnection.py b/tools/marvin/marvin/cloudstackConnection.py index 23f81fb48c2..48a2d21e514 100644 --- a/tools/marvin/marvin/cloudstackConnection.py +++ b/tools/marvin/marvin/cloudstackConnection.py @@ -20,24 +20,24 @@ import urllib import base64 import hmac import hashlib -import logging import time import cloudstackException from cloudstackAPI import * import jsonHelper -from requests import ConnectionError -from requests import HTTPError -from requests import Timeout -from requests import RequestException +from requests import ( + ConnectionError, + HTTPError, + Timeout, + RequestException + ) class cloudConnection(object): """ Connections to make API calls to the cloudstack management server """ - def __init__(self, mgmtDet, asyncTimeout=3600, logging=None, + def __init__(self, mgmtDet, asyncTimeout=3600, logger=None, path='client/api'): - self.loglevel() # Turn off requests logs self.apiKey = mgmtDet.apiKey self.securityKey = mgmtDet.securityKey self.mgtSvr = mgmtDet.mgtSvrIp @@ -46,7 +46,7 @@ class cloudConnection(object): self.passwd = mgmtDet.passwd self.certCAPath = mgmtDet.certCAPath self.certPath = mgmtDet.certPath - self.logging = logging + self.logger = logger self.path = path self.retries = 5 self.mgtDetails = mgmtDet @@ -67,13 +67,6 @@ class cloudConnection(object): self.logging, self.path) - def loglevel(self, lvl=logging.WARNING): - """ - Turns off the INFO/DEBUG logs from `requests` - """ - requests_log = logging.getLogger("requests") - requests_log.setLevel(lvl) - def poll(self, jobid, response): """ polls the completion of a given jobid @@ -95,9 +88,9 @@ class cloudConnection(object): return asyncResonse time.sleep(5) - if self.logging is not None: - self.logging.debug("job: %s still processing," - " will timeout in %ds" % (jobid, timeout)) + if self.logger is not None: + self.logger.debug("job: %s still processing," + "will timeout in %ds" % (jobid, timeout)) timeout = timeout - 5 raise cloudstackException.cloudstackAPIException( @@ -122,7 +115,7 @@ class cloudConnection(object): ) signature = base64.encodestring(hmac.new( self.securityKey, hashStr, hashlib.sha1).digest()).strip() - self.logging.debug("Computed Signature by Marvin: %s" % signature) + self.logger.debug("Computed Signature by Marvin: %s" % signature) return signature def request(self, command, auth=True, payload={}, method='GET'): @@ -186,7 +179,7 @@ class cloudConnection(object): then try with default certs, \ we dont need to mention here the cert path ''' - self.logging.debug("Creating CS connection over https \ + self.logger.debug("Creating CS connection over https \ didnt worked with user provided certs \ , so trying with no certs %s" % e) if method == 'POST': @@ -198,20 +191,20 @@ class cloudConnection(object): params=payload, verify=https_flag) except ConnectionError, c: - self.logging.debug("Connection refused. Reason: %s : %s" - % (self.baseurl, c)) + self.logger.debug("Connection refused. Reason: %s : %s" % + (self.baseurl, c)) raise c except HTTPError, h: - self.logging.debug("Http Error.Server returned error code: %s" % h) + self.logger.debug("Http Error.Server returned error code: %s" % h) raise h except Timeout, t: - self.logging.debug("Connection timed out with %s" % t) + self.logger.debug("Connection timed out with %s" % t) raise t except RequestException, r: - self.logging.debug("RequestException from server %s" % r) + self.logger.debug("RequestException from server %s" % r) raise r except Exception, e: - self.logging.debug("Error returned by server %s" % r) + self.logger.debug("Error returned by server %s" % r) raise e else: return response @@ -265,16 +258,16 @@ class cloudConnection(object): @return: """ cmdname, isAsync, payload = self.sanitizeCommand(cmd) - self.logging.debug("sending %s request: %s %s" % (method, cmdname, - str(payload))) + self.logger.debug("sending %s request: %s %s" % (method, cmdname, + str(payload))) response = self.request(cmdname, self.auth, payload=payload, method=method) if response is None: return None - self.logging.debug("Request: %s Response: %s" % - (response.url, response.text)) + self.logger.debug("Request: %s Response: %s" % (response.url, + response.text)) try: response = jsonHelper.getResultObj(response.json(), response_type) except TypeError: diff --git a/tools/marvin/marvin/codes.py b/tools/marvin/marvin/codes.py index f409c7c8d13..8f0f88d928e 100644 --- a/tools/marvin/marvin/codes.py +++ b/tools/marvin/marvin/codes.py @@ -42,3 +42,8 @@ PASS = 1 MATCH_NOT_FOUND = "ELEMENT NOT FOUND IN THE INPUT" SUCCESS = "SUCCESS" EXCEPTION_OCCURRED = "Exception Occurred" +NO = "no" +YES = "yes" +FAILED = "FAILED" +UNKNOWN_ERROR = "Unknown Error" +EXCEPTION = "Exception" diff --git a/tools/marvin/marvin/configGenerator.py b/tools/marvin/marvin/configGenerator.py index 631e40fce43..3f98aca9ff1 100644 --- a/tools/marvin/marvin/configGenerator.py +++ b/tools/marvin/marvin/configGenerator.py @@ -534,18 +534,6 @@ def descSetupInBasicMode(): cfg.value = v zs.globalConfig.append(cfg) - ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = "TestClient" - testClientLogger.file = "/tmp/testclient.log" - - testCaseLogger = logger() - testCaseLogger.name = "TestCase" - testCaseLogger.file = "/tmp/testcase.log" - - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) - return zs @@ -669,18 +657,6 @@ def descSetupInEipMode(): cfg.value = v zs.globalConfig.append(cfg) - ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = "TestClient" - testClientLogger.file = "/tmp/testclient.log" - - testCaseLogger = logger() - testCaseLogger.name = "TestCase" - testCaseLogger.file = "/tmp/testcase.log" - - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) - return zs @@ -801,18 +777,6 @@ def descSetupInAdvancedMode(): cfg.value = v zs.globalConfig.append(cfg) - ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = "TestClient" - testClientLogger.file = "/tmp/testclient.log" - - testCaseLogger = logger() - testCaseLogger.name = "TestCase" - testCaseLogger.file = "/tmp/testcase.log" - - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) - return zs '''sample code to generate setup configuration file''' @@ -926,18 +890,6 @@ def descSetupInAdvancedsgMode(): cfg.value = v zs.globalConfig.append(cfg) - ''''add loggers''' - testClientLogger = logger() - testClientLogger.name = "TestClient" - testClientLogger.file = "/tmp/testclient.log" - - testCaseLogger = logger() - testCaseLogger.name = "TestCase" - testCaseLogger.file = "/tmp/testcase.log" - - zs.logger.append(testClientLogger) - zs.logger.append(testCaseLogger) - return zs diff --git a/tools/marvin/marvin/deployDataCenter.py b/tools/marvin/marvin/deployDataCenter.py index 33bc438ce0e..93431719d55 100644 --- a/tools/marvin/marvin/deployDataCenter.py +++ b/tools/marvin/marvin/deployDataCenter.py @@ -28,16 +28,9 @@ from optparse import OptionParser class deployDataCenters(object): - def __init__(self, cfgFile): - if not path.exists(cfgFile) \ - and not path.exists(path.abspath(cfgFile)): - raise IOError("config file %s not found. please \ - specify a valid config file" % cfgFile) - self.configFile = cfgFile - ''' - parsed configuration information - ''' - self.config = None + def __init__(self, cfg, logger=None): + self.config = cfg + self.tcRunLogger = logger def addHosts(self, hosts, zoneId, podId, clusterId, hypervisor): if hosts is None: @@ -507,50 +500,17 @@ class deployDataCenters(object): self.config.mgtSvr[0].securityKey = securityKey return apiKey, securityKey - def getCfg(self): - if self.config is not None: - return self.config - return None - def loadCfg(self): - try: - self.config = configGenerator.getSetupConfig(self.configFile) - except: - raise cloudstackException.InvalidParameterException("Failed to load config %s" % self.configFile) - ''' Retrieving Management Server Connection Details ''' mgtDetails = self.config.mgtSvr[0] ''' Retrieving Database Connection Details''' dbSvrDetails = self.config.dbSvr - loggers = self.config.logger - testClientLogFile = None - self.testCaseLogFile = None - self.testResultLogFile = None - if loggers is not None and len(loggers) > 0: - for log in loggers: - if log.name == "TestClient": - testClientLogFile = log.file - elif log.name == "TestCase": - self.testCaseLogFile = log.file - elif log.name == "TestResult": - self.testResultLogFile = log.file - - testClientLogger = None - if testClientLogFile is not None: - testClientLogger = logging.getLogger("testclient.testengine.run") - fh = logging.FileHandler(testClientLogFile) - fh.setFormatter(logging. - Formatter("%(asctime)s - %(levelname)s - %(name)s\ - - %(message)s")) - testClientLogger.addHandler(fh) - testClientLogger.setLevel(logging.INFO) - self.testClientLogger = testClientLogger self.testClient = \ cloudstackTestClient.\ cloudstackTestClient(mgtDetails, dbSvrDetails, - logging=self.testClientLogger) + logging=self.tcRunLogger) if mgtDetails.apiKey is None: mgtDetails.apiKey, mgtDetails.securityKey = self.registerApiKey() @@ -559,8 +519,7 @@ class deployDataCenters(object): cloudstackTestClient.cloudstackTestClient( mgtDetails, dbSvrDetails, - logging= - self.testClientLogger) + logging=self.tcRunLogger) self.apiClient = self.testClient.getApiClient() """set hypervisor""" @@ -606,7 +565,11 @@ if __name__ == "__main__": ./datacenterCfg") (options, args) = parser.parse_args() - deploy = deployDataCenters(options.input) + from marvin.marvinLog import MarvinLog + cfg = configGenerator.getSetupConfig(options.input) + log_obj = MarvinLog("CSLog") + tcRunLogger = log_obj.setLogHandler("/tmp/debug.log") + deploy = deployDataCenters(cfg, tcRunLogger) deploy.deploy() """ diff --git a/tools/marvin/marvin/marvinInit.py b/tools/marvin/marvin/marvinInit.py new file mode 100644 index 00000000000..d822a2bbb98 --- /dev/null +++ b/tools/marvin/marvin/marvinInit.py @@ -0,0 +1,159 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +''' +@Desc: Initializes the marvin and does required prerequisites +for starting it. +1. Parses the configuration file passed to marvin and creates a + parsed config + 2. Initializes the logging required for marvin.All logs are + now made available under a single timestamped folder. + 3. Deploys the Data Center based upon input + +''' + +from marvin import configGenerator +from marvin import cloudstackException +from marvin.marvinLog import MarvinLog +from marvin.deployDataCenter import deployDataCenters +from marvin.codes import( + YES, + NO, + SUCCESS, + FAILED + ) +import sys +import time +import os +import logging + + +class MarvinInit: + def __init__(self, config_file, load_flag): + self.__configFile = config_file + self.__loadFlag = load_flag + self.__parsedConfig = None + self.__logFolderPath = None + self.__tcRunLogger = None + self.__testClient = None + self.__tcRunDebugFile = None + + def __parseConfig(self): + ''' + @Desc : Parses the configuration file passed and assigns + the parsed configuration + ''' + try: + self.__parsedConfig = configGenerator.\ + getSetupConfig(self.__configFile) + return SUCCESS + except Exception, e: + print "\n Exception Occurred Under __parseConfig : %s" % str(e) + return None + + def getParsedConfig(self): + return self.__parsedConfig + + def getLogFolderPath(self): + return self.__logFolderPath + + def getTestClient(self): + return self.__testClient + + def getLogger(self): + return self.__tcRunLogger + + def getDebugFile(self): + return self.__tcRunDebugFile + + def init(self): + ''' + @Desc :Initializes the marvin by + 1. Parsing the configuration and creating a parsed config + structure + 2. Creates a timestamped log folder and provides all logs + to be dumped there + 3. Creates the DataCenter based upon configuration provided + ''' + try: + if ((self.__parseConfig() is not None) and + (self.__initLogging() is not None) and + (self.__deployDC() is not None)): + return SUCCESS + else: + return FAILED + except Exception, e: + print "\n Exception Occurred Under init %s" % str(e) + return FAILED + + def __initLogging(self): + try: + ''' + @Desc : 1. Initializes the logging for marvin and so provides + various log features for automation run. + 2. Initializes all logs to be available under + given Folder Path,where all test run logs + are available for a given run. + 3. All logging like exception log,results, run info etc + for a given test run are available under a given + timestamped folder + ''' + log_config = self.__parsedConfig.logger + temp_path = "".join(str(time.time()).split(".")) + if log_config is not None: + if log_config.LogFolderPath is not None: + self.logFolderPath = log_config.LogFolderPath + temp_path + else: + self.logFolderPath = temp_path + else: + self.logFolderPath = temp_path + os.makedirs(self.logFolderPath) + ''' + Log File Paths + ''' + tc_failed_exceptionlog = self.logFolderPath + "/failed_" \ + "plus_" \ + "exceptions.txt" + tc_run_log = self.logFolderPath + "/runinfo.txt" + self.__tcRunDebugFile = open(self.logFolderPath + + "/tcresults.txt", "w") + + log_obj = MarvinLog("CSLog") + self.__tcRunLogger = log_obj.setLogHandler(tc_run_log) + log_obj.setLogHandler(tc_failed_exceptionlog, + log_level=logging.FATAL) + return SUCCESS + except Exception, e: + print "\n Exception Occurred Under __initLogging :%s" % str(e) + return None + + def __deployDC(self): + try: + ''' + Deploy the DataCenter and retrieves test client. + ''' + deploy_obj = deployDataCenters(self.__parsedConfig, + self.__tcRunLogger) + if self.__loadFlag: + deploy_obj.loadCfg() + else: + deploy_obj.deploy() + + self.__testClient = deploy_obj.testClient + return SUCCESS + except Exception, e: + print "\n Exception Occurred Under __deployDC : %s" % str(e) + return None diff --git a/tools/marvin/marvin/marvinLog.py b/tools/marvin/marvin/marvinLog.py new file mode 100644 index 00000000000..ca4d600ccf3 --- /dev/null +++ b/tools/marvin/marvin/marvinLog.py @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +''' +@Desc: Module for providing logging facilities to marvin +''' +import logging +import sys +from marvin.codes import (NO, + YES + ) + + +class MarvinLog: + ''' + @Desc : provides interface for logging to marvin + @Input : logger_name : name for logger + ''' + logFormat = "%(asctime)s - %(levelname)s - %(name)s - %(message)s" + _instance = None + + def __new__(cls, logger_name): + if not cls._instance: + cls._instance = super(MarvinLog, cls).__new__(cls, logger_name) + return cls._instance + + def __init__(self, logger_name): + self.loggerName = logger_name + self.logger = None + self.__setLogger() + + def __setLogger(self): + self.logger = logging.getLogger(self.loggerName) + self.logger.setLevel(logging.DEBUG) + + def setLogHandler(self, log_file_path, log_format=None, + log_level=logging.DEBUG): + ''' + @Desc: + @Input: log_file_path: Log File Path as where to store the logs + log_format : Format of log messages to be dumped + log_level : Determines the level of logging for this logger + ''' + try: + if log_file_path is not None: + stream = logging.FileHandler(log_file_path) + else: + stream = logging.StreamHandler(stream=sys.stdout) + + if log_format is None: + stream.setFormatter(log_format) + else: + stream.setFormatter(cls.logFormat) + stream.setLevel(log_level) + self.logger.addHandler(stream) + except Exception, e: + print "\n Exception Occurred Under setLogHandler %s" % str(e) + finally: + return self.logger diff --git a/tools/marvin/marvin/marvinPlugin.py b/tools/marvin/marvin/marvinPlugin.py index 48d5154bbea..852b84af7d7 100644 --- a/tools/marvin/marvin/marvinPlugin.py +++ b/tools/marvin/marvin/marvinPlugin.py @@ -20,9 +20,16 @@ import sys import logging import nose.core from marvin.cloudstackTestCase import cloudstackTestCase -from marvin import deployDataCenter +from marvin.marvinInit import MarvinInit from nose.plugins.base import Plugin +from marvin.codes import (SUCCESS, + FAILED, + EXCEPTION, + UNKNOWN_ERROR + ) +import traceback import time +import os class MarvinPlugin(Plugin): @@ -32,7 +39,21 @@ class MarvinPlugin(Plugin): name = "marvin" - def configure(self, options, config): + def __init__(self): + self.identifier = None + self.testClient = None + self.parsedConfig = None + self.configFile = None + self.loadFlag = None + self.conf = None + self.debugStream = sys.stdout + self.testRunner = None + self.startTime = None + self.testName = None + self.tcRunLogger = None + Plugin.__init__(self) + + def configure(self, options, conf): """enable the marvin plugin when the --with-marvin directive is given to nose. The enableOpt value is set from the command line directive and self.enabled (True|False) determines whether marvin's tests will run. @@ -44,33 +65,13 @@ class MarvinPlugin(Plugin): return else: self.enabled = True - self.logformat = logging.Formatter("%(asctime)s - %(levelname)s - " + - "%(name)s - %(message)s") - - if options.debug_log: - self.logger = logging.getLogger("NoseTestExecuteEngine") - self.debug_stream = logging.FileHandler(options.debug_log) - self.debug_stream.setFormatter(self.logformat) - self.logger.addHandler(self.debug_stream) - self.logger.setLevel(logging.DEBUG) - - if options.result_log: - ch = logging.StreamHandler() - ch.setLevel(logging.ERROR) - ch.setFormatter(self.logformat) - self.logger.addHandler(ch) - self.result_stream = open(options.result_log, "w") - else: - self.result_stream = sys.stdout - - deploy = deployDataCenter.deployDataCenters(options.config_file) - deploy.loadCfg() if options.load else deploy.deploy() - self.setClient(deploy.testClient) - self.setConfig(deploy.getCfg()) - - self.testrunner = nose.core.TextTestRunner(stream=self.result_stream, - descriptions=True, - verbosity=2, config=config) + self.configFile = options.config_file + self.loadFlag = options.load + self.conf = conf + ''' + Initializes the marvin with required settings + ''' + self.startMarvin() def options(self, parser, env): """ @@ -82,29 +83,11 @@ class MarvinPlugin(Plugin): help="Marvin's configuration file where the " + "datacenter information is specified " + "[MARVIN_CONFIG]") - parser.add_option("--result-log", action="store", - default=env.get('RESULT_LOG', None), - dest="result_log", - help="The path to the results file where test " + - "summary will be written to [RESULT_LOG]") - parser.add_option("--client-log", action="store", - default=env.get('DEBUG_LOG', 'debug.log'), - dest="debug_log", - help="The path to the testcase debug logs " + - "[DEBUG_LOG]") parser.add_option("--load", action="store_true", default=False, dest="load", help="Only load the deployment configuration given") - Plugin.options(self, parser, env) - def __init__(self): - self.identifier = None - Plugin.__init__(self) - - def prepareTestRunner(self, runner): - return self.testrunner - def wantClass(self, cls): if cls.__name__ == 'cloudstackTestCase': return False @@ -117,18 +100,13 @@ class MarvinPlugin(Plugin): self.identifier = cls.__name__ self._injectClients(cls) - def setClient(self, client): - if client is not None: - self.testclient = client - - def setConfig(self, config): - if config is not None: - self.config = config - def beforeTest(self, test): self.testName = test.__str__().split()[0] - self.testclient.identifier = '-'.join([self.identifier, self.testName]) - self.logger.name = test.__str__() + self.testClient.identifier = '-'.join([self.identifier, self.testName]) + self.tcRunLogger.name = test.__str__() + + def prepareTestRunner(self, runner): + return self.testRunner def startTest(self, test): """ @@ -136,6 +114,58 @@ class MarvinPlugin(Plugin): """ self.startTime = time.time() + def getErrorInfo(self, err): + ''' + Extracts and returns the sanitized error message + ''' + if err is not None: + return str(traceback.format_exc()) + else: + return UNKNOWN_ERROR + + def handleError(self, test, err): + ''' + Adds Exception throwing test cases and information to log. + ''' + err_msg = self.getErrorInfo(err) + self.tcRunLogger.fatal("%s: %s: %s" % + (EXCEPTION, self.testName, err_msg)) + + def handleFailure(self, test, err): + ''' + Adds Failing test cases and information to log. + ''' + err_msg = self.getErrorInfo(err) + self.tcRunLogger.fatal("%s: %s: %s" % + (FAILED, self.testName, err_msg)) + + def startMarvin(self): + ''' + Initializes the Marvin + creates the test Client + creates the runlogger for logging + Parses the config and creates a parsedconfig + Creates a debugstream for tc debug log + ''' + try: + obj_marvininit = MarvinInit(self.configFile, self.loadFlag) + if obj_marvininit.init() == SUCCESS: + self.testClient = obj_marvininit.getTestClient() + self.tcRunLogger = obj_marvininit.getLogger() + self.parsedConfig = obj_marvininit.getParsedConfig() + self.debugStream = obj_marvininit.getDebugFile() + self.testRunner = nose.core.TextTestRunner(stream= + self.debugStream, + descriptions=True, + verbosity=2, + config=self.conf) + return SUCCESS + else: + return FAILED + except Exception, e: + print "Exception Occurred under startMarvin: %s" % str(e) + return FAILED + def stopTest(self, test): """ Currently used to record end time for tests @@ -143,22 +173,24 @@ class MarvinPlugin(Plugin): endTime = time.time() if self.startTime is not None: totTime = int(endTime - self.startTime) - self.logger.debug( - "TestCaseName: %s; Time Taken: %s Seconds; \ - StartTime: %s; EndTime: %s" - % (self.testName, str(totTime), - str(time.ctime(self.startTime)), str(time.ctime(endTime)))) + self.tcRunLogger.debug("TestCaseName: %s; Time Taken: " + "%s Seconds; " + "StartTime: %s; EndTime: %s" + % (self.testName, str(totTime), + str(time.ctime(self.startTime)), + str(time.ctime(endTime)))) def _injectClients(self, test): - setattr(test, "debug", self.logger.debug) - setattr(test, "info", self.logger.info) - setattr(test, "warn", self.logger.warning) - setattr(test, "testClient", self.testclient) - setattr(test, "config", self.config) - if self.testclient.identifier is None: - self.testclient.identifier = self.identifier - setattr(test, "clstestclient", self.testclient) + setattr(test, "debug", self.tcRunLogger.debug) + setattr(test, "info", self.tcRunLogger.info) + setattr(test, "warn", self.tcRunLogger.warning) + setattr(test, "error", self.tcRunLogger.error) + setattr(test, "testClient", self.testClient) + setattr(test, "config", self.parsedConfig) + if self.testClient.identifier is None: + self.testClient.identifier = self.identifier + setattr(test, "clstestclient", self.testClient) if hasattr(test, "user"): # when the class-level attr applied. all test runs as 'user' - self.testclient.createUserApiClient(test.UserName, test.DomainName, + self.testClient.createUserApiClient(test.UserName, test.DomainName, test.AcctType)