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)

Reply via email to