2014-05-28 11:02:12 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
2013-09-18 22:38:25 +01:00
|
|
|
# Copyright 2012 OpenStack LLC.
|
|
|
|
# All 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 copy
|
2014-01-14 09:50:44 +02:00
|
|
|
import json
|
2013-09-18 22:38:25 +01:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import socket
|
2014-01-14 09:50:44 +02:00
|
|
|
import ssl
|
2015-04-07 11:43:54 -07:00
|
|
|
import textwrap
|
2013-09-18 22:38:25 +01:00
|
|
|
|
2014-07-31 23:33:21 -07:00
|
|
|
from keystoneclient import adapter
|
2013-11-11 12:09:16 +01:00
|
|
|
import six
|
2014-04-15 16:19:47 +02:00
|
|
|
import six.moves.urllib.parse as urlparse
|
2013-11-11 12:09:16 +01:00
|
|
|
|
2013-09-18 22:38:25 +01:00
|
|
|
from ironicclient import exc
|
|
|
|
|
|
|
|
|
2015-04-07 11:43:54 -07:00
|
|
|
# NOTE(deva): Record the latest version that this client was tested with.
|
|
|
|
# We still have a lot of work to do in the client to implement
|
|
|
|
# microversion support in the client properly! See
|
|
|
|
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
|
|
|
# for full details.
|
|
|
|
DEFAULT_VER = '1.6'
|
|
|
|
|
|
|
|
|
2013-09-18 22:38:25 +01:00
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
USER_AGENT = 'python-ironicclient'
|
|
|
|
CHUNKSIZE = 1024 * 64 # 64kB
|
|
|
|
|
2014-07-17 07:04:30 -07:00
|
|
|
API_VERSION = '/v1'
|
2015-04-07 11:43:54 -07:00
|
|
|
API_VERSION_SELECTED_STATES = ('user', 'negotiated', 'default')
|
2014-07-17 07:04:30 -07:00
|
|
|
|
2013-09-18 22:38:25 +01:00
|
|
|
|
2014-11-24 16:27:06 +02:00
|
|
|
def _trim_endpoint_api_version(url):
|
|
|
|
"""Trim API version and trailing slash from endpoint."""
|
|
|
|
return url.rstrip('/').rstrip(API_VERSION)
|
|
|
|
|
|
|
|
|
2014-12-16 15:35:53 +08:00
|
|
|
def _extract_error_json(body):
|
|
|
|
"""Return error_message from the HTTP response body."""
|
|
|
|
error_json = {}
|
|
|
|
try:
|
|
|
|
body_json = json.loads(body)
|
|
|
|
if 'error_message' in body_json:
|
|
|
|
raw_msg = body_json['error_message']
|
|
|
|
error_json = json.loads(raw_msg)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return error_json
|
|
|
|
|
|
|
|
|
2015-04-07 11:43:54 -07:00
|
|
|
class VersionNegotiationMixin(object):
|
|
|
|
def negotiate_version(self, conn, resp):
|
|
|
|
"""Negotiate the server version
|
|
|
|
|
|
|
|
Assumption: Called after receiving a 406 error when doing a request.
|
|
|
|
|
|
|
|
param conn: A connection object
|
|
|
|
param resp: The response object from http request
|
|
|
|
"""
|
|
|
|
if self.api_version_select_state not in API_VERSION_SELECTED_STATES:
|
|
|
|
raise RuntimeError(
|
|
|
|
'Error: self.api_version_select_state should be one of the '
|
|
|
|
'values in: "%(valid)s" but had the value: "%(value)s"' %
|
|
|
|
{'valid': ', '.join(API_VERSION_SELECTED_STATES),
|
|
|
|
'value': self.api_version_select_state})
|
|
|
|
min_ver, max_ver = self._parse_version_headers(resp)
|
|
|
|
# NOTE: servers before commit 32fb6e99 did not return version headers
|
|
|
|
# on error, so we need to perform a GET to determine
|
|
|
|
# the supported version range
|
|
|
|
if not max_ver:
|
|
|
|
LOG.debug('No version header in response, requesting from server')
|
|
|
|
if self.os_ironic_api_version:
|
|
|
|
base_version = ("/v%s" %
|
|
|
|
str(self.os_ironic_api_version).split('.')[0])
|
|
|
|
else:
|
|
|
|
base_version = API_VERSION
|
|
|
|
resp = self._make_simple_request(conn, 'GET', base_version)
|
|
|
|
min_ver, max_ver = self._parse_version_headers(resp)
|
|
|
|
# If the user requested an explicit version or we have negotiated a
|
|
|
|
# version and still failing then error now. The server could
|
|
|
|
# support the version requested but the requested operation may not
|
|
|
|
# be supported by the requested version.
|
|
|
|
if self.api_version_select_state == 'user':
|
|
|
|
raise exc.UnsupportedVersion(textwrap.fill(
|
|
|
|
"Requested API version %(req)s is not supported by the "
|
|
|
|
"server or the requested operation is not supported by the "
|
|
|
|
"requested version. Supported version range is %(min)s to "
|
|
|
|
"%(max)s"
|
|
|
|
% {'req': self.os_ironic_api_version,
|
|
|
|
'min': min_ver, 'max': max_ver}))
|
|
|
|
if self.api_version_select_state == 'negotiated':
|
|
|
|
raise exc.UnsupportedVersion(textwrap.fill(
|
|
|
|
"No API version was specified and the requested operation was "
|
|
|
|
"not supported by the client's negotiated API version "
|
|
|
|
"%(req)s. Supported version range is: %(min)s to %(max)s"
|
|
|
|
% {'req': self.os_ironic_api_version,
|
|
|
|
'min': min_ver, 'max': max_ver}))
|
|
|
|
|
|
|
|
# TODO(deva): cache the negotiated version for this server
|
|
|
|
negotiated_ver = min(self.os_ironic_api_version, max_ver)
|
|
|
|
if negotiated_ver < min_ver:
|
|
|
|
negotiated_ver = min_ver
|
|
|
|
# server handles microversions, but doesn't support
|
|
|
|
# the requested version, so try a negotiated version
|
|
|
|
self.api_version_select_state = 'negotiated'
|
|
|
|
self.os_ironic_api_version = negotiated_ver
|
|
|
|
LOG.debug('Negotiated API version is %s', negotiated_ver)
|
|
|
|
|
|
|
|
return negotiated_ver
|
|
|
|
|
|
|
|
def _generic_parse_version_headers(self, accessor_func):
|
|
|
|
min_ver = accessor_func('X-OpenStack-Ironic-API-Minimum-Version',
|
|
|
|
None)
|
|
|
|
max_ver = accessor_func('X-OpenStack-Ironic-API-Maximum-Version',
|
|
|
|
None)
|
|
|
|
return min_ver, max_ver
|
|
|
|
|
|
|
|
def _parse_version_headers(self, accessor_func):
|
|
|
|
# NOTE(jlvillal): Declared for unit testing purposes
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def _make_simple_request(self, conn, method, url):
|
|
|
|
# NOTE(jlvillal): Declared for unit testing purposes
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
|
|
class HTTPClient(VersionNegotiationMixin):
|
2013-09-18 22:38:25 +01:00
|
|
|
|
|
|
|
def __init__(self, endpoint, **kwargs):
|
|
|
|
self.endpoint = endpoint
|
2014-11-24 16:27:06 +02:00
|
|
|
self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
|
2013-09-18 22:38:25 +01:00
|
|
|
self.auth_token = kwargs.get('token')
|
2014-06-30 09:11:40 +09:30
|
|
|
self.auth_ref = kwargs.get('auth_ref')
|
2015-04-07 11:43:54 -07:00
|
|
|
self.os_ironic_api_version = kwargs.get('os_ironic_api_version',
|
|
|
|
DEFAULT_VER)
|
|
|
|
self.api_version_select_state = kwargs.get(
|
|
|
|
'api_version_select_state', 'default')
|
2013-09-18 22:38:25 +01:00
|
|
|
self.connection_params = self.get_connection_params(endpoint, **kwargs)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_connection_params(endpoint, **kwargs):
|
2014-04-15 16:19:47 +02:00
|
|
|
parts = urlparse.urlparse(endpoint)
|
2013-09-18 22:38:25 +01:00
|
|
|
|
2014-11-24 16:27:06 +02:00
|
|
|
path = _trim_endpoint_api_version(parts.path)
|
2014-07-17 07:04:30 -07:00
|
|
|
|
|
|
|
_args = (parts.hostname, parts.port, path)
|
2013-09-18 22:38:25 +01:00
|
|
|
_kwargs = {'timeout': (float(kwargs.get('timeout'))
|
|
|
|
if kwargs.get('timeout') else 600)}
|
|
|
|
|
|
|
|
if parts.scheme == 'https':
|
|
|
|
_class = VerifiedHTTPSConnection
|
|
|
|
_kwargs['ca_file'] = kwargs.get('ca_file', None)
|
|
|
|
_kwargs['cert_file'] = kwargs.get('cert_file', None)
|
|
|
|
_kwargs['key_file'] = kwargs.get('key_file', None)
|
|
|
|
_kwargs['insecure'] = kwargs.get('insecure', False)
|
|
|
|
elif parts.scheme == 'http':
|
2013-11-11 11:44:46 +01:00
|
|
|
_class = six.moves.http_client.HTTPConnection
|
2013-09-18 22:38:25 +01:00
|
|
|
else:
|
|
|
|
msg = 'Unsupported scheme: %s' % parts.scheme
|
2014-04-16 14:09:00 +03:00
|
|
|
raise exc.EndpointException(msg)
|
2013-09-18 22:38:25 +01:00
|
|
|
|
|
|
|
return (_class, _args, _kwargs)
|
|
|
|
|
|
|
|
def get_connection(self):
|
|
|
|
_class = self.connection_params[0]
|
|
|
|
try:
|
|
|
|
return _class(*self.connection_params[1][0:2],
|
|
|
|
**self.connection_params[2])
|
2013-11-11 11:44:46 +01:00
|
|
|
except six.moves.http_client.InvalidURL:
|
2014-04-16 14:09:00 +03:00
|
|
|
raise exc.EndpointException()
|
2013-09-18 22:38:25 +01:00
|
|
|
|
|
|
|
def log_curl_request(self, method, url, kwargs):
|
|
|
|
curl = ['curl -i -X %s' % method]
|
|
|
|
|
|
|
|
for (key, value) in kwargs['headers'].items():
|
|
|
|
header = '-H \'%s: %s\'' % (key, value)
|
|
|
|
curl.append(header)
|
|
|
|
|
|
|
|
conn_params_fmt = [
|
|
|
|
('key_file', '--key %s'),
|
|
|
|
('cert_file', '--cert %s'),
|
|
|
|
('ca_file', '--cacert %s'),
|
|
|
|
]
|
|
|
|
for (key, fmt) in conn_params_fmt:
|
|
|
|
value = self.connection_params[2].get(key)
|
|
|
|
if value:
|
|
|
|
curl.append(fmt % value)
|
|
|
|
|
|
|
|
if self.connection_params[2].get('insecure'):
|
|
|
|
curl.append('-k')
|
|
|
|
|
|
|
|
if 'body' in kwargs:
|
|
|
|
curl.append('-d \'%s\'' % kwargs['body'])
|
|
|
|
|
2014-11-24 16:27:06 +02:00
|
|
|
curl.append(urlparse.urljoin(self.endpoint_trimmed, url))
|
2013-09-18 22:38:25 +01:00
|
|
|
LOG.debug(' '.join(curl))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def log_http_response(resp, body=None):
|
|
|
|
status = (resp.version / 10.0, resp.status, resp.reason)
|
|
|
|
dump = ['\nHTTP/%.1f %s %s' % status]
|
|
|
|
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
|
|
|
|
dump.append('')
|
|
|
|
if body:
|
|
|
|
dump.extend([body, ''])
|
|
|
|
LOG.debug('\n'.join(dump))
|
|
|
|
|
|
|
|
def _make_connection_url(self, url):
|
|
|
|
(_class, _args, _kwargs) = self.connection_params
|
|
|
|
base_url = _args[2]
|
2014-07-17 07:04:30 -07:00
|
|
|
return '%s/%s' % (base_url, url.lstrip('/'))
|
2013-09-18 22:38:25 +01:00
|
|
|
|
2015-04-07 11:43:54 -07:00
|
|
|
def _parse_version_headers(self, resp):
|
|
|
|
return self._generic_parse_version_headers(resp.getheader)
|
|
|
|
|
|
|
|
def _make_simple_request(self, conn, method, url):
|
|
|
|
conn.request(method, self._make_connection_url(url))
|
|
|
|
return conn.getresponse()
|
|
|
|
|
2013-09-18 22:38:25 +01:00
|
|
|
def _http_request(self, url, method, **kwargs):
|
|
|
|
"""Send an http request with the specified characteristics.
|
|
|
|
|
|
|
|
Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
|
|
|
|
as setting headers and error handling.
|
|
|
|
"""
|
|
|
|
# Copy the kwargs so we can reuse the original in case of redirects
|
|
|
|
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
|
|
|
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
2015-02-13 10:47:16 +08:00
|
|
|
if self.os_ironic_api_version:
|
|
|
|
kwargs['headers'].setdefault('X-OpenStack-Ironic-API-Version',
|
|
|
|
self.os_ironic_api_version)
|
2013-09-25 17:54:21 -07:00
|
|
|
if self.auth_token:
|
|
|
|
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
2013-09-18 22:38:25 +01:00
|
|
|
|
|
|
|
self.log_curl_request(method, url, kwargs)
|
|
|
|
conn = self.get_connection()
|
|
|
|
|
|
|
|
try:
|
|
|
|
conn_url = self._make_connection_url(url)
|
|
|
|
conn.request(method, conn_url, **kwargs)
|
|
|
|
resp = conn.getresponse()
|
2015-04-07 11:43:54 -07:00
|
|
|
|
|
|
|
# TODO(deva): implement graceful client downgrade when connecting
|
|
|
|
# to servers that did not support microversions. Details here:
|
|
|
|
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html#use-case-3b-new-client-communicating-with-a-old-ironic-user-specified # noqa
|
|
|
|
|
|
|
|
if resp.status == 406:
|
|
|
|
negotiated_ver = self.negotiate_version(conn, resp)
|
|
|
|
kwargs['headers']['X-OpenStack-Ironic-API-Version'] = (
|
|
|
|
negotiated_ver)
|
|
|
|
return self._http_request(url, method, **kwargs)
|
|
|
|
|
2013-09-18 22:38:25 +01:00
|
|
|
except socket.gaierror as e:
|
|
|
|
message = ("Error finding address for %(url)s: %(e)s"
|
|
|
|
% dict(url=url, e=e))
|
2014-04-16 14:09:00 +03:00
|
|
|
raise exc.EndpointNotFound(message)
|
2013-09-18 22:38:25 +01:00
|
|
|
except (socket.error, socket.timeout) as e:
|
|
|
|
endpoint = self.endpoint
|
|
|
|
message = ("Error communicating with %(endpoint)s %(e)s"
|
|
|
|
% dict(endpoint=endpoint, e=e))
|
2014-04-16 14:09:00 +03:00
|
|
|
raise exc.ConnectionRefused(message)
|
2013-09-18 22:38:25 +01:00
|
|
|
|
|
|
|
body_iter = ResponseBodyIterator(resp)
|
|
|
|
|
|
|
|
# Read body into string if it isn't obviously image data
|
2013-10-02 16:16:31 +01:00
|
|
|
body_str = None
|
2013-09-18 22:38:25 +01:00
|
|
|
if resp.getheader('content-type', None) != 'application/octet-stream':
|
|
|
|
body_str = ''.join([chunk for chunk in body_iter])
|
|
|
|
self.log_http_response(resp, body_str)
|
2013-10-16 03:23:16 +08:00
|
|
|
body_iter = six.StringIO(body_str)
|
2013-09-18 22:38:25 +01:00
|
|
|
else:
|
|
|
|
self.log_http_response(resp)
|
|
|
|
|
|
|
|
if 400 <= resp.status < 600:
|
|
|
|
LOG.warn("Request returned failure status.")
|
2014-12-16 15:35:53 +08:00
|
|
|
error_json = _extract_error_json(body_str)
|
2014-04-16 14:09:00 +03:00
|
|
|
raise exc.from_response(
|
|
|
|
resp, error_json.get('faultstring'),
|
|
|
|
error_json.get('debuginfo'), method, url)
|
2013-09-18 22:38:25 +01:00
|
|
|
elif resp.status in (301, 302, 305):
|
|
|
|
# Redirected. Reissue the request to the new location.
|
|
|
|
return self._http_request(resp['location'], method, **kwargs)
|
|
|
|
elif resp.status == 300:
|
2014-04-16 14:09:00 +03:00
|
|
|
raise exc.from_response(resp, method=method, url=url)
|
2013-09-18 22:38:25 +01:00
|
|
|
|
|
|
|
return resp, body_iter
|
|
|
|
|
|
|
|
def json_request(self, method, url, **kwargs):
|
|
|
|
kwargs.setdefault('headers', {})
|
|
|
|
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
|
|
|
kwargs['headers'].setdefault('Accept', 'application/json')
|
|
|
|
|
|
|
|
if 'body' in kwargs:
|
|
|
|
kwargs['body'] = json.dumps(kwargs['body'])
|
|
|
|
|
|
|
|
resp, body_iter = self._http_request(url, method, **kwargs)
|
|
|
|
content_type = resp.getheader('content-type', None)
|
|
|
|
|
|
|
|
if resp.status == 204 or resp.status == 205 or content_type is None:
|
|
|
|
return resp, list()
|
|
|
|
|
|
|
|
if 'application/json' in content_type:
|
|
|
|
body = ''.join([chunk for chunk in body_iter])
|
|
|
|
try:
|
|
|
|
body = json.loads(body)
|
|
|
|
except ValueError:
|
|
|
|
LOG.error('Could not decode response body as JSON')
|
|
|
|
else:
|
|
|
|
body = None
|
|
|
|
|
|
|
|
return resp, body
|
|
|
|
|
|
|
|
def raw_request(self, method, url, **kwargs):
|
|
|
|
kwargs.setdefault('headers', {})
|
|
|
|
kwargs['headers'].setdefault('Content-Type',
|
|
|
|
'application/octet-stream')
|
|
|
|
return self._http_request(url, method, **kwargs)
|
|
|
|
|
|
|
|
|
2013-11-11 11:44:46 +01:00
|
|
|
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
|
2013-09-18 22:38:25 +01:00
|
|
|
"""httplib-compatibile connection using client-side SSL authentication
|
|
|
|
|
|
|
|
:see http://code.activestate.com/recipes/
|
|
|
|
577548-https-httplib-client-connection-with-certificate-v/
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, host, port, key_file=None, cert_file=None,
|
|
|
|
ca_file=None, timeout=None, insecure=False):
|
2013-11-11 11:44:46 +01:00
|
|
|
six.moves.http_client.HTTPSConnection.__init__(self, host, port,
|
2015-03-10 14:07:06 -07:00
|
|
|
key_file=key_file,
|
|
|
|
cert_file=cert_file)
|
2013-09-18 22:38:25 +01:00
|
|
|
self.key_file = key_file
|
|
|
|
self.cert_file = cert_file
|
|
|
|
if ca_file is not None:
|
|
|
|
self.ca_file = ca_file
|
|
|
|
else:
|
|
|
|
self.ca_file = self.get_system_ca_file()
|
|
|
|
self.timeout = timeout
|
|
|
|
self.insecure = insecure
|
|
|
|
|
|
|
|
def connect(self):
|
|
|
|
"""Connect to a host on a given (SSL) port.
|
2014-10-02 10:40:11 +00:00
|
|
|
|
2013-09-18 22:38:25 +01:00
|
|
|
If ca_file is pointing somewhere, use it to check Server Certificate.
|
|
|
|
|
|
|
|
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
|
|
|
|
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
|
|
|
|
ssl.wrap_socket(), which forces SSL to check server certificate against
|
|
|
|
our client certificate.
|
|
|
|
"""
|
|
|
|
sock = socket.create_connection((self.host, self.port), self.timeout)
|
|
|
|
|
|
|
|
if self._tunnel_host:
|
|
|
|
self.sock = sock
|
|
|
|
self._tunnel()
|
|
|
|
|
|
|
|
if self.insecure is True:
|
|
|
|
kwargs = {'cert_reqs': ssl.CERT_NONE}
|
|
|
|
else:
|
|
|
|
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
|
|
|
|
|
|
|
|
if self.cert_file:
|
|
|
|
kwargs['certfile'] = self.cert_file
|
|
|
|
if self.key_file:
|
|
|
|
kwargs['keyfile'] = self.key_file
|
|
|
|
|
|
|
|
self.sock = ssl.wrap_socket(sock, **kwargs)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_system_ca_file():
|
|
|
|
"""Return path to system default CA file."""
|
|
|
|
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
|
|
|
# Suse, FreeBSD/OpenBSD
|
|
|
|
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
|
|
|
|
'/etc/pki/tls/certs/ca-bundle.crt',
|
|
|
|
'/etc/ssl/ca-bundle.pem',
|
|
|
|
'/etc/ssl/cert.pem']
|
|
|
|
for ca in ca_path:
|
|
|
|
if os.path.exists(ca):
|
|
|
|
return ca
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2015-04-07 11:43:54 -07:00
|
|
|
class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
|
2014-07-31 23:33:21 -07:00
|
|
|
"""HTTP client based on Keystone client session."""
|
|
|
|
|
2015-04-07 11:43:54 -07:00
|
|
|
def _parse_version_headers(self, resp):
|
|
|
|
return self._generic_parse_version_headers(resp.headers.get)
|
|
|
|
|
|
|
|
def _make_simple_request(self, conn, method, url):
|
|
|
|
# NOTE: conn is self.session for this class
|
|
|
|
return conn.request(url, method, raise_exc=False)
|
|
|
|
|
2014-07-31 23:33:21 -07:00
|
|
|
def _http_request(self, url, method, **kwargs):
|
|
|
|
kwargs.setdefault('user_agent', USER_AGENT)
|
|
|
|
kwargs.setdefault('auth', self.auth)
|
2015-02-13 10:47:16 +08:00
|
|
|
if getattr(self, 'os_ironic_api_version', None):
|
|
|
|
kwargs['headers'].setdefault('X-OpenStack-Ironic-API-Version',
|
|
|
|
self.os_ironic_api_version)
|
2014-07-31 23:33:21 -07:00
|
|
|
|
|
|
|
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
|
|
|
endpoint_filter.setdefault('interface', self.interface)
|
|
|
|
endpoint_filter.setdefault('service_type', self.service_type)
|
|
|
|
endpoint_filter.setdefault('region_name', self.region_name)
|
|
|
|
|
|
|
|
resp = self.session.request(url, method,
|
|
|
|
raise_exc=False, **kwargs)
|
2015-04-07 11:43:54 -07:00
|
|
|
if resp.status_code == 406:
|
|
|
|
negotiated_ver = self.negotiate_version(self.session, resp)
|
|
|
|
kwargs['headers']['X-OpenStack-Ironic-API-Version'] = (
|
|
|
|
negotiated_ver)
|
|
|
|
return self._http_request(url, method, **kwargs)
|
2014-07-31 23:33:21 -07:00
|
|
|
if 400 <= resp.status_code < 600:
|
2014-12-16 15:35:53 +08:00
|
|
|
error_json = _extract_error_json(resp.content)
|
|
|
|
raise exc.from_response(resp, error_json.get('faultstring'),
|
2015-03-10 14:07:06 -07:00
|
|
|
error_json.get('debuginfo'), method, url)
|
2014-07-31 23:33:21 -07:00
|
|
|
elif resp.status_code in (301, 302, 305):
|
|
|
|
# Redirected. Reissue the request to the new location.
|
|
|
|
location = resp.headers.get('location')
|
|
|
|
resp = self._http_request(location, method, **kwargs)
|
|
|
|
elif resp.status_code == 300:
|
|
|
|
raise exc.from_response(resp, method=method, url=url)
|
|
|
|
return resp
|
|
|
|
|
|
|
|
def json_request(self, method, url, **kwargs):
|
|
|
|
kwargs.setdefault('headers', {})
|
|
|
|
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
|
|
|
kwargs['headers'].setdefault('Accept', 'application/json')
|
|
|
|
|
|
|
|
if 'body' in kwargs:
|
|
|
|
kwargs['data'] = json.dumps(kwargs.pop('body'))
|
|
|
|
|
|
|
|
resp = self._http_request(url, method, **kwargs)
|
|
|
|
body = resp.content
|
|
|
|
content_type = resp.headers.get('content-type', None)
|
|
|
|
status = resp.status_code
|
|
|
|
if status == 204 or status == 205 or content_type is None:
|
|
|
|
return resp, list()
|
|
|
|
if 'application/json' in content_type:
|
|
|
|
try:
|
|
|
|
body = resp.json()
|
|
|
|
except ValueError:
|
|
|
|
LOG.error('Could not decode response body as JSON')
|
|
|
|
else:
|
|
|
|
body = None
|
|
|
|
|
|
|
|
return resp, body
|
|
|
|
|
|
|
|
def raw_request(self, method, url, **kwargs):
|
|
|
|
kwargs.setdefault('headers', {})
|
|
|
|
kwargs['headers'].setdefault('Content-Type',
|
|
|
|
'application/octet-stream')
|
|
|
|
return self._http_request(url, method, **kwargs)
|
|
|
|
|
|
|
|
|
2013-09-18 22:38:25 +01:00
|
|
|
class ResponseBodyIterator(object):
|
|
|
|
"""A class that acts as an iterator over an HTTP response."""
|
|
|
|
|
|
|
|
def __init__(self, resp):
|
|
|
|
self.resp = resp
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
while True:
|
|
|
|
yield self.next()
|
|
|
|
|
|
|
|
def next(self):
|
|
|
|
chunk = self.resp.read(CHUNKSIZE)
|
|
|
|
if chunk:
|
|
|
|
return chunk
|
|
|
|
else:
|
|
|
|
raise StopIteration()
|
2014-07-31 23:33:21 -07:00
|
|
|
|
|
|
|
|
|
|
|
def _construct_http_client(*args, **kwargs):
|
|
|
|
session = kwargs.pop('session', None)
|
|
|
|
auth = kwargs.pop('auth', None)
|
|
|
|
|
|
|
|
if session:
|
|
|
|
service_type = kwargs.pop('service_type', 'baremetal')
|
|
|
|
interface = kwargs.pop('endpoint_type', None)
|
|
|
|
region_name = kwargs.pop('region_name', None)
|
2015-04-07 11:43:54 -07:00
|
|
|
os_ironic_api_version = kwargs.pop('os_ironic_api_version',
|
|
|
|
DEFAULT_VER)
|
2015-02-13 10:47:16 +08:00
|
|
|
session_client = SessionClient(session=session,
|
|
|
|
auth=auth,
|
|
|
|
interface=interface,
|
|
|
|
service_type=service_type,
|
|
|
|
region_name=region_name,
|
|
|
|
service_name=None,
|
|
|
|
user_agent='python-ironicclient')
|
|
|
|
# Append an ironic specific variable to session
|
|
|
|
session_client.os_ironic_api_version = os_ironic_api_version
|
2015-04-07 11:43:54 -07:00
|
|
|
session_client.api_version_select_state = (
|
|
|
|
kwargs.pop('api_version_select_state', 'default'))
|
2015-02-13 10:47:16 +08:00
|
|
|
return session_client
|
2014-07-31 23:33:21 -07:00
|
|
|
else:
|
|
|
|
return HTTPClient(*args, **kwargs)
|