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

Conflicts:
	tools/marvin/marvin/deployDataCenter.py
	tools/marvin/marvin/marvinPlugin.py
This commit is contained in:
Santhosh Edukulla 2013-12-06 12:01:53 +05:30 committed by Girish Shilamkar
parent 9dfba19009
commit 1190abd4ca
8 changed files with 374 additions and 204 deletions

View File

@ -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",

View File

@ -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:

View File

@ -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"

View File

@ -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

View File

@ -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()
"""

View File

@ -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

View File

@ -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

View File

@ -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)