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:
parent
20cd3402b2
commit
533c9c5ba1
43
bin/swift
43
bin/swift
@ -1125,6 +1125,41 @@ def st_upload(parser, args, thread_manager):
|
|||||||
thread_manager.error('Account not found')
|
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):
|
def split_headers(options, prefix='', thread_manager=None):
|
||||||
"""
|
"""
|
||||||
Splits 'Key: Value' strings and returns them as a dictionary.
|
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,
|
'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
|
if (options.os_options.get('object_storage_url') and
|
||||||
options.os_options.get('auth_token') and
|
options.os_options.get('auth_token') and
|
||||||
options.auth_version == '2.0'):
|
options.auth_version == '2.0'):
|
||||||
@ -1227,6 +1265,8 @@ Positional arguments:
|
|||||||
stat Displays information for the account, container,
|
stat Displays information for the account, container,
|
||||||
or object
|
or object
|
||||||
upload Uploads files or directories to the given container
|
upload Uploads files or directories to the given container
|
||||||
|
capabilities List cluster capabilities
|
||||||
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
%%prog -A https://auth.api.rackspacecloud.com/v1.0 -U user -K api_key stat -v
|
%%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)
|
(options, args) = parse_args(parser, argv[1:], enforce_requires=False)
|
||||||
parser.enable_interspersed_args()
|
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:
|
if not args or args[0] not in commands:
|
||||||
parser.print_usage()
|
parser.print_usage()
|
||||||
if args:
|
if args:
|
||||||
|
@ -89,14 +89,20 @@ You can specify optional headers with the repeatable cURL-like option
|
|||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fBdelete\fR [\fIcommand-options\fR] [\fIcontainer\fR] [\fIobject\fR] [\fIobject\fR] [...]
|
\fBdelete\fR [\fIcommand-options\fR] [\fIcontainer\fR] [\fIobject\fR] [\fIobject\fR] [...]
|
||||||
|
|
||||||
.RS 4
|
.RS 4
|
||||||
Deletes everything in the account (with --all), or everything in a container,
|
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
|
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.
|
will be deleted as well, unless you specify the --leave-segments option.
|
||||||
|
|
||||||
.RE
|
.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
|
.SH OPTIONS
|
||||||
.PD 0
|
.PD 0
|
||||||
|
@ -1028,6 +1028,29 @@ def delete_object(url, token=None, container=None, name=None, http_conn=None,
|
|||||||
http_response_content=body)
|
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):
|
class Connection(object):
|
||||||
"""Convenience class to make requests that will also retry the request"""
|
"""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,
|
return self._retry(None, delete_object, container, obj,
|
||||||
query_string=query_string,
|
query_string=query_string,
|
||||||
response_dict=response_dict)
|
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)
|
||||||
|
@ -656,6 +656,20 @@ class TestDeleteObject(MockHttpTest):
|
|||||||
query_string="hello=20")
|
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):
|
class TestConnection(MockHttpTest):
|
||||||
|
|
||||||
def test_instance(self):
|
def test_instance(self):
|
||||||
@ -703,6 +717,20 @@ class TestConnection(MockHttpTest):
|
|||||||
for method, args in method_signatures:
|
for method, args in method_signatures:
|
||||||
method(*args)
|
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):
|
def test_retry(self):
|
||||||
c.http_connection = self.fake_http_connection(500)
|
c.http_connection = self.fake_http_connection(500)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user