Use API v2 as default

Now we have claimed v2 is the current API version of Glance,
we should change the Glance client as well to be consistent
with Glance server.

DocImpact

Change-Id: I09c9e409d149e2d797785591183e06c13229b7f7
This commit is contained in:
Fei Long Wang
2015-06-22 15:47:48 +12:00
parent 9284eb4253
commit 181131ef2e
3 changed files with 107 additions and 29 deletions

View File

@@ -22,6 +22,7 @@ from __future__ import print_function
import argparse import argparse
import copy import copy
import getpass import getpass
import hashlib
import json import json
import logging import logging
import os import os
@@ -263,7 +264,7 @@ class OpenStackImagesShell(object):
parser.add_argument('--os-image-api-version', parser.add_argument('--os-image-api-version',
default=utils.env('OS_IMAGE_API_VERSION', default=utils.env('OS_IMAGE_API_VERSION',
default=None), default=None),
help='Defaults to env[OS_IMAGE_API_VERSION] or 1.') help='Defaults to env[OS_IMAGE_API_VERSION] or 2.')
parser.add_argument('--os_image_api_version', parser.add_argument('--os_image_api_version',
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
@@ -551,9 +552,13 @@ class OpenStackImagesShell(object):
def _cache_schemas(self, options, home_dir='~/.glanceclient'): def _cache_schemas(self, options, home_dir='~/.glanceclient'):
homedir = os.path.expanduser(home_dir) homedir = os.path.expanduser(home_dir)
if not os.path.exists(homedir): path_prefix = homedir
if options.os_auth_url:
hash_host = hashlib.sha1(options.os_auth_url.encode('utf-8'))
path_prefix = os.path.join(path_prefix, hash_host.hexdigest())
if not os.path.exists(path_prefix):
try: try:
os.makedirs(homedir) os.makedirs(path_prefix)
except OSError as e: except OSError as e:
# This avoids glanceclient to crash if it can't write to # This avoids glanceclient to crash if it can't write to
# ~/.glanceclient, which may happen on some env (for me, # ~/.glanceclient, which may happen on some env (for me,
@@ -561,12 +566,12 @@ class OpenStackImagesShell(object):
# /var/lib/jenkins). # /var/lib/jenkins).
msg = '%s' % e msg = '%s' % e
print(encodeutils.safe_decode(msg), file=sys.stderr) print(encodeutils.safe_decode(msg), file=sys.stderr)
resources = ['image', 'metadefs/namespace', 'metadefs/resource_type'] resources = ['image', 'metadefs/namespace', 'metadefs/resource_type']
schema_file_paths = [homedir + os.sep + x + '_schema.json' schema_file_paths = [os.path.join(path_prefix, x + '_schema.json')
for x in ['image', 'namespace', 'resource_type']] for x in ['image', 'namespace', 'resource_type']]
client = None client = None
failed_download_schema = 0
for resource, schema_file_path in zip(resources, schema_file_paths): for resource, schema_file_path in zip(resources, schema_file_paths):
if (not os.path.exists(schema_file_path)) or options.get_schema: if (not os.path.exists(schema_file_path)) or options.get_schema:
try: try:
@@ -580,8 +585,11 @@ class OpenStackImagesShell(object):
except Exception: except Exception:
# NOTE(esheffield) do nothing here, we'll get a message # NOTE(esheffield) do nothing here, we'll get a message
# later if the schema is missing # later if the schema is missing
failed_download_schema += 1
pass pass
return failed_download_schema >= len(resources)
def main(self, argv): def main(self, argv):
# Parse args once to find version # Parse args once to find version
@@ -605,7 +613,7 @@ class OpenStackImagesShell(object):
# build available subcommands based on version # build available subcommands based on version
try: try:
api_version = int(options.os_image_api_version or url_version or 1) api_version = int(options.os_image_api_version or url_version or 2)
if api_version not in SUPPORTED_VERSIONS: if api_version not in SUPPORTED_VERSIONS:
raise ValueError raise ValueError
except ValueError: except ValueError:
@@ -614,7 +622,12 @@ class OpenStackImagesShell(object):
utils.exit(msg=msg) utils.exit(msg=msg)
if api_version == 2: if api_version == 2:
self._cache_schemas(options) switch_version = self._cache_schemas(options)
if switch_version:
print('WARNING: The client is falling back to v1 because'
' the accessing to v2 failed. This behavior will'
' be removed in future versions')
api_version = 1
try: try:
subcommand_parser = self.get_subcommand_parser(api_version) subcommand_parser = self.get_subcommand_parser(api_version)

View File

@@ -24,26 +24,61 @@ class SimpleReadOnlyGlanceClientTest(base.ClientTestBase):
This only exercises client commands that are read only. This only exercises client commands that are read only.
""" """
def test_list(self): def test_list_v1(self):
out = self.glance('image-list') out = self.glance('--os-image-api-version 1 image-list')
endpoints = self.parser.listing(out) endpoints = self.parser.listing(out)
self.assertTableStruct(endpoints, [ self.assertTableStruct(endpoints, [
'ID', 'Name', 'Disk Format', 'Container Format', 'ID', 'Name', 'Disk Format', 'Container Format',
'Size', 'Status']) 'Size', 'Status'])
def test_list_v2(self):
out = self.glance('--os-image-api-version 2 image-list')
endpoints = self.parser.listing(out)
self.assertTableStruct(endpoints, ['ID', 'Name'])
def test_fake_action(self): def test_fake_action(self):
self.assertRaises(exceptions.CommandFailed, self.assertRaises(exceptions.CommandFailed,
self.glance, self.glance,
'this-does-not-exist') 'this-does-not-exist')
def test_member_list(self): def test_member_list_v1(self):
tenant_name = '--tenant-id %s' % self.tenant_name tenant_name = '--tenant-id %s' % self.tenant_name
out = self.glance('member-list', out = self.glance('--os-image-api-version 1 member-list',
params=tenant_name) params=tenant_name)
endpoints = self.parser.listing(out) endpoints = self.parser.listing(out)
self.assertTableStruct(endpoints, self.assertTableStruct(endpoints,
['Image ID', 'Member ID', 'Can Share']) ['Image ID', 'Member ID', 'Can Share'])
def test_member_list_v2(self):
try:
# NOTE(flwang): If set disk-format and container-format, Jenkins
# will raise an error said can't recognize the params, thouhg it
# works fine at local. Without the two params, Glance will
# complain. So we just catch the exception can skip it.
self.glance('--os-image-api-version 2 image-create --name temp')
except Exception:
pass
out = self.glance('--os-image-api-version 2 image-list'
' --visibility private')
image_list = self.parser.listing(out)
# NOTE(flwang): Because the member-list command of v2 is using
# image-id as required parameter, so we have to get a valid image id
# based on current environment. If there is no valid image id, we will
# pass in a fake one and expect a 404 error.
if len(image_list) > 0:
param_image_id = '--image-id %s' % image_list[0]['ID']
out = self.glance('--os-image-api-version 2 member-list',
params=param_image_id)
endpoints = self.parser.listing(out)
self.assertTableStruct(endpoints,
['Image ID', 'Member ID', 'Status'])
else:
param_image_id = '--image-id fake_image_id'
self.assertRaises(exceptions.CommandFailed,
self.glance,
'--os-image-api-version 2 member-list',
params=param_image_id)
def test_help(self): def test_help(self):
help_text = self.glance('help') help_text = self.glance('help')
lines = help_text.split('\n') lines = help_text.split('\n')

View File

@@ -19,6 +19,7 @@ try:
from collections import OrderedDict from collections import OrderedDict
except ImportError: except ImportError:
from ordereddict import OrderedDict from ordereddict import OrderedDict
import hashlib
import os import os
import sys import sys
import uuid import uuid
@@ -197,8 +198,8 @@ class ShellTest(testutils.TestCase):
def test_no_auth_with_token_and_image_url_with_v1(self, v1_client): def test_no_auth_with_token_and_image_url_with_v1(self, v1_client):
# test no authentication is required if both token and endpoint url # test no authentication is required if both token and endpoint url
# are specified # are specified
args = ('--os-auth-token mytoken --os-image-url https://image:1234/v1 ' args = ('--os-image-api-version 1 --os-auth-token mytoken'
'image-list') ' --os-image-url https://image:1234/v1 image-list')
glance_shell = openstack_shell.OpenStackImagesShell() glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split()) glance_shell.main(args.split())
assert v1_client.called assert v1_client.called
@@ -206,7 +207,8 @@ class ShellTest(testutils.TestCase):
self.assertEqual('mytoken', kwargs['token']) self.assertEqual('mytoken', kwargs['token'])
self.assertEqual('https://image:1234', args[0]) self.assertEqual('https://image:1234', args[0])
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas') @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas',
return_value=False)
def test_no_auth_with_token_and_image_url_with_v2(self, def test_no_auth_with_token_and_image_url_with_v2(self,
cache_schemas): cache_schemas):
with mock.patch('glanceclient.v2.client.Client') as v2_client: with mock.patch('glanceclient.v2.client.Client') as v2_client:
@@ -237,13 +239,14 @@ class ShellTest(testutils.TestCase):
@mock.patch('glanceclient.v1.client.Client') @mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_with_v1(self, v1_client): def test_auth_plugin_invocation_with_v1(self, v1_client):
args = 'image-list' args = '--os-image-api-version 1 image-list'
glance_shell = openstack_shell.OpenStackImagesShell() glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split()) glance_shell.main(args.split())
self._assert_auth_plugin_args() self._assert_auth_plugin_args()
@mock.patch('glanceclient.v2.client.Client') @mock.patch('glanceclient.v2.client.Client')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas') @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas',
return_value=False)
def test_auth_plugin_invocation_with_v2(self, def test_auth_plugin_invocation_with_v2(self,
v2_client, v2_client,
cache_schemas): cache_schemas):
@@ -255,13 +258,15 @@ class ShellTest(testutils.TestCase):
@mock.patch('glanceclient.v1.client.Client') @mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_with_unversioned_auth_url_with_v1( def test_auth_plugin_invocation_with_unversioned_auth_url_with_v1(
self, v1_client): self, v1_client):
args = '--os-auth-url %s image-list' % DEFAULT_UNVERSIONED_AUTH_URL args = ('--os-image-api-version 1 --os-auth-url %s image-list' %
DEFAULT_UNVERSIONED_AUTH_URL)
glance_shell = openstack_shell.OpenStackImagesShell() glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split()) glance_shell.main(args.split())
self._assert_auth_plugin_args() self._assert_auth_plugin_args()
@mock.patch('glanceclient.v2.client.Client') @mock.patch('glanceclient.v2.client.Client')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas') @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas',
return_value=False)
def test_auth_plugin_invocation_with_unversioned_auth_url_with_v2( def test_auth_plugin_invocation_with_unversioned_auth_url_with_v2(
self, v2_client, cache_schemas): self, v2_client, cache_schemas):
args = ('--os-auth-url %s --os-image-api-version 2 ' args = ('--os-auth-url %s --os-image-api-version 2 '
@@ -293,7 +298,8 @@ class ShellTest(testutils.TestCase):
@mock.patch( @mock.patch(
'glanceclient.shell.OpenStackImagesShell._get_keystone_session') 'glanceclient.shell.OpenStackImagesShell._get_keystone_session')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas') @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas',
return_value=False)
def test_no_auth_with_proj_name(self, cache_schemas, session): def test_no_auth_with_proj_name(self, cache_schemas, session):
with mock.patch('glanceclient.v2.client.Client'): with mock.patch('glanceclient.v2.client.Client'):
args = ('--os-project-name myname ' args = ('--os-project-name myname '
@@ -407,7 +413,10 @@ class ShellTest(testutils.TestCase):
self.assertRaises(exc.CommandError, glance_shell.main, args.split()) self.assertRaises(exc.CommandError, glance_shell.main, args.split())
@mock.patch('glanceclient.v2.client.Client') @mock.patch('glanceclient.v2.client.Client')
def test_auth_plugin_invocation_without_tenant_with_v2(self, v2_client): @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas',
return_value=False)
def test_auth_plugin_invocation_without_tenant_with_v2(self, v2_client,
cache_schemas):
if 'OS_TENANT_NAME' in os.environ: if 'OS_TENANT_NAME' in os.environ:
self.make_env(exclude='OS_TENANT_NAME') self.make_env(exclude='OS_TENANT_NAME')
if 'OS_PROJECT_ID' in os.environ: if 'OS_PROJECT_ID' in os.environ:
@@ -438,13 +447,14 @@ class ShellTestWithKeystoneV3Auth(ShellTest):
@mock.patch('glanceclient.v1.client.Client') @mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_with_v1(self, v1_client): def test_auth_plugin_invocation_with_v1(self, v1_client):
args = 'image-list' args = '--os-image-api-version 1 image-list'
glance_shell = openstack_shell.OpenStackImagesShell() glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split()) glance_shell.main(args.split())
self._assert_auth_plugin_args() self._assert_auth_plugin_args()
@mock.patch('glanceclient.v2.client.Client') @mock.patch('glanceclient.v2.client.Client')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas') @mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas',
return_value=False)
def test_auth_plugin_invocation_with_v2(self, v2_client, cache_schemas): def test_auth_plugin_invocation_with_v2(self, v2_client, cache_schemas):
args = '--os-image-api-version 2 image-list' args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell() glance_shell = openstack_shell.OpenStackImagesShell()
@@ -482,9 +492,12 @@ class ShellCacheSchemaTest(testutils.TestCase):
self._mock_client_setup() self._mock_client_setup()
self._mock_shell_setup() self._mock_shell_setup()
self.cache_dir = '/dir_for_cached_schema' self.cache_dir = '/dir_for_cached_schema'
self.cache_files = [self.cache_dir + '/image_schema.json', self.os_auth_url = 'http://localhost:5000/v2'
self.cache_dir + '/namespace_schema.json', url_hex = hashlib.sha1(self.os_auth_url.encode('utf-8')).hexdigest()
self.cache_dir + '/resource_type_schema.json'] self.prefix_path = (self.cache_dir + '/' + url_hex)
self.cache_files = [self.prefix_path + '/image_schema.json',
self.prefix_path + '/namespace_schema.json',
self.prefix_path + '/resource_type_schema.json']
def tearDown(self): def tearDown(self):
super(ShellCacheSchemaTest, self).tearDown() super(ShellCacheSchemaTest, self).tearDown()
@@ -517,7 +530,8 @@ class ShellCacheSchemaTest(testutils.TestCase):
@mock.patch('os.path.exists', return_value=True) @mock.patch('os.path.exists', return_value=True)
def test_cache_schemas_gets_when_forced(self, exists_mock): def test_cache_schemas_gets_when_forced(self, exists_mock):
options = { options = {
'get_schema': True 'get_schema': True,
'os_auth_url': self.os_auth_url
} }
schema_odict = OrderedDict(self.schema_dict) schema_odict = OrderedDict(self.schema_dict)
@@ -538,7 +552,8 @@ class ShellCacheSchemaTest(testutils.TestCase):
@mock.patch('os.path.exists', side_effect=[True, False, False, False]) @mock.patch('os.path.exists', side_effect=[True, False, False, False])
def test_cache_schemas_gets_when_not_exists(self, exists_mock): def test_cache_schemas_gets_when_not_exists(self, exists_mock):
options = { options = {
'get_schema': False 'get_schema': False,
'os_auth_url': self.os_auth_url
} }
schema_odict = OrderedDict(self.schema_dict) schema_odict = OrderedDict(self.schema_dict)
@@ -559,14 +574,29 @@ class ShellCacheSchemaTest(testutils.TestCase):
@mock.patch('os.path.exists', return_value=True) @mock.patch('os.path.exists', return_value=True)
def test_cache_schemas_leaves_when_present_not_forced(self, exists_mock): def test_cache_schemas_leaves_when_present_not_forced(self, exists_mock):
options = { options = {
'get_schema': False 'get_schema': False,
'os_auth_url': self.os_auth_url
} }
self.shell._cache_schemas(self._make_args(options), self.shell._cache_schemas(self._make_args(options),
home_dir=self.cache_dir) home_dir=self.cache_dir)
os.path.exists.assert_any_call(self.cache_dir) os.path.exists.assert_any_call(self.prefix_path)
os.path.exists.assert_any_call(self.cache_files[0]) os.path.exists.assert_any_call(self.cache_files[0])
os.path.exists.assert_any_call(self.cache_files[1]) os.path.exists.assert_any_call(self.cache_files[1])
self.assertEqual(4, exists_mock.call_count) self.assertEqual(4, exists_mock.call_count)
self.assertEqual(0, open.mock_calls.__len__()) self.assertEqual(0, open.mock_calls.__len__())
@mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True)
@mock.patch('os.path.exists', return_value=True)
def test_cache_schemas_leaves_auto_switch(self, exists_mock):
options = {
'get_schema': True,
'os_auth_url': self.os_auth_url
}
self.client.schemas.get.return_value = Exception()
switch_version = self.shell._cache_schemas(self._make_args(options),
home_dir=self.cache_dir)
self.assertEqual(switch_version, True)