Basic support for API versions

Currently only one version (1, 0) is supported, and no header is
send to the server. However, this change allows calling code to
fix version (1, 0) already.

Change-Id: I1636e39d5a4c4344bd3e159d559886607d0c285e
Closes-Bug: #1465359
This commit is contained in:
Dmitry Tantsur
2015-06-18 09:26:53 +02:00
parent a18811b908
commit 45155822d9
4 changed files with 72 additions and 2 deletions

View File

@@ -46,6 +46,16 @@ Every call accepts additional optional arguments:
* ``base_url`` **ironic-inspector** API endpoint, defaults to
``127.0.0.1:5050``,
* ``auth_token`` Keystone authentication token.
* ``api_version`` requested API version; can be a tuple (MAJ, MIN), string
"MAJ.MIN" or integer (only major). Right now only (1, 0) is supported, other
versions will raise an exception. Defaults to ``DEFAULT_API_VERSION``.
Two constants are exposed by the client:
* ``DEFAULT_API_VERSION`` server API version used by default.
* ``MAX_API_VERSION`` maximum API version this client was designed to work
with. Right now providing bigger value for ``api_version`` argument raises
on exception, this limitation may be lifted later.
Refer to HTTP-API.rst_ for information on the **ironic-inspector** HTTP API.

View File

@@ -12,3 +12,4 @@
# limitations under the License.
from .client import introspect, get_status # noqa
from .client import DEFAULT_API_VERSION, MAX_API_VERSION # noqa

View File

@@ -26,6 +26,10 @@ _ERROR_ENCODING = 'utf-8'
LOG = logging.getLogger('ironic_inspector_client')
DEFAULT_API_VERSION = (1, 0)
MAX_API_VERSION = (1, 0)
def _prepare(base_url, auth_token):
base_url = (base_url or _DEFAULT_URL).rstrip('/')
if not base_url.endswith('v1'):
@@ -34,6 +38,30 @@ def _prepare(base_url, auth_token):
return base_url, headers
def _check_api_version(api_version):
if isinstance(api_version, int):
api_version = (api_version, 0)
if isinstance(api_version, six.string_types):
try:
api_version = tuple(int(x) for x in api_version.split('.'))
except (ValueError, TypeError):
raise ValueError(_("Malformed API version: expect tuple, string "
"in form of X.Y or integer"))
api_version = tuple(api_version)
if not all(isinstance(x, int) for x in api_version):
raise TypeError(_("All API version components should be integers"))
if len(api_version) != 2:
raise ValueError(_("API version should be of length 2"))
# TODO(dtantsur): support more than one API version
if api_version != (1, 0):
raise RuntimeError(_("Unsupported API version %s, only (1, 0) is "
"supported in this version of client"),
api_version)
return api_version
class ClientError(requests.HTTPError):
"""Error returned from a server."""
def __init__(self, response):
@@ -56,7 +84,8 @@ class ClientError(requests.HTTPError):
def introspect(uuid, base_url=None, auth_token=None,
new_ipmi_password=None, new_ipmi_username=None):
new_ipmi_password=None, new_ipmi_username=None,
api_version=DEFAULT_API_VERSION):
"""Start introspection for a node.
:param uuid: node uuid
@@ -68,6 +97,8 @@ def introspect(uuid, base_url=None, auth_token=None,
:param new_ipmi_username: if new_ipmi_password is set, this values sets
new IPMI user name. Defaults to one in
driver_info.
:param api_version: requested ironic-inspector API version, defaults to
``DEFAULT_API_VERSION`` attribute.
:raises: ClientError on error reported from a server
:raises: *requests* library exception on connection problems.
"""
@@ -75,6 +106,7 @@ def introspect(uuid, base_url=None, auth_token=None,
raise TypeError(_("Expected string for uuid argument, got %r") % uuid)
if new_ipmi_username and not new_ipmi_password:
raise ValueError(_("Setting IPMI user name requires a new password"))
_check_api_version(api_version)
base_url, headers = _prepare(base_url, auth_token)
params = {'new_ipmi_username': new_ipmi_username,
@@ -84,7 +116,8 @@ def introspect(uuid, base_url=None, auth_token=None,
ClientError.raise_if_needed(res)
def get_status(uuid, base_url=None, auth_token=None):
def get_status(uuid, base_url=None, auth_token=None,
api_version=DEFAULT_API_VERSION):
"""Get introspection status for a node.
New in ironic-inspector version 1.0.0.
@@ -92,11 +125,14 @@ def get_status(uuid, base_url=None, auth_token=None):
:param base_url: *ironic-inspector* URL in form: http://host:port[/ver],
defaults to ``http://<current host>:5050/v1``.
:param auth_token: Keystone authentication token.
:param api_version: requested ironic-inspector API version, defaults to
``DEFAULT_API_VERSION`` attribute.
:raises: ClientError on error reported from a server
:raises: *requests* library exception on connection problems.
"""
if not isinstance(uuid, six.string_types):
raise TypeError(_("Expected string for uuid argument, got %r") % uuid)
_check_api_version(api_version)
base_url, headers = _prepare(base_url, auth_token)
res = requests.get("%s/introspection/%s" % (base_url, uuid),

View File

@@ -137,3 +137,26 @@ class TestGetStatus(unittest.TestCase):
mock_post.return_value.content = b'42'
self.assertRaisesRegexp(client.ClientError, "42",
client.get_status, self.uuid)
class TestCheckVesion(unittest.TestCase):
def test_tuple(self):
self.assertEqual((1, 0), client._check_api_version((1, 0)))
def test_int(self):
self.assertEqual((1, 0), client._check_api_version(1))
def test_str(self):
self.assertEqual((1, 0), client._check_api_version("1.0"))
def test_invalid_tuple(self):
self.assertRaises(TypeError, client._check_api_version, (1, "x"))
self.assertRaises(ValueError, client._check_api_version, (1, 2, 3))
def test_invalid_str(self):
self.assertRaises(ValueError, client._check_api_version, "a.b")
self.assertRaises(ValueError, client._check_api_version, "1.2.3")
self.assertRaises(ValueError, client._check_api_version, "foo")
def test_only_1_0_supported(self):
self.assertRaises(RuntimeError, client._check_api_version, (1, 1))