diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index fdeca139ec..690cabba7e 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,6 +20,7 @@ import logging from openstackclient.compute import client as compute_client from openstackclient.identity import client as identity_client from openstackclient.image import client as image_client +from openstackclient.object import client as object_client from openstackclient.volume import client as volume_client @@ -44,6 +45,7 @@ class ClientManager(object): compute = ClientCache(compute_client.make_client) identity = ClientCache(identity_client.make_client) image = ClientCache(image_client.make_client) + object = ClientCache(object_client.make_client) volume = ClientCache(volume_client.make_client) def __init__(self, token=None, url=None, auth_url=None, project_name=None, diff --git a/openstackclient/object/__init__.py b/openstackclient/object/__init__.py new file mode 100644 index 0000000000..02be10cd89 --- /dev/null +++ b/openstackclient/object/__init__.py @@ -0,0 +1,12 @@ +# 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. +# diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py new file mode 100644 index 0000000000..a83a5c0a3d --- /dev/null +++ b/openstackclient/object/client.py @@ -0,0 +1,58 @@ +# Copyright 2013 Nebula Inc. +# +# 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. +# + +"""Object client""" + +import logging + +from openstackclient.common import utils + +LOG = logging.getLogger(__name__) + +API_NAME = 'object-store' +API_VERSIONS = { + '1': 'openstackclient.object.client.ObjectClientv1', +} + + +def make_client(instance): + """Returns an object service client.""" + object_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + if instance._url: + endpoint = instance._url + else: + endpoint = instance.get_endpoint_for_service_type(API_NAME) + LOG.debug('instantiating object client') + client = object_client( + endpoint=endpoint, + token=instance._token, + ) + return client + + +class ObjectClientv1(object): + + def __init__( + self, + endpoint_type='publicURL', + endpoint=None, + token=None, + ): + self.endpoint_type = endpoint_type + self.endpoint = endpoint + self.token = token diff --git a/openstackclient/object/v1/__init__.py b/openstackclient/object/v1/__init__.py new file mode 100644 index 0000000000..02be10cd89 --- /dev/null +++ b/openstackclient/object/v1/__init__.py @@ -0,0 +1,12 @@ +# 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. +# diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py new file mode 100644 index 0000000000..8c4db66ae4 --- /dev/null +++ b/openstackclient/object/v1/container.py @@ -0,0 +1,100 @@ +# Copyright 2013 Nebula Inc. +# +# 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. +# + +"""Container v1 action implementations""" + + +import logging + +from cliff import lister + +from openstackclient.common import utils +from openstackclient.object.v1.lib import container as lib_container + + +class ListContainer(lister.Lister): + """List containers""" + + log = logging.getLogger(__name__ + '.ListContainer') + + def get_parser(self, prog_name): + parser = super(ListContainer, self).get_parser(prog_name) + parser.add_argument( + "--prefix", + metavar="<prefix>", + help="Filter list using <prefix>", + ) + parser.add_argument( + "--marker", + metavar="<marker>", + help="Anchor for paging", + ) + parser.add_argument( + "--end-marker", + metavar="<end-marker>", + help="End anchor for paging", + ) + parser.add_argument( + "--limit", + metavar="<limit>", + type=int, + help="Limit the number of containers returned", + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + parser.add_argument( + '--all', + action='store_true', + default=False, + help='List all containers (default is 10000)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + if parsed_args.long: + columns = ('Name', 'Bytes', 'Count') + else: + columns = ('Name',) + + kwargs = {} + if parsed_args.prefix: + kwargs['prefix'] = parsed_args.prefix + if parsed_args.marker: + kwargs['marker'] = parsed_args.marker + if parsed_args.end_marker: + kwargs['end_marker'] = parsed_args.end_marker + if parsed_args.limit: + kwargs['limit'] = parsed_args.limit + if parsed_args.all: + kwargs['full_listing'] = True + + data = lib_container.list_containers( + self.app.restapi, + self.app.client_manager.object.endpoint, + **kwargs + ) + #print "data: %s" % data + + return (columns, + (utils.get_dict_properties( + s, columns, + formatters={}, + ) for s in data)) diff --git a/openstackclient/object/v1/lib/__init__.py b/openstackclient/object/v1/lib/__init__.py new file mode 100644 index 0000000000..02be10cd89 --- /dev/null +++ b/openstackclient/object/v1/lib/__init__.py @@ -0,0 +1,12 @@ +# 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. +# diff --git a/openstackclient/object/v1/lib/container.py b/openstackclient/object/v1/lib/container.py new file mode 100644 index 0000000000..f30533c804 --- /dev/null +++ b/openstackclient/object/v1/lib/container.py @@ -0,0 +1,77 @@ +# Copyright 2010-2012 OpenStack Foundation +# Copyright 2013 Nebula Inc. +# +# 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. +# + +"""Object v1 API library""" + + +def list_containers( + api, + url, + marker=None, + limit=None, + end_marker=None, + prefix=None, + full_listing=False, +): + """Get containers in an account + + :param api: a restapi object + :param url: endpoint + :param marker: marker query + :param limit: limit query + :param end_marker: end_marker query + :param prefix: prefix query + :param full_listing: if True, return a full listing, else returns a max + of 10000 listings + :returns: list of containers + """ + + if full_listing: + data = listing = list_containers( + api, + url, + marker, + limit, + end_marker, + prefix, + ) + while listing: + marker = listing[-1]['name'] + listing = list_containers( + api, + url, + marker, + limit, + end_marker, + prefix, + ) + if listing: + data.extend(listing) + return data + + object_url = url + query = "format=json" + if marker: + query += '&marker=%s' % marker + if limit: + query += '&limit=%d' % limit + if end_marker: + query += '&end_marker=%s' % end_marker + if prefix: + query += '&prefix=%s' % prefix + url = "%s?%s" % (object_url, query) + response = api.request('GET', url) + return response.json() diff --git a/openstackclient/object/v1/lib/object.py b/openstackclient/object/v1/lib/object.py new file mode 100644 index 0000000000..d7c4a1ce37 --- /dev/null +++ b/openstackclient/object/v1/lib/object.py @@ -0,0 +1,97 @@ +# Copyright 2010-2012 OpenStack Foundation +# Copyright 2013 Nebula Inc. +# +# 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. +# + +"""Object v1 API library""" + + +def list_objects( + api, + url, + container, + marker=None, + limit=None, + end_marker=None, + delimiter=None, + prefix=None, + path=None, + full_listing=False, +): + """Get objects in a container + + :param api: a restapi object + :param url: endpoint + :param container: container name to get a listing for + :param marker: marker query + :param limit: limit query + :param end_marker: marker query + :param delimiter: string to delimit the queries on + :param prefix: prefix query + :param path: path query (equivalent: "delimiter=/" and "prefix=path/") + :param full_listing: if True, return a full listing, else returns a max + of 10000 listings + :returns: a tuple of (response headers, a list of objects) The response + headers will be a dict and all header names will be lowercase. + """ + + if full_listing: + data = listing = list_objects( + api, + url, + container, + marker, + limit, + end_marker, + delimiter, + prefix, + path, + ) + while listing: + if delimiter: + marker = listing[-1].get('name', listing[-1].get('subdir')) + else: + marker = listing[-1]['name'] + listing = list_objects( + api, + url, + container, + marker, + limit, + end_marker, + delimiter, + prefix, + path, + ) + if listing: + data.extend(listing) + return data + + object_url = url + query = "format=json" + if marker: + query += '&marker=%s' % marker + if limit: + query += '&limit=%d' % limit + if end_marker: + query += '&end_marker=%s' % end_marker + if delimiter: + query += '&delimiter=%s' % delimiter + if prefix: + query += '&prefix=%s' % prefix + if path: + query += '&path=%s' % path + url = "%s/%s?%s" % (object_url, container, query) + response = api.request('GET', url) + return response.json() diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py new file mode 100644 index 0000000000..c6bd755b32 --- /dev/null +++ b/openstackclient/object/v1/object.py @@ -0,0 +1,118 @@ +# Copyright 2013 Nebula Inc. +# +# 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. +# + +"""Object v1 action implementations""" + + +import logging + +from cliff import lister + +from openstackclient.common import utils +from openstackclient.object.v1.lib import object as lib_object + + +class ListObject(lister.Lister): + """List objects""" + + log = logging.getLogger(__name__ + '.ListObject') + + def get_parser(self, prog_name): + parser = super(ListObject, self).get_parser(prog_name) + parser.add_argument( + "container", + metavar="<container-name>", + help="List contents of container-name", + ) + parser.add_argument( + "--prefix", + metavar="<prefix>", + help="Filter list using <prefix>", + ) + parser.add_argument( + "--delimiter", + metavar="<delimiter>", + help="Roll up items with <delimiter>", + ) + parser.add_argument( + "--marker", + metavar="<marker>", + help="Anchor for paging", + ) + parser.add_argument( + "--end-marker", + metavar="<end-marker>", + help="End anchor for paging", + ) + parser.add_argument( + "--limit", + metavar="<limit>", + type=int, + help="Limit the number of objects returned", + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + parser.add_argument( + '--all', + action='store_true', + default=False, + help='List all objects in container (default is 10000)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + if parsed_args.long: + columns = ( + 'Name', + 'Bytes', + 'Hash', + 'Content Type', + 'Last Modified', + ) + else: + columns = ('Name',) + + kwargs = {} + if parsed_args.prefix: + kwargs['prefix'] = parsed_args.prefix + if parsed_args.delimiter: + kwargs['delimiter'] = parsed_args.delimiter + if parsed_args.marker: + kwargs['marker'] = parsed_args.marker + if parsed_args.end_marker: + kwargs['end_marker'] = parsed_args.end_marker + if parsed_args.limit: + kwargs['limit'] = parsed_args.limit + if parsed_args.all: + kwargs['full_listing'] = True + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + parsed_args.container, + **kwargs + ) + + return (columns, + (utils.get_dict_properties( + s, columns, + formatters={}, + ) for s in data)) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 91b02a2b0a..6cb7c1ee2c 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -38,6 +38,7 @@ KEYRING_SERVICE = 'openstack' DEFAULT_COMPUTE_API_VERSION = '2' DEFAULT_IDENTITY_API_VERSION = '2.0' DEFAULT_IMAGE_API_VERSION = '1' +DEFAULT_OBJECT_API_VERSION = '1' DEFAULT_VOLUME_API_VERSION = '1' DEFAULT_DOMAIN = 'default' @@ -187,6 +188,15 @@ class OpenStackShell(app.App): help='Image API version, default=' + DEFAULT_IMAGE_API_VERSION + ' (Env: OS_IMAGE_API_VERSION)') + parser.add_argument( + '--os-object-api-version', + metavar='<object-api-version>', + default=env( + 'OS_OBJECT_API_VERSION', + default=DEFAULT_OBJECT_API_VERSION), + help='Object API version, default=' + + DEFAULT_OBJECT_API_VERSION + + ' (Env: OS_OBJECT_API_VERSION)') parser.add_argument( '--os-volume-api-version', metavar='<volume-api-version>', @@ -339,14 +349,15 @@ class OpenStackShell(app.App): 'compute': self.options.os_compute_api_version, 'identity': self.options.os_identity_api_version, 'image': self.options.os_image_api_version, + 'object-store': self.options.os_object_api_version, 'volume': self.options.os_volume_api_version, } # Add the API version-specific commands for api in self.api_version.keys(): version = '.v' + self.api_version[api].replace('.', '_') - self.command_manager.add_command_group( - 'openstack.' + api + version) + cmd_group = 'openstack.' + api.replace('-', '_') + version + self.command_manager.add_command_group(cmd_group) # Commands that span multiple APIs self.command_manager.add_command_group( diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index d0cd4a9f81..e0122022a8 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -44,6 +44,11 @@ class FakeClientManager(object): pass +class FakeRESTApi(object): + def __init__(self): + pass + + class FakeResource(object): def __init__(self, manager, info, loaded=False): self.manager = manager diff --git a/openstackclient/tests/object/__init__.py b/openstackclient/tests/object/__init__.py new file mode 100644 index 0000000000..02be10cd89 --- /dev/null +++ b/openstackclient/tests/object/__init__.py @@ -0,0 +1,12 @@ +# 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. +# diff --git a/openstackclient/tests/object/fakes.py b/openstackclient/tests/object/fakes.py new file mode 100644 index 0000000000..fbc784aae5 --- /dev/null +++ b/openstackclient/tests/object/fakes.py @@ -0,0 +1,67 @@ +# Copyright 2013 Nebula Inc. +# +# 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. +# + +container_name = 'bit-bucket' +container_bytes = 1024 +container_count = 1 + +container_name_2 = 'archive' +container_name_3 = 'bit-blit' + +CONTAINER = { + 'name': container_name, + 'bytes': container_bytes, + 'count': container_count, +} + +CONTAINER_2 = { + 'name': container_name_2, + 'bytes': container_bytes * 2, + 'count': container_count * 2, +} + +CONTAINER_3 = { + 'name': container_name_3, + 'bytes': container_bytes * 3, + 'count': container_count * 3, +} + +object_name_1 = 'punch-card' +object_bytes_1 = 80 +object_hash_1 = '1234567890' +object_content_type_1 = 'text' +object_modified_1 = 'today' + +object_name_2 = 'floppy-disk' +object_bytes_2 = 1440000 +object_hash_2 = '0987654321' +object_content_type_2 = 'text' +object_modified_2 = 'today' + +OBJECT = { + 'name': object_name_1, + 'bytes': object_bytes_1, + 'hash': object_hash_1, + 'content_type': object_content_type_1, + 'last_modified': object_modified_1, +} + +OBJECT_2 = { + 'name': object_name_2, + 'bytes': object_bytes_2, + 'hash': object_hash_2, + 'content_type': object_content_type_2, + 'last_modified': object_modified_2, +} diff --git a/openstackclient/tests/object/test_container.py b/openstackclient/tests/object/test_container.py new file mode 100644 index 0000000000..9b53e36037 --- /dev/null +++ b/openstackclient/tests/object/test_container.py @@ -0,0 +1,315 @@ +# Copyright 2013 OpenStack Foundation +# +# 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 copy +import mock + +from openstackclient.common import clientmanager +from openstackclient.object.v1 import container +from openstackclient.tests.object import fakes as object_fakes +from openstackclient.tests import utils + + +AUTH_TOKEN = "foobar" +AUTH_URL = "http://0.0.0.0" + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.endpoint = AUTH_URL + self.token = AUTH_TOKEN + + +class TestObject(utils.TestCommand): + def setUp(self): + super(TestObject, self).setUp() + + api_version = {"object-store": "1"} + self.app.client_manager = clientmanager.ClientManager( + token=AUTH_TOKEN, + url=AUTH_URL, + auth_url=AUTH_URL, + api_version=api_version, + ) + + +class TestObjectClient(TestObject): + + def test_make_client(self): + self.assertEqual(self.app.client_manager.object.endpoint, AUTH_URL) + self.assertEqual(self.app.client_manager.object.token, AUTH_TOKEN) + + +@mock.patch( + 'openstackclient.object.v1.container.lib_container.list_containers' +) +class TestContainerList(TestObject): + + def setUp(self): + super(TestContainerList, self).setUp() + + # Get the command object to test + self.cmd = container.ListContainer(self.app, None) + + def test_object_list_containers_no_options(self, c_mock): + c_mock.return_value = [ + copy.deepcopy(object_fakes.CONTAINER), + copy.deepcopy(object_fakes.CONTAINER_3), + copy.deepcopy(object_fakes.CONTAINER_2), + ] + + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + } + c_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.container_name, ), + (object_fakes.container_name_3, ), + (object_fakes.container_name_2, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_containers_prefix(self, c_mock): + c_mock.return_value = [ + copy.deepcopy(object_fakes.CONTAINER), + copy.deepcopy(object_fakes.CONTAINER_3), + ] + + arglist = [ + '--prefix', 'bit', + ] + verifylist = [ + ('prefix', 'bit'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'prefix': 'bit', + } + c_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.container_name, ), + (object_fakes.container_name_3, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_containers_marker(self, c_mock): + c_mock.return_value = [ + copy.deepcopy(object_fakes.CONTAINER), + copy.deepcopy(object_fakes.CONTAINER_3), + ] + + arglist = [ + '--marker', object_fakes.container_name, + ] + verifylist = [ + ('marker', object_fakes.container_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'marker': object_fakes.container_name, + } + c_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.container_name, ), + (object_fakes.container_name_3, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_containers_end_marker(self, c_mock): + c_mock.return_value = [ + copy.deepcopy(object_fakes.CONTAINER), + copy.deepcopy(object_fakes.CONTAINER_3), + ] + + arglist = [ + '--end-marker', object_fakes.container_name_3, + ] + verifylist = [ + ('end_marker', object_fakes.container_name_3), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'end_marker': object_fakes.container_name_3, + } + c_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.container_name, ), + (object_fakes.container_name_3, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_containers_limit(self, c_mock): + c_mock.return_value = [ + copy.deepcopy(object_fakes.CONTAINER), + copy.deepcopy(object_fakes.CONTAINER_3), + ] + + arglist = [ + '--limit', '2', + ] + verifylist = [ + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'limit': 2, + } + c_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.container_name, ), + (object_fakes.container_name_3, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_containers_long(self, c_mock): + c_mock.return_value = [ + copy.deepcopy(object_fakes.CONTAINER), + copy.deepcopy(object_fakes.CONTAINER_3), + ] + + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + } + c_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + **kwargs + ) + + collist = ('Name', 'Bytes', 'Count') + self.assertEqual(columns, collist) + datalist = ( + ( + object_fakes.container_name, + object_fakes.container_bytes, + object_fakes.container_count, + ), + ( + object_fakes.container_name_3, + object_fakes.container_bytes * 3, + object_fakes.container_count * 3, + ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_containers_all(self, c_mock): + c_mock.return_value = [ + copy.deepcopy(object_fakes.CONTAINER), + copy.deepcopy(object_fakes.CONTAINER_2), + copy.deepcopy(object_fakes.CONTAINER_3), + ] + + arglist = [ + '--all', + ] + verifylist = [ + ('all', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'full_listing': True, + } + c_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.container_name, ), + (object_fakes.container_name_2, ), + (object_fakes.container_name_3, ), + ) + self.assertEqual(tuple(data), datalist) diff --git a/openstackclient/tests/object/test_object.py b/openstackclient/tests/object/test_object.py new file mode 100644 index 0000000000..ddd5b592a9 --- /dev/null +++ b/openstackclient/tests/object/test_object.py @@ -0,0 +1,362 @@ +# Copyright 2013 OpenStack Foundation +# +# 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 copy +import mock + +from openstackclient.common import clientmanager +from openstackclient.object.v1 import object as obj +from openstackclient.tests.object import fakes as object_fakes +from openstackclient.tests import utils + + +AUTH_TOKEN = "foobar" +AUTH_URL = "http://0.0.0.0" + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.endpoint = AUTH_URL + self.token = AUTH_TOKEN + + +class TestObject(utils.TestCommand): + def setUp(self): + super(TestObject, self).setUp() + + api_version = {"object-store": "1"} + self.app.client_manager = clientmanager.ClientManager( + token=AUTH_TOKEN, + url=AUTH_URL, + auth_url=AUTH_URL, + api_version=api_version, + ) + + +class TestObjectClient(TestObject): + + def test_make_client(self): + self.assertEqual(self.app.client_manager.object.endpoint, AUTH_URL) + self.assertEqual(self.app.client_manager.object.token, AUTH_TOKEN) + + +@mock.patch( + 'openstackclient.object.v1.object.lib_object.list_objects' +) +class TestObjectList(TestObject): + + def setUp(self): + super(TestObjectList, self).setUp() + + # Get the command object to test + self.cmd = obj.ListObject(self.app, None) + + def test_object_list_objects_no_options(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT), + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + object_fakes.container_name, + ] + verifylist = [ + ('container', object_fakes.container_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name, + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.object_name_1, ), + (object_fakes.object_name_2, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_objects_prefix(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + '--prefix', 'floppy', + object_fakes.container_name_2, + ] + verifylist = [ + ('prefix', 'floppy'), + ('container', object_fakes.container_name_2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'prefix': 'floppy', + } + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name_2, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.object_name_2, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_objects_delimiter(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + '--delimiter', '=', + object_fakes.container_name_2, + ] + verifylist = [ + ('delimiter', '='), + ('container', object_fakes.container_name_2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'delimiter': '=', + } + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name_2, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.object_name_2, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_objects_marker(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + '--marker', object_fakes.object_name_2, + object_fakes.container_name_2, + ] + verifylist = [ + ('marker', object_fakes.object_name_2), + ('container', object_fakes.container_name_2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'marker': object_fakes.object_name_2, + } + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name_2, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.object_name_2, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_objects_end_marker(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + '--end-marker', object_fakes.object_name_2, + object_fakes.container_name_2, + ] + verifylist = [ + ('end_marker', object_fakes.object_name_2), + ('container', object_fakes.container_name_2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'end_marker': object_fakes.object_name_2, + } + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name_2, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.object_name_2, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_objects_limit(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + '--limit', '2', + object_fakes.container_name_2, + ] + verifylist = [ + ('limit', 2), + ('container', object_fakes.container_name_2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'limit': 2, + } + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name_2, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.object_name_2, ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_objects_long(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT), + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + '--long', + object_fakes.container_name, + ] + verifylist = [ + ('long', True), + ('container', object_fakes.container_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + } + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name, + **kwargs + ) + + collist = ('Name', 'Bytes', 'Hash', 'Content Type', 'Last Modified') + self.assertEqual(columns, collist) + datalist = ( + ( + object_fakes.object_name_1, + object_fakes.object_bytes_1, + object_fakes.object_hash_1, + object_fakes.object_content_type_1, + object_fakes.object_modified_1, + ), + ( + object_fakes.object_name_2, + object_fakes.object_bytes_2, + object_fakes.object_hash_2, + object_fakes.object_content_type_2, + object_fakes.object_modified_2, + ), + ) + self.assertEqual(tuple(data), datalist) + + def test_object_list_objects_all(self, o_mock): + o_mock.return_value = [ + copy.deepcopy(object_fakes.OBJECT), + copy.deepcopy(object_fakes.OBJECT_2), + ] + + arglist = [ + '--all', + object_fakes.container_name, + ] + verifylist = [ + ('all', True), + ('container', object_fakes.container_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'full_listing': True, + } + o_mock.assert_called_with( + self.app.restapi, + AUTH_URL, + object_fakes.container_name, + **kwargs + ) + + collist = ('Name',) + self.assertEqual(columns, collist) + datalist = ( + (object_fakes.object_name_1, ), + (object_fakes.object_name_2, ), + ) + self.assertEqual(tuple(data), datalist) diff --git a/openstackclient/tests/object/v1/__init__.py b/openstackclient/tests/object/v1/__init__.py new file mode 100644 index 0000000000..02be10cd89 --- /dev/null +++ b/openstackclient/tests/object/v1/__init__.py @@ -0,0 +1,12 @@ +# 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. +# diff --git a/openstackclient/tests/object/v1/lib/__init__.py b/openstackclient/tests/object/v1/lib/__init__.py new file mode 100644 index 0000000000..02be10cd89 --- /dev/null +++ b/openstackclient/tests/object/v1/lib/__init__.py @@ -0,0 +1,12 @@ +# 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. +# diff --git a/openstackclient/tests/object/v1/lib/test_container.py b/openstackclient/tests/object/v1/lib/test_container.py new file mode 100644 index 0000000000..a241cc021f --- /dev/null +++ b/openstackclient/tests/object/v1/lib/test_container.py @@ -0,0 +1,159 @@ +# Copyright 2013 Nebula Inc. +# +# 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. +# + +"""Test Object API library module""" + +from __future__ import unicode_literals + +import mock + + +from openstackclient.object.v1.lib import container as lib_container +from openstackclient.tests.common import test_restapi as restapi +from openstackclient.tests import utils + + +fake_auth = '11223344556677889900' +fake_url = 'http://gopher.com' + +fake_container = 'rainbarrel' + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.endpoint = fake_url + self.token = fake_auth + + +class TestObject(utils.TestCommand): + + def setUp(self): + super(TestObject, self).setUp() + self.app.client_manager.object = FakeClient() + self.app.restapi = mock.MagicMock() + + +class TestObjectListContainers(TestObject): + + def test_list_containers_no_options(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_container.list_containers( + self.app.restapi, + self.app.client_manager.object.endpoint, + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '?format=json', + ) + self.assertEqual(data, resp) + + def test_list_containers_marker(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_container.list_containers( + self.app.restapi, + self.app.client_manager.object.endpoint, + marker='next', + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '?format=json&marker=next', + ) + self.assertEqual(data, resp) + + def test_list_containers_limit(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_container.list_containers( + self.app.restapi, + self.app.client_manager.object.endpoint, + limit=5, + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '?format=json&limit=5', + ) + self.assertEqual(data, resp) + + def test_list_containers_end_marker(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_container.list_containers( + self.app.restapi, + self.app.client_manager.object.endpoint, + end_marker='last', + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '?format=json&end_marker=last', + ) + self.assertEqual(data, resp) + + def test_list_containers_prefix(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_container.list_containers( + self.app.restapi, + self.app.client_manager.object.endpoint, + prefix='foo/', + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '?format=json&prefix=foo/', + ) + self.assertEqual(data, resp) + + def test_list_containers_full_listing(self): + + def side_effect(*args, **kwargs): + rv = self.app.restapi.request.return_value + self.app.restapi.request.return_value = restapi.FakeResponse( + data=[], + ) + self.app.restapi.request.side_effect = None + return rv + + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + self.app.restapi.request.side_effect = side_effect + + data = lib_container.list_containers( + self.app.restapi, + self.app.client_manager.object.endpoint, + full_listing=True, + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '?format=json&marker=is-name', + ) + self.assertEqual(data, resp) diff --git a/openstackclient/tests/object/v1/lib/test_object.py b/openstackclient/tests/object/v1/lib/test_object.py new file mode 100644 index 0000000000..ef8ae18dc5 --- /dev/null +++ b/openstackclient/tests/object/v1/lib/test_object.py @@ -0,0 +1,203 @@ +# Copyright 2013 Nebula Inc. +# +# 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. +# + +"""Test Object API library module""" + +from __future__ import unicode_literals + +import mock + +from openstackclient.object.v1.lib import object as lib_object +from openstackclient.tests.common import test_restapi as restapi +from openstackclient.tests import utils + + +fake_auth = '11223344556677889900' +fake_url = 'http://gopher.com' + +fake_container = 'rainbarrel' + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.endpoint = fake_url + self.token = fake_auth + + +class TestObject(utils.TestCommand): + + def setUp(self): + super(TestObject, self).setUp() + self.app.client_manager.object = FakeClient() + self.app.restapi = mock.MagicMock() + + +class TestObjectListObjects(TestObject): + + def test_list_objects_no_options(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json', + ) + self.assertEqual(data, resp) + + def test_list_objects_marker(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + marker='next', + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json&marker=next', + ) + self.assertEqual(data, resp) + + def test_list_objects_limit(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + limit=5, + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json&limit=5', + ) + self.assertEqual(data, resp) + + def test_list_objects_end_marker(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + end_marker='last', + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json&end_marker=last', + ) + self.assertEqual(data, resp) + + def test_list_objects_delimiter(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + delimiter='|', + ) + + # Check expected values + # NOTE(dtroyer): requests handles the URL encoding and we're + # mocking that so use the otherwise-not-legal + # pipe '|' char in the response. + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json&delimiter=|', + ) + self.assertEqual(data, resp) + + def test_list_objects_prefix(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + prefix='foo/', + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json&prefix=foo/', + ) + self.assertEqual(data, resp) + + def test_list_objects_path(self): + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + path='next', + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json&path=next', + ) + self.assertEqual(data, resp) + + def test_list_objects_full_listing(self): + + def side_effect(*args, **kwargs): + rv = self.app.restapi.request.return_value + self.app.restapi.request.return_value = restapi.FakeResponse( + data=[], + ) + self.app.restapi.request.side_effect = None + return rv + + resp = [{'name': 'is-name'}] + self.app.restapi.request.return_value = restapi.FakeResponse(data=resp) + self.app.restapi.request.side_effect = side_effect + + data = lib_object.list_objects( + self.app.restapi, + self.app.client_manager.object.endpoint, + fake_container, + full_listing=True, + ) + + # Check expected values + self.app.restapi.request.assert_called_with( + 'GET', + fake_url + '/' + fake_container + '?format=json&marker=is-name', + ) + self.assertEqual(data, resp) diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index ff7d8a336d..4516318908 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -71,6 +71,7 @@ class TestCommand(TestCase): self.fake_stdout = fakes.FakeStdout() self.app = fakes.FakeApp(self.fake_stdout) self.app.client_manager = fakes.FakeClientManager() + self.app.restapi = fakes.FakeRESTApi() def check_parser(self, cmd, args, verify_args): cmd_parser = cmd.get_parser('check_parser') diff --git a/setup.cfg b/setup.cfg index ebfcb9fa87..e6fe8475ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -239,6 +239,10 @@ openstack.compute.v2 = server_unrescue = openstackclient.compute.v2.server:UnrescueServer server_unset = openstackclient.compute.v2.server:UnsetServer +openstack.object_store.v1 = + container_list = openstackclient.object.v1.container:ListContainer + object_list = openstackclient.object.v1.object:ListObject + openstack.volume.v1 = snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot snapshot_delete = openstackclient.volume.v1.snapshot:DeleteSnapshot