From ee7fd2f5bdf0af94bf0d5e73db1da2a73d825e91 Mon Sep 17 00:00:00 2001 From: Stuart McLaren Date: Tue, 22 Oct 2013 15:51:49 +0000 Subject: [PATCH] Handle endpoints with versions consistently When using the cli the Glance client wraps the endpoint in a 'strip version' function. This means that endpoints of the following forms can both be used: https://region-x.images.example.com:443/v1 https://region-x.images.example.com:443 When calling the client library directly (as Ceilometer does) however only endpoints of the second form work. The cli and library should handle the two cases consistently. Addresses bug 1243276. Change-Id: Ice7b581fee32540a7057ba47433a10166a3caed2 --- glanceclient/common/utils.py | 13 +++++++++++ glanceclient/shell.py | 19 +--------------- glanceclient/v1/client.py | 6 +++-- glanceclient/v2/client.py | 6 +++-- tests/v1/test_client.py | 36 +++++++++++++++++++++++++++++ tests/v2/test_client.py | 44 ++++++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 tests/v1/test_client.py create mode 100644 tests/v2/test_client.py diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 4aec25de..c7728222 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -17,6 +17,7 @@ from __future__ import print_function import errno import os +import re import sys import uuid @@ -315,3 +316,15 @@ def get_data_file(args): else: # (3) no image data provided return None + + +def strip_version(endpoint): + """Strip version from the last component of endpoint if present.""" + # Get rid of trailing '/' if present + if endpoint.endswith('/'): + endpoint = endpoint[:-1] + url_bits = endpoint.split('/') + # regex to match 'v1' or 'v2.0' etc + if re.match('v\d+\.?\d*', url_bits[-1]): + endpoint = '/'.join(url_bits[:-1]) + return endpoint diff --git a/glanceclient/shell.py b/glanceclient/shell.py index 8140d35c..321b7e90 100644 --- a/glanceclient/shell.py +++ b/glanceclient/shell.py @@ -24,7 +24,6 @@ import json import logging import os from os.path import expanduser -import re import sys from keystoneclient.v2_0 import client as ksclient @@ -306,21 +305,6 @@ class OpenStackImagesShell(object): subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) - # TODO(dtroyer): move this into the common client support? - # Compatibility check to remove API version as the trailing component - # in a service endpoint; also removes a trailing '/' - def _strip_version(self, endpoint): - """Strip version from the last component of endpoint if present.""" - - # Get rid of trailing '/' if present - if endpoint.endswith('/'): - endpoint = endpoint[:-1] - url_bits = endpoint.split('/') - # regex to match 'v1' or 'v2.0' etc - if re.match('v\d+\.?\d*', url_bits[-1]): - endpoint = '/'.join(url_bits[:-1]) - return endpoint - def _get_ksclient(self, **kwargs): """Get an endpoint and auth token from Keystone. @@ -349,8 +333,7 @@ class OpenStackImagesShell(object): endpoint_kwargs['attr'] = 'region' endpoint_kwargs['filter_value'] = kwargs.get('region_name') - endpoint = client.service_catalog.url_for(**endpoint_kwargs) - return self._strip_version(endpoint) + return client.service_catalog.url_for(**endpoint_kwargs) def _get_image_url(self, args): """Translate the available url-related options into a single string. diff --git a/glanceclient/v1/client.py b/glanceclient/v1/client.py index 2b6a888d..23bb7377 100644 --- a/glanceclient/v1/client.py +++ b/glanceclient/v1/client.py @@ -14,6 +14,7 @@ # under the License. from glanceclient.common import http +from glanceclient.common import utils from glanceclient.v1 import image_members from glanceclient.v1 import images @@ -28,8 +29,9 @@ class Client(object): http requests. (optional) """ - def __init__(self, *args, **kwargs): + def __init__(self, endpoint, *args, **kwargs): """Initialize a new client for the Images v1 API.""" - self.http_client = http.HTTPClient(*args, **kwargs) + self.http_client = http.HTTPClient(utils.strip_version(endpoint), + *args, **kwargs) self.images = images.ImageManager(self.http_client) self.image_members = image_members.ImageMemberManager(self.http_client) diff --git a/glanceclient/v2/client.py b/glanceclient/v2/client.py index baf0bc4a..b3060964 100644 --- a/glanceclient/v2/client.py +++ b/glanceclient/v2/client.py @@ -16,6 +16,7 @@ import warlock from glanceclient.common import http +from glanceclient.common import utils from glanceclient.v2 import image_members from glanceclient.v2 import image_tags from glanceclient.v2 import images @@ -32,8 +33,9 @@ class Client(object): http requests. (optional) """ - def __init__(self, *args, **kwargs): - self.http_client = http.HTTPClient(*args, **kwargs) + def __init__(self, endpoint, *args, **kwargs): + self.http_client = http.HTTPClient(utils.strip_version(endpoint), + *args, **kwargs) self.schemas = schemas.Controller(self.http_client) image_model = self._get_image_model() self.images = images.Controller(self.http_client, diff --git a/tests/v1/test_client.py b/tests/v1/test_client.py new file mode 100644 index 00000000..c31c6b29 --- /dev/null +++ b/tests/v1/test_client.py @@ -0,0 +1,36 @@ +# Copyright 2013 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 testtools + +from glanceclient.v1 import client + + +class ClientTest(testtools.TestCase): + + def setUp(self): + super(ClientTest, self).setUp() + + def test_endpoint(self): + gc = client.Client("http://example.com") + self.assertEqual(gc.http_client.endpoint, "http://example.com") + + def test_versioned_endpoint(self): + gc = client.Client("http://example.com/v1") + self.assertEqual(gc.http_client.endpoint, "http://example.com") + + def test_versioned_endpoint_with_minor_revision(self): + gc = client.Client("http://example.com/v1.1") + self.assertEqual(gc.http_client.endpoint, "http://example.com") diff --git a/tests/v2/test_client.py b/tests/v2/test_client.py new file mode 100644 index 00000000..94752e3e --- /dev/null +++ b/tests/v2/test_client.py @@ -0,0 +1,44 @@ +# Copyright 2013 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. + +from mox3 import mox +import testtools + +from glanceclient.v2 import client + + +class ClientTest(testtools.TestCase): + + def setUp(self): + super(ClientTest, self).setUp() + self.mock = mox.Mox() + self.mock.StubOutWithMock(client.Client, '_get_image_model') + self.mock.StubOutWithMock(client.Client, '_get_member_model') + + def tearDown(self): + super(ClientTest, self).tearDown() + self.mock.UnsetStubs() + + def test_endpoint(self): + gc = client.Client("http://example.com") + self.assertEqual(gc.http_client.endpoint, "http://example.com") + + def test_versioned_endpoint(self): + gc = client.Client("http://example.com/v2") + self.assertEqual(gc.http_client.endpoint, "http://example.com") + + def test_versioned_endpoint_with_minor_revision(self): + gc = client.Client("http://example.com/v2.1") + self.assertEqual(gc.http_client.endpoint, "http://example.com")