cloudstack/tools/marvin/marvin/cloudstackConnection.py

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