2017-05-05 12:07:42 -05:00

187 lines
7.0 KiB
Python

# -*- encoding: utf-8 -*-
#
# Copyright (c) 2014-2016 AT&T
#
# Licensed 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.
'''Valet API Wrapper'''
from heat.common.i18n import _
import json
from oslo_config import cfg
from oslo_log import log as logging
import requests
import sys
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def _exception(exc, exc_info, req):
'''Handle an exception'''
response = None
try:
if req is not None:
response = json.loads(req.text)
except Exception as e:
# FIXME(GJ): if Valet returns error
LOG.error("Exception is: %s, body is: %s" % (e, req.text))
return None
if response and 'error' in response:
# FIXME(GJ): if Valet returns error
error = response.get('error')
msg = "%(explanation)s (valet-api: %(message)s)" % {
'explanation': response.get('explanation', _('No remediation available')),
'message': error.get('message', _('Unknown error'))
}
LOG.error("Response with error: " + msg)
return "error"
else:
# TODO(JD): Re-evaluate if this clause is necessary.
exc_class, exc, traceback = exc_info # pylint: disable=W0612
msg = _("%(exc)s for %(method)s %(url)s with body %(body)s") % {'exc': exc, 'method': exc.request.method, 'url': exc.request.url, 'body': exc.request.body}
LOG.error("Response is none: " + msg)
return "error"
# TODO(JD): Improve exception reporting back up to heat
class ValetAPIError(Exception):
'''Valet API Error'''
pass
class ValetAPIWrapper(object):
'''Valet API Wrapper'''
def __init__(self):
'''Initializer'''
self.headers = {'Content-Type': 'application/json'}
self.opt_group_str = 'valet'
self.opt_name_str = 'url'
self.opt_conn_timeout = 'connect_timeout'
self.opt_read_timeout = 'read_timeout'
self._register_opts()
def _api_endpoint(self):
'''Returns API endpoint'''
try:
opt = getattr(cfg.CONF, self.opt_group_str)
endpoint = opt[self.opt_name_str]
if endpoint:
return endpoint
else:
# FIXME: Possibly not wanted (misplaced-bare-raise)
raise # pylint: disable=E0704
except Exception:
raise # exception.Error(_('API Endpoint not defined.'))
def _get_timeout(self):
'''Returns Valet plugin API request timeout tuple (conn_timeout, read_timeout)'''
read_timeout = 600
try:
opt = getattr(cfg.CONF, self.opt_group_str)
# conn_timeout = opt[self.opt_conn_timeout]
read_timeout = opt[self.opt_read_timeout]
except Exception:
pass
# Timeout accepts tupple on 'requests' version 2.4.0 and above - adding *connect* timeouts
# return conn_timeout, read_timeout
return read_timeout
def _register_opts(self):
'''Register options'''
opts = []
option = cfg.StrOpt(self.opt_name_str, default=None, help=_('Valet API endpoint'))
opts.append(option)
option = cfg.IntOpt(self.opt_conn_timeout, default=3, help=_('Valet Plugin Connect Timeout'))
opts.append(option)
option = cfg.IntOpt(self.opt_read_timeout, default=5, help=_('Valet Plugin Read Timeout'))
opts.append(option)
opt_group = cfg.OptGroup(self.opt_group_str)
cfg.CONF.register_group(opt_group)
cfg.CONF.register_opts(opts, group=opt_group)
# TODO(JD): Keep stack param for now. We may need it again.
def plans_create(self, stack, plan, auth_token=None): # pylint: disable=W0613
'''Create a plan'''
response = None
try:
req = None
timeout = self._get_timeout()
url = self._api_endpoint() + '/plans/'
payload = json.dumps(plan)
self.headers['X-Auth-Token'] = auth_token
req = requests.post(url, data=payload, headers=self.headers, timeout=timeout)
req.raise_for_status()
response = json.loads(req.text)
except (requests.exceptions.HTTPError, requests.exceptions.Timeout, requests.exceptions.ConnectionError)\
as exc:
return _exception(exc, sys.exc_info(), req)
except Exception as e:
LOG.error("Exception (at plans_create) is: %s" % e)
return None
return response
# TODO(JD): Keep stack param for now. We may need it again.
def plans_delete(self, stack, auth_token=None): # pylint: disable=W0613
'''Delete a plan'''
try:
req = None
timeout = self._get_timeout()
url = self._api_endpoint() + '/plans/' + stack.id
self.headers['X-Auth-Token'] = auth_token
req = requests.delete(url, headers=self.headers, timeout=timeout)
except (requests.exceptions.HTTPError, requests.exceptions.Timeout, requests.exceptions.ConnectionError)\
as exc:
return _exception(exc, sys.exc_info(), req)
except Exception as e:
LOG.error("Exception (plans_delete) is: %s" % e)
return None
# Delete does not return a response body.
def placement(self, orch_id, res_id, hosts=None, auth_token=None):
'''Reserve previously made placement.'''
try:
req = None
payload = None
timeout = self._get_timeout()
url = self._api_endpoint() + '/placements/' + orch_id
self.headers['X-Auth-Token'] = auth_token
if hosts:
kwargs = {
"locations": hosts,
"resource_id": res_id
}
payload = json.dumps(kwargs)
req = requests.post(url, data=payload, headers=self.headers, timeout=timeout)
else:
req = requests.get(url, headers=self.headers, timeout=timeout)
# TODO(JD): Raise an exception IFF the scheduler can handle it
# req.raise_for_status()
response = json.loads(req.text)
except (requests.exceptions.HTTPError, requests.exceptions.Timeout, requests.exceptions.ConnectionError)\
as exc:
return _exception(exc, sys.exc_info(), req)
except Exception as e: # pylint: disable=W0702
LOG.error("Exception (placement) is: %s" % e)
# FIXME: Find which exceptions we should really handle here.
response = None
return response