Updated Branches: refs/heads/bvt 3307dbaec -> 4c5c71cdf
WIP: work in progress marvin-apidiscovery autosync Signed-off-by: Prasanna Santhanam <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/4c5c71cd Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/4c5c71cd Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/4c5c71cd Branch: refs/heads/bvt Commit: 4c5c71cdfa235d8d45155a6c4c56f41d158474d1 Parents: 3307dba Author: Prasanna Santhanam <[email protected]> Authored: Tue Mar 26 18:29:23 2013 +0530 Committer: Prasanna Santhanam <[email protected]> Committed: Tue Mar 26 18:29:23 2013 +0530 ---------------------------------------------------------------------- test/integration/smoke/test_vm_life_cycle.py | 17 ++++- tools/marvin/marvin/cloudstackTestClient.py | 15 +++- tools/marvin/marvin/codegenerator.py | 96 +++++++++++++++++++-- tools/marvin/marvin/marvinPlugin.py | 14 +++ 4 files changed, 129 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4c5c71cd/test/integration/smoke/test_vm_life_cycle.py ---------------------------------------------------------------------- diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 9d6c249..cf9fd75 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -262,11 +262,26 @@ class TestDeployVM(cloudstackTestCase): self.assertIsNotNone(router.publicip, msg="Router has no public ip") self.assertIsNotNone(router.guestipaddress, msg="Router has no guest ip") + @attr(hypervisor = ["simulator"]) + @attr(mode = ["basic"]) + def test_basicZoneVirtualRouter(self): + """ + Tests for basic zone virtual router + 1. Is Running + 2. is in the account the VM was deployed in + @return: + """ + routers = list_routers(self.apiclient, account=self.account.account.name) + self.assertTrue(len(routers) > 0, msg = "No virtual router found") + router = routers[0] + + self.assertEqual(router.state, 'Running', msg="Router is not in running state") + self.assertEqual(router.account, self.account.account.name, msg="Router does not belong to the account") + def tearDown(self): pass - class TestVMLifeCycle(cloudstackTestCase): @classmethod http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4c5c71cd/tools/marvin/marvin/cloudstackTestClient.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/cloudstackTestClient.py b/tools/marvin/marvin/cloudstackTestClient.py index cb63179..4bfb90b 100644 --- a/tools/marvin/marvin/cloudstackTestClient.py +++ b/tools/marvin/marvin/cloudstackTestClient.py @@ -24,7 +24,8 @@ import string import hashlib class cloudstackTestClient(object): - def __init__(self, mgtSvr=None, port=8096, apiKey = None, securityKey = None, asyncTimeout=3600, defaultWorkerThreads=10, logging=None): + def __init__(self, mgtSvr=None, port=8096, apiKey = None, securityKey = None, asyncTimeout=3600, + defaultWorkerThreads=10, logging=None): self.connection = cloudstackConnection.cloudConnection(mgtSvr, port, apiKey, securityKey, asyncTimeout, logging) self.apiClient = cloudstackAPIClient.CloudStackAPIClient(self.connection) self.dbConnection = None @@ -32,7 +33,6 @@ class cloudstackTestClient(object): self.ssh = None self.defaultWorkerThreads = defaultWorkerThreads - def dbConfigure(self, host="localhost", port=3306, user='cloud', passwd='cloud', db='cloud'): self.dbConnection = dbConnection.dbConnection(host, port, user, passwd, db) @@ -147,7 +147,16 @@ class cloudstackTestClient(object): if hasattr(self, "userApiClient"): return self.userApiClient return None - + + def synchronize(self): + """ + synchronize the api from an endpoint + """ + apiclient = self.getApiClient() + cmd = listApis.listApisCmd() + response = apiclient.listApis(cmd) + + '''FixME, httplib has issue if more than one thread submitted''' def submitCmdsAndWait(self, cmds, workers=1): if self.asyncJobMgr is None: http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4c5c71cd/tools/marvin/marvin/codegenerator.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/codegenerator.py b/tools/marvin/marvin/codegenerator.py index ed9248c..5d9a2df 100644 --- a/tools/marvin/marvin/codegenerator.py +++ b/tools/marvin/marvin/codegenerator.py @@ -16,10 +16,12 @@ # under the License. import xml.dom.minidom +import json from optparse import OptionParser from textwrap import dedent import os import sys + class cmdParameterProperty(object): def __init__(self): self.name = None @@ -97,6 +99,7 @@ class codeGenerator: subclass += self.space + self.space + 'self.%s = None\n'%pro.name self.subclass.append(subclass) + def generate(self, cmd): self.cmd = cmd @@ -159,8 +162,7 @@ class codeGenerator: fp.close() self.code = "" self.subclass = [] - - + def finalize(self): '''generate an api call''' @@ -215,8 +217,7 @@ class codeGenerator: fp.write(basecmd) fp.close() - - def constructResponse(self, response): + def constructResponseFromXML(self, response): paramProperty = cmdParameterProperty() paramProperty.name = getText(response.getElementsByTagName('name')) paramProperty.desc = getText(response.getElementsByTagName('description')) @@ -224,7 +225,23 @@ class codeGenerator: '''This is a list''' paramProperty.name = paramProperty.name.split('(*)')[0] for subresponse in response.getElementsByTagName('arguments')[0].getElementsByTagName('arg'): - subProperty = self.constructResponse(subresponse) + subProperty = self.constructResponseFromXML(subresponse) + paramProperty.subProperties.append(subProperty) + return paramProperty + + def constructResponseFromJSON(self, response): + paramProperty = cmdParameterProperty() + if response.has_key('name'): + paramProperty.name = response['name'] + assert paramProperty.name + + if response.has_key('description'): + paramProperty.desc = response['description'] + if response.has_key('type') and response['type'] == 'list': + #Here list becomes a subproperty + paramProperty.name = paramProperty.name.split('(*)')[0] + for subresponse in response.getElementsByTagName('arguments')[0].getElementsByTagName('arg'): + subProperty = self.constructResponseFromXML(subresponse) paramProperty.subProperties.append(subProperty) return paramProperty @@ -269,18 +286,79 @@ class codeGenerator: if response.parentNode != responseEle: continue - paramProperty = self.constructResponse(response) + paramProperty = self.constructResponseFromXML(response) csCmd.response.append(paramProperty) cmds.append(csCmd) return cmds - - def generateCode(self): + + def loadCmdFromJSON(self, apiStream): + if apiStream is None: + raise Exception("No APIs found through discovery") + + apiDict = json.loads(apiStream) + if not apiDict.has_key('listapisresponse'): + raise Exception("API discovery plugin response failed") + if not apiDict['listapisresponse'].has_key('count'): + raise Exception("Malformed api response") + + apilist = apiDict['listapisresponse']['api'] + cmds = [] + for cmd in apilist: + csCmd = cloudStackCmd() + if cmd.has_key('name'): + csCmd.name = cmd['name'] + assert csCmd.name + + if cmd.has_key('description'): + csCmd.desc = cmd['description'] + + if cmd.has_key('async'): + csCmd.async = cmd['isasync'] + + for param in cmd['params']: + paramProperty = cmdParameterProperty() + + if param.has_key('name'): + paramProperty.name = param['name'] + assert paramProperty.name + + if param.has_key('required'): + paramProperty.required = param.getElementsByTagName('required') + + if param.has_key('description'): + paramProperty.desc = param['description'] + + if param.has_key('type'): + paramProperty.type = param['type'] + + csCmd.request.append(paramProperty) + + for response in cmd['response']: + paramProperty = self.constructResponseFromJSON(response) + csCmd.response.append(paramProperty) + + cmds.append(csCmd) + return cmds + + + def generateCodeFromXML(self): cmds = self.loadCmdFromXML() for cmd in cmds: self.generate(cmd) self.finalize() + def generateCodeFromJSON(self, apiJson): + """ + Api Discovery plugin returns the supported APIs of a CloudStack endpoint. + @return: The classes in cloudstackAPI/ formed from api discovery json + """ + with open(apiJson, 'r') as apiStream: + cmds = self.loadCmdFromJSON(apiStream) + for cmd in cmds: + self.generate(cmd) + self.finalize() + def getText(elements): return elements[0].childNodes[0].nodeValue.strip() @@ -315,5 +393,5 @@ if __name__ == "__main__": exit(2) cg = codeGenerator(folder, apiSpecFile) - cg.generateCode() + cg.generateCodeFromXML() http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4c5c71cd/tools/marvin/marvin/marvinPlugin.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/marvinPlugin.py b/tools/marvin/marvin/marvinPlugin.py index c52596e..518f27f 100644 --- a/tools/marvin/marvin/marvinPlugin.py +++ b/tools/marvin/marvin/marvinPlugin.py @@ -21,6 +21,7 @@ import logging import nose.core from marvin.cloudstackTestCase import cloudstackTestCase from marvin import deployDataCenter +from marvin import apiSynchronizer from nose.plugins.base import Plugin from functools import partial @@ -39,6 +40,10 @@ class MarvinPlugin(Plugin): self.enableOpt = "--with-marvin" self.logformat = logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s") + if options.sync: + self.do_sync(options.config) + return + if options.debug_log: self.logger = logging.getLogger("NoseTestExecuteEngine") self.debug_stream = logging.FileHandler(options.debug_log) @@ -65,6 +70,13 @@ class MarvinPlugin(Plugin): cfg.debugLog = self.debug_stream self.testrunner = nose.core.TextTestRunner(stream=self.result_stream, descriptions=True, verbosity=2, config=config) + + def do_sync(self, config): + """ + Use the ApiDiscovery plugin exposed by the CloudStack mgmt server to rebuild the cloudStack API + """ + apiSynchronizer.sync(config) + def options(self, parser, env): """ @@ -84,6 +96,8 @@ class MarvinPlugin(Plugin): 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") + parser.add_option("--sync", action="store_true", default=False, dest="sync", + help="Sync the APIs from the CloudStack endpoint in marvin-config") Plugin.options(self, parser, env)
