mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-11-04 00:02:37 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			464 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			464 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# 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.
 | 
						|
 | 
						|
import xml.dom.minidom
 | 
						|
import json
 | 
						|
from optparse import OptionParser
 | 
						|
from textwrap import dedent
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import urllib2
 | 
						|
 | 
						|
 | 
						|
class cmdParameterProperty(object):
 | 
						|
    def __init__(self):
 | 
						|
        self.name = None
 | 
						|
        self.required = False
 | 
						|
        self.desc = ""
 | 
						|
        self.type = "planObject"
 | 
						|
        self.subProperties = []
 | 
						|
 | 
						|
 | 
						|
class cloudStackCmd(object):
 | 
						|
    def __init__(self):
 | 
						|
        self.name = ""
 | 
						|
        self.desc = ""
 | 
						|
        self.async = "false"
 | 
						|
        self.request = []
 | 
						|
        self.response = []
 | 
						|
 | 
						|
 | 
						|
class codeGenerator(object):
 | 
						|
    """
 | 
						|
    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 = '    '
 | 
						|
    newline = '\n'
 | 
						|
    cmdsName = []
 | 
						|
 | 
						|
    def __init__(self, outputFolder):
 | 
						|
        self.cmd = None
 | 
						|
        self.code = ""
 | 
						|
        self.required = []
 | 
						|
        self.subclass = []
 | 
						|
        self.outputFolder = outputFolder
 | 
						|
        lic = """\
 | 
						|
          # 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.
 | 
						|
 | 
						|
          """
 | 
						|
        self.license = dedent(lic)
 | 
						|
 | 
						|
    def addAttribute(self, attr, pro):
 | 
						|
        value = pro.value
 | 
						|
        if pro.required:
 | 
						|
            self.required.append(attr)
 | 
						|
        desc = pro.desc
 | 
						|
        if desc is not None:
 | 
						|
            self.code += self.space
 | 
						|
            self.code += "''' " + pro.desc + " '''"
 | 
						|
            self.code += self.newline
 | 
						|
 | 
						|
        self.code += self.space
 | 
						|
        self.code += attr + " = " + str(value)
 | 
						|
        self.code += self.newline
 | 
						|
 | 
						|
    def generateSubClass(self, name, properties):
 | 
						|
        '''generate code for sub list'''
 | 
						|
        subclass = 'class %s:\n' % name
 | 
						|
        subclass += self.space + "def __init__(self):\n"
 | 
						|
        for pro in properties:
 | 
						|
            if pro.desc is not None:
 | 
						|
                subclass += self.space + self.space + '""""%s"""\n' % pro.desc
 | 
						|
            if len(pro.subProperties) > 0:
 | 
						|
                subclass += self.space + self.space
 | 
						|
                subclass += 'self.%s = []\n' % pro.name
 | 
						|
                self.generateSubClass(pro.name, pro.subProperties)
 | 
						|
            else:
 | 
						|
                subclass += self.space + self.space
 | 
						|
                subclass += '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 += self.newline
 | 
						|
        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.code += 'self.isAsync = "%s"\n' % str(self.cmd.async).lower()
 | 
						|
 | 
						|
        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.code += '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 += self.newline
 | 
						|
        self.code += 'class %sResponse (baseResponse):\n' % self.cmd.name
 | 
						|
        self.code += self.space + "def __init__(self):\n"
 | 
						|
        if len(self.cmd.response) == 0:
 | 
						|
            self.code += self.space + self.space + "pass"
 | 
						|
        else:
 | 
						|
            for res in self.cmd.response:
 | 
						|
                if res.desc is not None:
 | 
						|
                    self.code += self.space + self.space
 | 
						|
                    self.code += '"""%s"""\n' % res.desc
 | 
						|
 | 
						|
                if len(res.subProperties) > 0:
 | 
						|
                    self.code += self.space + self.space
 | 
						|
                    self.code += 'self.%s = []\n' % res.name
 | 
						|
                    self.generateSubClass(res.name, res.subProperties)
 | 
						|
                else:
 | 
						|
                    self.code += self.space + self.space
 | 
						|
                    self.code += 'self.%s = None\n' % res.name
 | 
						|
        self.code += self.newline
 | 
						|
 | 
						|
        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()
 | 
						|
        self.code = ""
 | 
						|
        self.subclass = []
 | 
						|
 | 
						|
    def finalize(self):
 | 
						|
        '''generate an api call'''
 | 
						|
 | 
						|
        header = '"""Test Client for CloudStack API"""\n'
 | 
						|
        imports = "import copy\n"
 | 
						|
        initCmdsList = '__all__ = ['
 | 
						|
        body = ''
 | 
						|
        body += "class CloudStackAPIClient(object):\n"
 | 
						|
        body += self.space + 'def __init__(self, connection):\n'
 | 
						|
        body += self.space + self.space + 'self.connection = connection\n'
 | 
						|
        body += self.space + self.space + 'self._id = None\n'
 | 
						|
        body += self.newline
 | 
						|
 | 
						|
        body += self.space + 'def __copy__(self):\n'
 | 
						|
        body += self.space + self.space
 | 
						|
        body += 'return CloudStackAPIClient(copy.copy(self.connection))\n'
 | 
						|
        body += self.newline
 | 
						|
 | 
						|
        # The `id` property will be used to link the test with the cloud
 | 
						|
        # resource being created
 | 
						|
        #            @property
 | 
						|
        #            def id(self):
 | 
						|
        #                return self._id
 | 
						|
        #
 | 
						|
        #            @id.setter
 | 
						|
        #            def id(self, identifier):
 | 
						|
        #                self._id = identifier
 | 
						|
 | 
						|
        body += self.space + '@property' + self.newline
 | 
						|
        body += self.space + 'def id(self):' + self.newline
 | 
						|
        body += self.space*2 + 'return self._id' + self.newline
 | 
						|
        body += self.newline
 | 
						|
 | 
						|
        body += self.space + '@id.setter' + self.newline
 | 
						|
        body += self.space + 'def id(self, identifier):' + self.newline
 | 
						|
        body += self.space*2 + 'self._id = identifier' + self.newline
 | 
						|
        body += self.newline
 | 
						|
 | 
						|
        for cmdName in self.cmdsName:
 | 
						|
            body += self.space
 | 
						|
            body += 'def %s(self, command, method="GET"):\n' % cmdName
 | 
						|
            body += self.space + self.space
 | 
						|
            body += 'response = %sResponse()\n' % cmdName
 | 
						|
            body += self.space + self.space
 | 
						|
            body += 'response = self.connection.marvin_request(command,'
 | 
						|
            body += ' response_type=response, method=method)\n'
 | 
						|
            body += self.space + self.space + 'return response\n'
 | 
						|
            body += self.newline
 | 
						|
 | 
						|
            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'
 | 
						|
        basecmd += 'class baseCmd(object):\n'
 | 
						|
        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'
 | 
						|
        basecmd += 'class baseResponse(object):\n'
 | 
						|
        basecmd += self.space + 'pass\n'
 | 
						|
        fp.write(basecmd)
 | 
						|
        fp.close()
 | 
						|
 | 
						|
    def constructResponseFromXML(self, response):
 | 
						|
        paramProperty = cmdParameterProperty()
 | 
						|
        paramProperty.name = getText(response.getElementsByTagName('name'))
 | 
						|
        paramProperty.desc = getText(response.
 | 
						|
                                     getElementsByTagName('description'))
 | 
						|
        if paramProperty.name.find('(*)') != -1:
 | 
						|
            '''This is a list'''
 | 
						|
            paramProperty.name = paramProperty.name.split('(*)')[0]
 | 
						|
            argList = response.getElementsByTagName('arguments')[0].\
 | 
						|
                getElementsByTagName('arg')
 | 
						|
            for subresponse in argList:
 | 
						|
                subProperty = self.constructResponseFromXML(subresponse)
 | 
						|
                paramProperty.subProperties.append(subProperty)
 | 
						|
        return paramProperty
 | 
						|
 | 
						|
    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:
 | 
						|
                csCmd.desc = desc
 | 
						|
 | 
						|
            async = getText(cmd.getElementsByTagName('isAsync'))
 | 
						|
            if async:
 | 
						|
                csCmd.async = async
 | 
						|
 | 
						|
            argList = cmd.getElementsByTagName("request")[0].\
 | 
						|
                getElementsByTagName("arg")
 | 
						|
            for param in argList:
 | 
						|
                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:
 | 
						|
                    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 'name' in response:
 | 
						|
            paramProperty.name = response['name']
 | 
						|
        assert paramProperty.name, "%s has no property name" % response
 | 
						|
 | 
						|
        if 'description' in response:
 | 
						|
            paramProperty.desc = response['description']
 | 
						|
        if 'type' in response:
 | 
						|
            if response['type'] in ['list', 'map', 'set']:
 | 
						|
            #Here list becomes a subproperty
 | 
						|
                if 'response' in 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")
 | 
						|
 | 
						|
        jsonOut = apiStream.readlines()
 | 
						|
        assert len(jsonOut) > 0
 | 
						|
        apiDict = json.loads(jsonOut[0])
 | 
						|
        if not 'listapisresponse' in apiDict:
 | 
						|
            raise Exception("API discovery plugin response failed")
 | 
						|
        if not 'count' in apiDict['listapisresponse']:
 | 
						|
            raise Exception("Malformed api response")
 | 
						|
 | 
						|
        apilist = apiDict['listapisresponse']['api']
 | 
						|
        cmds = []
 | 
						|
        for cmd in apilist:
 | 
						|
            csCmd = cloudStackCmd()
 | 
						|
            if 'name' in cmd:
 | 
						|
                csCmd.name = cmd['name']
 | 
						|
            assert csCmd.name
 | 
						|
 | 
						|
            if 'description' in cmd:
 | 
						|
                csCmd.desc = cmd['description']
 | 
						|
 | 
						|
            if 'isasync' in cmd:
 | 
						|
                csCmd.async = cmd['isasync']
 | 
						|
 | 
						|
            for param in cmd['params']:
 | 
						|
                paramProperty = cmdParameterProperty()
 | 
						|
 | 
						|
                if 'name' in param:
 | 
						|
                    paramProperty.name = param['name']
 | 
						|
                assert paramProperty.name
 | 
						|
 | 
						|
                if 'required' in param:
 | 
						|
                    paramProperty.required = param['required']
 | 
						|
 | 
						|
                if 'description' in param:
 | 
						|
                    paramProperty.desc = param['description']
 | 
						|
 | 
						|
                if 'type' in param:
 | 
						|
                    paramProperty.type = param['type']
 | 
						|
 | 
						|
                csCmd.request.append(paramProperty)
 | 
						|
 | 
						|
            for response in cmd['response']:
 | 
						|
            #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 generateCodeFromJSON(self, endpointUrl):
 | 
						|
        """
 | 
						|
        Api Discovery plugin returns the supported APIs of a CloudStack
 | 
						|
        endpoint.
 | 
						|
        @return: The classes in cloudstackAPI/ formed from api discovery json
 | 
						|
        """
 | 
						|
        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()
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    parser = OptionParser()
 | 
						|
    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()
 | 
						|
 | 
						|
    folder = "."
 | 
						|
    if options.output is not None:
 | 
						|
        folder = options.output
 | 
						|
    apiModule = folder + "/cloudstackAPI"
 | 
						|
    if not os.path.exists(apiModule):
 | 
						|
        try:
 | 
						|
            os.mkdir(apiModule)
 | 
						|
        except:
 | 
						|
            print "Failed to create folder %s, due to %s" % (apiModule,
 | 
						|
                                                             sys.exc_info())
 | 
						|
            print parser.print_help()
 | 
						|
            exit(2)
 | 
						|
 | 
						|
    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)
 |