
Rather than always returning JSON or YAML, add functionality to return table responses when applicable for ease of reading. Adds some marker exceptions to the Shipyard API Client to handle cases where the user of the client would generally want to take a specific and repeatable course of action instead of handling the response in a case-by-case basis. Moved cli action testing to use the responses library instead of as many internal mocks. Added test response generators for standard api responses. Change-Id: I3a593fb29b6e76d971adc7f3bb3a4b7f378ed091
162 lines
6.0 KiB
Python
162 lines
6.0 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# 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.
|
|
import abc
|
|
import logging
|
|
|
|
from keystoneauth1.exceptions.auth import AuthorizationFailure
|
|
from keystoneauth1.exceptions.catalog import EndpointNotFound
|
|
from keystoneauth1.identity import v3
|
|
from keystoneauth1 import session
|
|
import requests
|
|
|
|
from shipyard_client.api_client.client_error import ClientError
|
|
from shipyard_client.api_client.client_error import UnauthenticatedClientError
|
|
from shipyard_client.api_client.client_error import UnauthorizedClientError
|
|
|
|
|
|
class BaseClient(metaclass=abc.ABCMeta):
|
|
"""Abstract base client class
|
|
|
|
Requrires the definition of service_type and interface by child classes
|
|
"""
|
|
@property
|
|
@abc.abstractmethod
|
|
def service_type(self):
|
|
"""Specify the name/type used to lookup the service"""
|
|
pass
|
|
|
|
@property
|
|
@abc.abstractmethod
|
|
def interface(self):
|
|
"""The interface to choose from during service lookup
|
|
|
|
Specify the interface to look up the service: public, internal admin
|
|
"""
|
|
pass
|
|
|
|
def __init__(self, context):
|
|
self.logger = logging.Logger('api_client')
|
|
self.context = context
|
|
self.endpoint = None
|
|
|
|
def log_message(self, level, msg):
|
|
""" Logs a message with context, and extra populated. """
|
|
self.logger.log(level, msg)
|
|
|
|
def debug(self, msg):
|
|
""" Debug logger for resources, incorporating context. """
|
|
self.log_message(logging.DEBUG, msg)
|
|
|
|
def info(self, ctx, msg):
|
|
""" Info logger for resources, incorporating context. """
|
|
self.log_message(logging.INFO, msg)
|
|
|
|
def warn(self, msg):
|
|
""" Warn logger for resources, incorporating context. """
|
|
self.log_message(logging.WARN, msg)
|
|
|
|
def error(self, msg):
|
|
""" Error logger for resources, incorporating context. """
|
|
self.log_message(logging.ERROR, msg)
|
|
|
|
def post_resp(self,
|
|
url,
|
|
query_params=None,
|
|
data=None,
|
|
content_type='application/x-yaml'):
|
|
""" Thin wrapper of requests post """
|
|
if not query_params:
|
|
query_params = {}
|
|
if not data:
|
|
data = {}
|
|
try:
|
|
headers = {
|
|
'X-Context-Marker': self.context.context_marker,
|
|
'content-type': content_type,
|
|
'X-Auth-Token': self.get_token()
|
|
}
|
|
self.debug('Post request url: ' + url)
|
|
self.debug('Query Params: ' + str(query_params))
|
|
# This could use keystoneauth1 session, but that library handles
|
|
# responses strangely (wraps all 400/500 in a keystone exception)
|
|
response = requests.post(
|
|
url, data=data, params=query_params, headers=headers)
|
|
# handle some cases where the response code is sufficient to know
|
|
# what needs to be done
|
|
if response.status_code == 401:
|
|
raise UnauthenticatedClientError()
|
|
if response.status_code == 403:
|
|
raise UnauthorizedClientError()
|
|
return response
|
|
except requests.exceptions.RequestException as e:
|
|
self.error(str(e))
|
|
raise ClientError(str(e))
|
|
|
|
def get_resp(self, url, query_params=None):
|
|
""" Thin wrapper of requests get """
|
|
if not query_params:
|
|
query_params = {}
|
|
try:
|
|
headers = {
|
|
'X-Context-Marker': self.context.context_marker,
|
|
'X-Auth-Token': self.get_token()
|
|
}
|
|
self.debug('url: ' + url)
|
|
self.debug('Query Params: ' + str(query_params))
|
|
response = requests.get(url, params=query_params, headers=headers)
|
|
# handle some cases where the response code is sufficient to know
|
|
# what needs to be done
|
|
if response.status_code == 401:
|
|
raise UnauthenticatedClientError()
|
|
if response.status_code == 403:
|
|
raise UnauthorizedClientError()
|
|
return response
|
|
except requests.exceptions.RequestException as e:
|
|
self.error(str(e))
|
|
raise ClientError(str(e))
|
|
|
|
def get_token(self):
|
|
"""
|
|
Returns the simple token string for a token acquired from keystone
|
|
"""
|
|
return self._get_ks_session().get_auth_headers().get('X-Auth-Token')
|
|
|
|
def _get_ks_session(self):
|
|
self.logger.debug('Accessing keystone for keystone session')
|
|
try:
|
|
auth = v3.Password(**self.context.keystone_auth)
|
|
return session.Session(auth=auth)
|
|
except AuthorizationFailure as e:
|
|
self.logger.error('Could not authorize against keystone: %s',
|
|
str(e))
|
|
raise ClientError(str(e))
|
|
|
|
def get_endpoint(self):
|
|
"""Lookup the endpoint for the client. Cache it.
|
|
|
|
Uses a keystone session to find an endpoint for the specified
|
|
service_type at the specified interface (public, internal, admin)
|
|
"""
|
|
if self.endpoint is None:
|
|
self.logger.debug('Accessing keystone for %s endpoint',
|
|
self.service_type)
|
|
try:
|
|
self.endpoint = self._get_ks_session().get_endpoint(
|
|
interface=self.interface, service_type=self.service_type)
|
|
except EndpointNotFound as e:
|
|
self.logger.error('Could not find %s interface for %s',
|
|
self.interface, self.service_type)
|
|
raise ClientError(str(e))
|
|
return self.endpoint
|