Object API commands using our REST API layer

* Add object-store API to ClientManager
* Add object-store client
* Add Object API library in openstackclient.object.v1.lib
* Add Object API {container,object} list commands
* Add library tests
* Add command tests

This should complete the Object v1 container and object list commands

Change-Id: Ib1770d45efa8871959826b85faafa1e0bcef0a03
This commit is contained in:
Dean Troyer 2013-08-20 15:13:41 -05:00
parent 17f13f7bf4
commit 725e2543ef
21 changed files with 1653 additions and 2 deletions

@ -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,

@ -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.
#

@ -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

@ -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.
#

@ -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))

@ -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.
#

@ -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()

@ -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()

@ -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))

@ -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(

@ -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

@ -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.
#

@ -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,
}

@ -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)

@ -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)

@ -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.
#

@ -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.
#

@ -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)

@ -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)

@ -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')

@ -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