mirror of https://github.com/apache/cloudstack.git
282 lines
11 KiB
Python
282 lines
11 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 requests
|
|
import urllib
|
|
import base64
|
|
import hmac
|
|
import hashlib
|
|
import time
|
|
import cloudstackException
|
|
from cloudstackAPI import *
|
|
import jsonHelper
|
|
from requests import (
|
|
ConnectionError,
|
|
HTTPError,
|
|
Timeout,
|
|
RequestException
|
|
)
|
|
|
|
|
|
class cloudConnection(object):
|
|
|
|
""" Connections to make API calls to the cloudstack management server
|
|
"""
|
|
def __init__(self, mgmtDet, asyncTimeout=3600, logger=None,
|
|
path='client/api'):
|
|
self.apiKey = mgmtDet.apiKey
|
|
self.securityKey = mgmtDet.securityKey
|
|
self.mgtSvr = mgmtDet.mgtSvrIp
|
|
self.port = mgmtDet.port
|
|
self.user = mgmtDet.user
|
|
self.passwd = mgmtDet.passwd
|
|
self.certCAPath = mgmtDet.certCAPath
|
|
self.certPath = mgmtDet.certPath
|
|
self.logger = logger
|
|
self.path = path
|
|
self.retries = 5
|
|
self.mgtDetails = mgmtDet
|
|
self.protocol = "http"
|
|
self.asyncTimeout = asyncTimeout
|
|
self.auth = True
|
|
if self.port == 8096 or \
|
|
(self.apiKey is None and self.securityKey is None):
|
|
self.auth = False
|
|
if mgmtDet.useHttps == "True":
|
|
self.protocol = "https"
|
|
self.baseurl = "%s://%s:%d/%s"\
|
|
% (self.protocol, self.mgtSvr, self.port, self.path)
|
|
|
|
def __copy__(self):
|
|
return cloudConnection(self.mgtDetails,
|
|
self.asyncTimeout,
|
|
self.logger,
|
|
self.path)
|
|
|
|
def poll(self, jobid, response):
|
|
"""
|
|
polls the completion of a given jobid
|
|
@param jobid:
|
|
@param response:
|
|
@return:
|
|
"""
|
|
cmd = queryAsyncJobResult.queryAsyncJobResultCmd()
|
|
cmd.jobid = jobid
|
|
timeout = self.asyncTimeout
|
|
|
|
while timeout > 0:
|
|
asyncResonse = self.marvinRequest(cmd, response_type=response)
|
|
|
|
if asyncResonse.jobstatus == 2:
|
|
raise cloudstackException.cloudstackAPIException(
|
|
"asyncquery", asyncResonse.jobresult)
|
|
elif asyncResonse.jobstatus == 1:
|
|
return asyncResonse
|
|
|
|
time.sleep(5)
|
|
if self.logger is not None:
|
|
self.logger.debug("job: %s still processing,"
|
|
"will timeout in %ds" % (jobid, timeout))
|
|
timeout = timeout - 5
|
|
|
|
raise cloudstackException.cloudstackAPIException(
|
|
"asyncquery", "Async job timeout %s" % jobid)
|
|
|
|
def sign(self, payload):
|
|
"""
|
|
signs a given request URL when the apiKey and secretKey are known
|
|
|
|
@param payload: dict of GET params to be signed
|
|
@return: the signature of the payload
|
|
"""
|
|
params = zip(payload.keys(), payload.values())
|
|
params.sort(key=lambda k: str.lower(k[0]))
|
|
hashStr = "&".join(
|
|
["=".join(
|
|
[str.lower(r[0]),
|
|
str.lower(
|
|
urllib.quote_plus(str(r[1]))
|
|
).replace("+", "%20")]
|
|
) for r in params]
|
|
)
|
|
signature = base64.encodestring(hmac.new(
|
|
self.securityKey, hashStr, hashlib.sha1).digest()).strip()
|
|
self.logger.debug("Computed Signature by Marvin: %s" % signature)
|
|
return signature
|
|
|
|
def request(self, command, auth=True, payload={}, method='GET'):
|
|
"""
|
|
Makes requests using auth or over integration port
|
|
@param command: cloudstack API command name
|
|
eg: deployVirtualMachineCommand
|
|
@param auth: Authentication (apikey,secretKey) => True
|
|
else False for integration.api.port
|
|
@param payload: request data composed as a dictionary
|
|
@param method: GET/POST via HTTP
|
|
@return:
|
|
"""
|
|
payload["command"] = command
|
|
payload["response"] = "json"
|
|
|
|
if auth:
|
|
payload["apiKey"] = self.apiKey
|
|
signature = self.sign(payload)
|
|
payload["signature"] = signature
|
|
|
|
try:
|
|
#https_flag : Signifies whether to verify connection over \
|
|
#http or https, \
|
|
#initialized to False, will be set to true if user provided https
|
|
#connection
|
|
https_flag = False
|
|
cert_path = ()
|
|
if self.protocol == "https":
|
|
https_flag = True
|
|
if self.certCAPath != "NA" and self.certPath != "NA":
|
|
cert_path = (self.certCAPath, self.certPath)
|
|
|
|
#Verify whether protocol is "http", then call the request over http
|
|
if self.protocol == "http":
|
|
if method == 'POST':
|
|
response = requests.post(self.baseurl, params=payload,
|
|
verify=https_flag)
|
|
else:
|
|
response = requests.get(self.baseurl, params=payload,
|
|
verify=https_flag)
|
|
else:
|
|
'''
|
|
If protocol is https, then create the connection url with \
|
|
user provided certificates \
|
|
provided as part of cert
|
|
'''
|
|
try:
|
|
if method == 'POST':
|
|
response = requests.post(self.baseurl,
|
|
params=payload,
|
|
cert=cert_path,
|
|
verify=https_flag)
|
|
else:
|
|
response = requests.get(self.baseurl, params=payload,
|
|
cert=cert_path,
|
|
verify=https_flag)
|
|
except Exception, e:
|
|
'''
|
|
If an exception occurs with user provided CA certs, \
|
|
then try with default certs, \
|
|
we dont need to mention here the cert path
|
|
'''
|
|
self.logger.debug("Creating CS connection over https \
|
|
didnt worked with user provided certs \
|
|
, so trying with no certs %s" % e)
|
|
if method == 'POST':
|
|
response = requests.post(self.baseurl,
|
|
params=payload,
|
|
verify=https_flag)
|
|
else:
|
|
response = requests.get(self.baseurl,
|
|
params=payload,
|
|
verify=https_flag)
|
|
except ConnectionError, c:
|
|
self.logger.debug("Connection refused. Reason: %s : %s" %
|
|
(self.baseurl, c))
|
|
raise c
|
|
except HTTPError, h:
|
|
self.logger.debug("Http Error.Server returned error code: %s" % h)
|
|
raise h
|
|
except Timeout, t:
|
|
self.logger.debug("Connection timed out with %s" % t)
|
|
raise t
|
|
except RequestException, r:
|
|
self.logger.debug("RequestException from server %s" % r)
|
|
raise r
|
|
except Exception, e:
|
|
self.logger.debug("Error returned by server %s" % r)
|
|
raise e
|
|
else:
|
|
return response
|
|
|
|
def sanitizeCommand(self, cmd):
|
|
"""
|
|
Removes None values, Validates all required params are present
|
|
@param cmd: Cmd object eg: createPhysicalNetwork
|
|
@return:
|
|
"""
|
|
requests = {}
|
|
required = []
|
|
for attribute in dir(cmd):
|
|
if not attribute.startswith('__'):
|
|
if attribute == "isAsync":
|
|
isAsync = getattr(cmd, attribute)
|
|
elif attribute == "required":
|
|
required = getattr(cmd, attribute)
|
|
else:
|
|
requests[attribute] = getattr(cmd, attribute)
|
|
|
|
cmdname = cmd.__class__.__name__.replace("Cmd", "")
|
|
for requiredPara in required:
|
|
if requests[requiredPara] is None:
|
|
raise cloudstackException.cloudstackAPIException(
|
|
cmdname, "%s is required" % requiredPara)
|
|
for param, value in requests.items():
|
|
if value is None:
|
|
requests.pop(param)
|
|
elif isinstance(value, list):
|
|
if len(value) == 0:
|
|
requests.pop(param)
|
|
else:
|
|
if not isinstance(value[0], dict):
|
|
requests[param] = ",".join(value)
|
|
else:
|
|
requests.pop(param)
|
|
i = 0
|
|
for val in value:
|
|
for k, v in val.iteritems():
|
|
requests["%s[%d].%s" % (param, i, k)] = v
|
|
i = i + 1
|
|
return cmdname.strip(), isAsync, requests
|
|
|
|
def marvinRequest(self, cmd, response_type=None, method='GET', data=''):
|
|
"""
|
|
Requester for marvin command objects
|
|
@param cmd: marvin's command from cloudstackAPI
|
|
@param response_type: response type of the command in cmd
|
|
@param method: HTTP GET/POST, defaults to GET
|
|
@return:
|
|
"""
|
|
cmdname, isAsync, payload = self.sanitizeCommand(cmd)
|
|
self.logger.debug("sending %s request: %s %s" % (method, cmdname,
|
|
str(payload)))
|
|
response = self.request(cmdname,
|
|
self.auth,
|
|
payload=payload,
|
|
method=method)
|
|
if response is None:
|
|
return None
|
|
self.logger.debug("Request: %s Response: %s" % (response.url,
|
|
response.text))
|
|
try:
|
|
response = jsonHelper.getResultObj(response.json(), response_type)
|
|
except TypeError:
|
|
response = jsonHelper.getResultObj(response.json, response_type)
|
|
|
|
if isAsync == "false":
|
|
return response
|
|
else:
|
|
asyncJobId = response.jobid
|
|
response = self.poll(asyncJobId, response_type)
|
|
return response.jobresult
|