Add capabilities option

This patch adds a capabilities option on swiftclient.
This option uses the new /info endpoint to request the
remote capabilities and nicely display it.

Change-Id: Ie34b454511d5527e402e66e1fdb72120f427f2fd
This commit is contained in:
Fabien Boucher 2014-01-13 22:39:28 +01:00
parent 20cd3402b2
commit 533c9c5ba1
4 changed files with 110 additions and 3 deletions

View File

@ -1125,6 +1125,41 @@ def st_upload(parser, args, thread_manager):
thread_manager.error('Account not found')
st_capabilities_options = "[<proxy_url>]"
st_capabilities_help = '''
Retrieve capability of the proxy
Optional positional arguments:
<proxy_url> proxy URL of the cluster to retreive capabilities
'''
def st_capabilities(parser, args, thread_manager):
def _print_compo_cap(name, capabilities):
for feature, options in sorted(capabilities.items(),
key=lambda x: x[0]):
thread_manager.print_msg("%s: %s" % (name, feature))
if options:
thread_manager.print_msg(" Options:")
for key, value in sorted(options.items(),
key=lambda x: x[0]):
thread_manager.print_msg(" %s: %s" % (key, value))
(options, args) = parse_args(parser, args)
if (args and len(args) > 2):
thread_manager.error('Usage: %s capabilities %s\n%s',
basename(argv[0]),
st_capabilities_options, st_capabilities_help)
return
conn = get_conn(options)
url = None
if len(args) == 2:
url = args[1]
capabilities = conn.get_capabilities(url)
_print_compo_cap('Core', {'swift': capabilities['swift']})
del capabilities['swift']
_print_compo_cap('Additional middleware', capabilities)
def split_headers(options, prefix='', thread_manager=None):
"""
Splits 'Key: Value' strings and returns them as a dictionary.
@ -1177,6 +1212,9 @@ def parse_args(parser, args, enforce_requires=True):
'region_name': options.os_region_name,
}
if len(args) > 1 and args[0] == "capabilities":
return options, args
if (options.os_options.get('object_storage_url') and
options.os_options.get('auth_token') and
options.auth_version == '2.0'):
@ -1227,6 +1265,8 @@ Positional arguments:
stat Displays information for the account, container,
or object
upload Uploads files or directories to the given container
capabilities List cluster capabilities
Examples:
%%prog -A https://auth.api.rackspacecloud.com/v1.0 -U user -K api_key stat -v
@ -1362,7 +1402,8 @@ Examples:
(options, args) = parse_args(parser, argv[1:], enforce_requires=False)
parser.enable_interspersed_args()
commands = ('delete', 'download', 'list', 'post', 'stat', 'upload')
commands = ('delete', 'download', 'list', 'post',
'stat', 'upload', 'capabilities')
if not args or args[0] not in commands:
parser.print_usage()
if args:

View File

@ -89,14 +89,20 @@ You can specify optional headers with the repeatable cURL-like option
.RE
\fBdelete\fR [\fIcommand-options\fR] [\fIcontainer\fR] [\fIobject\fR] [\fIobject\fR] [...]
.RS 4
Deletes everything in the account (with --all), or everything in a container,
or a list of objects depending on the args given. Segments of manifest objects
will be deleted as well, unless you specify the --leave-segments option.
.RE
\fBcapabilities\fR [\fIproxy-url\fR]
.RS 4
Displays cluster capabilities. The output includes the list of the activated
Swift middlewares as well as relevant options for each ones. Addtionaly the
command displays relevant options for the Swift core. If the proxy-url option
is not provided the storage-url retreived after authentication is used as
proxy-url.
.RE
.SH OPTIONS
.PD 0

View File

@ -1028,6 +1028,29 @@ def delete_object(url, token=None, container=None, name=None, http_conn=None,
http_response_content=body)
def get_capabilities(http_conn):
"""
Get cluster capability infos.
:param http_conn: HTTP connection
:returns: a dict containing the cluster capabilities
:raises ClientException: HTTP Capabilities GET failed
"""
parsed, conn = http_conn
conn.request('GET', parsed.path, '')
resp = conn.getresponse()
body = resp.read()
http_log((parsed.geturl(), 'GET',), {'headers': {}}, resp, body)
if resp.status < 200 or resp.status >= 300:
raise ClientException('Capabilities GET failed',
http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=parsed.path, http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
return json_loads(body)
class Connection(object):
"""Convenience class to make requests that will also retry the request"""
@ -1263,3 +1286,12 @@ class Connection(object):
return self._retry(None, delete_object, container, obj,
query_string=query_string,
response_dict=response_dict)
def get_capabilities(self, url=None):
if not url:
url, _ = self.get_auth()
scheme = urlparse(url).scheme
netloc = urlparse(url).netloc
url = scheme + '://' + netloc + '/info'
http_conn = http_connection(url, ssl_compression=self.ssl_compression)
return get_capabilities(http_conn)

View File

@ -656,6 +656,20 @@ class TestDeleteObject(MockHttpTest):
query_string="hello=20")
class TestGetCapabilities(MockHttpTest):
def test_ok(self):
conn = self.fake_http_connection(200, body='{}')
http_conn = conn('http://www.test.com/info')
self.assertEqual(type(c.get_capabilities(http_conn)), dict)
self.assertTrue(http_conn[1].has_been_read)
def test_server_error(self):
conn = self.fake_http_connection(500)
http_conn = conn('http://www.test.com/info')
self.assertRaises(c.ClientException, c.get_capabilities, http_conn)
class TestConnection(MockHttpTest):
def test_instance(self):
@ -703,6 +717,20 @@ class TestConnection(MockHttpTest):
for method, args in method_signatures:
method(*args)
def test_get_capabilities(self):
conn = c.Connection()
with mock.patch('swiftclient.client.get_capabilities') as get_cap:
conn.get_capabilities('http://storage2.test.com')
parsed = get_cap.call_args[0][0][0]
self.assertEqual(parsed.path, '/info')
self.assertEqual(parsed.netloc, 'storage2.test.com')
conn.get_auth = lambda: ('http://storage.test.com/v1/AUTH_test',
'token')
conn.get_capabilities()
parsed = get_cap.call_args[0][0][0]
self.assertEqual(parsed.path, '/info')
self.assertEqual(parsed.netloc, 'storage.test.com')
def test_retry(self):
c.http_connection = self.fake_http_connection(500)