mirror of https://github.com/apache/cloudstack.git
Enhanced few features under Marvin
Added few enhancements to marvin. Added new module for Logging Facility to marvin. Added new Init facility to marvin. Currently, there are multiple ways we are doing logging Removed few unwanted logging cases. Removed few command line switch options for logging. The new way of logging now provides consolidated logging under one single folder timestamped under the configured folder path. Removed parsing configuration from deploydata center Added parsing,start logging and deploy as part of init Added new error handling facility to catch unknown exception from test cases. Currently, lot of scripts are throwing unknown exceptions, add a handler to plugin to dump them to a file ToDO: Will do clean up in phase2 for this patch. Separate deployDatacenter from creating test client. Clean up configGenerator
This commit is contained in:
parent
88f64b2b93
commit
cafd820e3e
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,51 +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()
|
||||
|
|
@ -560,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 +564,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()
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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,34 +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):
|
||||
"""
|
||||
|
|
@ -83,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
|
||||
|
|
@ -118,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):
|
||||
"""
|
||||
|
|
@ -137,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
|
||||
|
|
@ -144,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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue