mirror of
https://github.com/apache/cloudstack.git
synced 2025-12-16 02:22:52 +01:00
There were few unwanted calls as part of test client, did some clean up Made the test client API uniform to accept both mgmt and dbsvr details Did some minor bug fixes as well. Signed-off-by: Santhosh Edukulla <Santhosh.Edukulla@citrix.com>
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.marvinRequest(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)
|