marvin syncs from endpoint Use codegenerator to sync marvin cloudstackAPIs from a given endpoint.
Signed-off-by: Prasanna Santhanam <t...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/2c2894e4 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/2c2894e4 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/2c2894e4 Branch: refs/heads/bvt Commit: 2c2894e4a5f254c910039003a2a6442c5a64a3d4 Parents: 392e92b Author: Prasanna Santhanam <t...@apache.org> Authored: Thu Mar 28 18:19:15 2013 +0530 Committer: Prasanna Santhanam <t...@apache.org> Committed: Thu Mar 28 18:19:15 2013 +0530 ---------------------------------------------------------------------- .../cloudstack/api/response/ServiceResponse.java | 9 +- tools/marvin/marvin/codegenerator.py | 209 ++++++++------- 2 files changed, 116 insertions(+), 102 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2c2894e4/api/src/org/apache/cloudstack/api/response/ServiceResponse.java ---------------------------------------------------------------------- diff --git a/api/src/org/apache/cloudstack/api/response/ServiceResponse.java b/api/src/org/apache/cloudstack/api/response/ServiceResponse.java index 445afcf..c93c55e 100644 --- a/api/src/org/apache/cloudstack/api/response/ServiceResponse.java +++ b/api/src/org/apache/cloudstack/api/response/ServiceResponse.java @@ -16,13 +16,12 @@ // under the License. package org.apache.cloudstack.api.response; -import java.util.List; - +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; +import java.util.List; @SuppressWarnings("unused") public class ServiceResponse extends BaseResponse { @@ -30,7 +29,7 @@ public class ServiceResponse extends BaseResponse { @SerializedName(ApiConstants.NAME) @Param(description="the service name") private String name; - @SerializedName(ApiConstants.PROVIDER) @Param(description="the service provider name") + @SerializedName(ApiConstants.PROVIDER) @Param(description="the service provider name", responseObject = ProviderResponse.class) private List<ProviderResponse> providers; @SerializedName("capability") @Param(description="the list of capabilities", responseObject = CapabilityResponse.class) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2c2894e4/tools/marvin/marvin/codegenerator.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/codegenerator.py b/tools/marvin/marvin/codegenerator.py index 5d9a2df..6ae85b1 100644 --- a/tools/marvin/marvin/codegenerator.py +++ b/tools/marvin/marvin/codegenerator.py @@ -21,6 +21,7 @@ from optparse import OptionParser from textwrap import dedent import os import sys +import urllib2 class cmdParameterProperty(object): def __init__(self): @@ -38,18 +39,21 @@ class cloudStackCmd: self.request = [] self.response = [] + class codeGenerator: + """ + Apache CloudStack- marvin python classes can be generated from the json returned by API discovery or from the + xml spec of commands generated by the ApiDocWriter. This class provides helper methods for these uses. + """ space = " " - cmdsName = [] - - def __init__(self, outputFolder, apiSpecFile): + + def __init__(self, outputFolder): self.cmd = None self.code = "" self.required = [] self.subclass = [] self.outputFolder = outputFolder - self.apiSpecFile = apiSpecFile lic = """\ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -58,19 +62,19 @@ class codeGenerator: # 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. + # under the License. """ self.license = dedent(lic) - + def addAttribute(self, attr, pro): value = pro.value if pro.required: @@ -80,11 +84,11 @@ class codeGenerator: self.code += self.space self.code += "''' " + pro.desc + " '''" self.code += "\n" - + self.code += self.space self.code += attr + " = " + str(value) self.code += "\n" - + def generateSubClass(self, name, properties): '''generate code for sub list''' subclass = 'class %s:\n'%name @@ -97,44 +101,44 @@ class codeGenerator: self.generateSubClass(pro.name, pro.subProperties) else: subclass += self.space + self.space + 'self.%s = None\n'%pro.name - + self.subclass.append(subclass) def generate(self, cmd): - + self.cmd = cmd self.cmdsName.append(self.cmd.name) self.code = self.license self.code += "\n" - self.code += '"""%s"""\n'%self.cmd.desc + self.code += '"""%s"""\n'%self.cmd.desc self.code += 'from baseCmd import *\n' self.code += 'from baseResponse import *\n' self.code += "class %sCmd (baseCmd):\n"%self.cmd.name self.code += self.space + "def __init__(self):\n" - + self.code += self.space + self.space + 'self.isAsync = "%s"\n' %self.cmd.async - + for req in self.cmd.request: if req.desc is not None: self.code += self.space + self.space + '"""%s"""\n'%req.desc if req.required == "true": self.code += self.space + self.space + '"""Required"""\n' - + value = "None" if req.type == "list" or req.type == "map": value = "[]" - + self.code += self.space + self.space + 'self.%s = %s\n'%(req.name,value) if req.required == "true": self.required.append(req.name) - + self.code += self.space + self.space + "self.required = [" for require in self.required: self.code += '"' + require + '",' self.code += "]\n" self.required = [] - - + + """generate response code""" subItems = {} self.code += "\n" @@ -146,17 +150,17 @@ class codeGenerator: for res in self.cmd.response: if res.desc is not None: self.code += self.space + self.space + '"""%s"""\n'%res.desc - + if len(res.subProperties) > 0: self.code += self.space + self.space + 'self.%s = []\n'%res.name self.generateSubClass(res.name, res.subProperties) else: self.code += self.space + self.space + 'self.%s = None\n'%res.name self.code += '\n' - + for subclass in self.subclass: self.code += subclass + "\n" - + fp = open(self.outputFolder + "/cloudstackAPI/%s.py"%self.cmd.name, "w") fp.write(self.code) fp.close() @@ -165,7 +169,7 @@ class codeGenerator: def finalize(self): '''generate an api call''' - + header = '"""Test Client for CloudStack API"""\n' imports = "import copy\n" initCmdsList = '__all__ = [' @@ -174,33 +178,33 @@ class codeGenerator: body += self.space + 'def __init__(self, connection):\n' body += self.space + self.space + 'self.connection = connection\n' body += "\n" - + body += self.space + 'def __copy__(self):\n' body += self.space + self.space + 'return CloudStackAPIClient(copy.copy(self.connection))\n' body += "\n" - + for cmdName in self.cmdsName: body += self.space + 'def %s(self,command):\n'%cmdName body += self.space + self.space + 'response = %sResponse()\n'%cmdName body += self.space + self.space + 'response = self.connection.make_request(command, response)\n' body += self.space + self.space + 'return response\n' body += '\n' - + imports += 'from %s import %sResponse\n'%(cmdName, cmdName) initCmdsList += '"%s",'%cmdName - + fp = open(self.outputFolder + '/cloudstackAPI/cloudstackAPIClient.py', 'w') fp.write(self.license) for item in [header, imports, body]: fp.write(item) fp.close() - + '''generate __init__.py''' initCmdsList = self.license + initCmdsList + '"cloudstackAPIClient"]' fp = open(self.outputFolder + '/cloudstackAPI/__init__.py', 'w') fp.write(initCmdsList) fp.close() - + fp = open(self.outputFolder + '/cloudstackAPI/baseCmd.py', 'w') basecmd = self.license basecmd += '"""Base Command"""\n' @@ -208,7 +212,7 @@ class codeGenerator: basecmd += self.space + 'pass\n' fp.write(basecmd) fp.close() - + fp = open(self.outputFolder + '/cloudstackAPI/baseResponse.py', 'w') basecmd = self.license basecmd += '"""Base class for response"""\n' @@ -216,7 +220,7 @@ class codeGenerator: basecmd += self.space + 'pass\n' fp.write(basecmd) fp.close() - + def constructResponseFromXML(self, response): paramProperty = cmdParameterProperty() paramProperty.name = getText(response.getElementsByTagName('name')) @@ -229,74 +233,84 @@ class codeGenerator: 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 - - def loadCmdFromXML(self): - dom = xml.dom.minidom.parse(self.apiSpecFile) + def loadCmdFromXML(self, dom): cmds = [] for cmd in dom.getElementsByTagName("command"): csCmd = cloudStackCmd() csCmd.name = getText(cmd.getElementsByTagName('name')) assert csCmd.name - + desc = getText(cmd.getElementsByTagName('description')) - if desc: + if desc: csCmd.desc = desc - + async = getText(cmd.getElementsByTagName('isAsync')) if async: csCmd.async = async - + for param in cmd.getElementsByTagName("request")[0].getElementsByTagName("arg"): paramProperty = cmdParameterProperty() - + paramProperty.name = getText(param.getElementsByTagName('name')) assert paramProperty.name - + required = param.getElementsByTagName('required') if required: paramProperty.required = getText(required) - + requestDescription = param.getElementsByTagName('description') - if requestDescription: + if requestDescription: paramProperty.desc = getText(requestDescription) - + type = param.getElementsByTagName("type") if type: paramProperty.type = getText(type) - + csCmd.request.append(paramProperty) - + responseEle = cmd.getElementsByTagName("response")[0] for response in responseEle.getElementsByTagName("arg"): if response.parentNode != responseEle: continue - + paramProperty = self.constructResponseFromXML(response) csCmd.response.append(paramProperty) - + cmds.append(csCmd) return cmds + def generateCodeFromXML(self, apiSpecFile): + dom = xml.dom.minidom.parse(apiSpecFile) + cmds = self.loadCmdFromXML(dom) + for cmd in cmds: + self.generate(cmd) + self.finalize() + + def constructResponseFromJSON(self, response): + paramProperty = cmdParameterProperty() + if response.has_key('name'): + paramProperty.name = response['name'] + assert paramProperty.name, "%s has no property name"%response + + if response.has_key('description'): + paramProperty.desc = response['description'] + if response.has_key('type'): + if response['type'] in ['list', 'map', 'set']: + #Here list becomes a subproperty + if response.has_key('response'): + for innerResponse in response['response']: + subProperty = self.constructResponseFromJSON(innerResponse) + paramProperty.subProperties.append(subProperty) + paramProperty.type = response['type'] + return paramProperty + def loadCmdFromJSON(self, apiStream): if apiStream is None: raise Exception("No APIs found through discovery") - apiDict = json.loads(apiStream) + jsonOut = apiStream.readlines() + assert len(jsonOut) > 0 + apiDict = json.loads(jsonOut[0]) if not apiDict.has_key('listapisresponse'): raise Exception("API discovery plugin response failed") if not apiDict['listapisresponse'].has_key('count'): @@ -324,7 +338,7 @@ class codeGenerator: assert paramProperty.name if param.has_key('required'): - paramProperty.required = param.getElementsByTagName('required') + paramProperty.required = param['required'] if param.has_key('description'): paramProperty.desc = param['description'] @@ -335,29 +349,25 @@ class codeGenerator: csCmd.request.append(paramProperty) for response in cmd['response']: - paramProperty = self.constructResponseFromJSON(response) - csCmd.response.append(paramProperty) + #FIXME: ExtractImage related APIs return empty dicts in response + if len(response) > 0: + 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): + def generateCodeFromJSON(self, endpointUrl): """ 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() + if endpointUrl.find('response=json') >= 0: + apiStream = urllib2.urlopen(endpointUrl) + cmds = self.loadCmdFromJSON(apiStream) + for cmd in cmds: + self.generate(cmd) + self.finalize() def getText(elements): return elements[0].childNodes[0].nodeValue.strip() @@ -365,20 +375,14 @@ def getText(elements): if __name__ == "__main__": parser = OptionParser() - parser.add_option("-o", "--output", dest="output", help="the root path where code genereted, default is .") - parser.add_option("-s", "--specfile", dest="spec", help="the path and name of the api spec xml file, default is /etc/cloud/cli/commands.xml") - + parser.add_option("-o", "--output", dest="output", + help="The path to the generated code entities, default is .") + parser.add_option("-s", "--specfile", dest="spec", + help="The path and name of the api spec xml file, default is /etc/cloud/cli/commands.xml") + parser.add_option("-e", "--endpoint", dest="endpoint", + help="The endpoint mgmt server (with open 8096) where apis are discovered, default is localhost") + (options, args) = parser.parse_args() - - apiSpecFile = "/etc/cloud/cli/commands.xml" - if options.spec is not None: - apiSpecFile = options.spec - - if not os.path.exists(apiSpecFile): - print "the spec file %s does not exists"%apiSpecFile - print parser.print_help() - exit(1) - folder = "." if options.output is not None: @@ -391,7 +395,18 @@ if __name__ == "__main__": print "Failed to create folder %s, due to %s"%(apiModule,sys.exc_info()) print parser.print_help() exit(2) - - cg = codeGenerator(folder, apiSpecFile) - cg.generateCodeFromXML() - + + apiSpecFile = "/etc/cloud/cli/commands.xml" + if options.spec is not None: + apiSpecFile = options.spec + if not os.path.exists(apiSpecFile): + print "the spec file %s does not exists"%apiSpecFile + print parser.print_help() + exit(1) + + cg = codeGenerator(folder) + if options.spec is not None: + cg.generateCodeFromXML(apiSpecFile) + elif options.endpoint is not None: + endpointUrl='http://%s:8096/client/api?command=listApis&response=json'%options.endpoint + cg.generateCodeFromJSON(endpointUrl)