Add datastore filter to backup-list

This fix enhances the backup-list command to optionally receive
a datastore name or ID to filter the backup list by. The filter
is sent as a query string.

To attach the query string to the URL and have it still work with
the URL for pagination, i have made some changes in the way url
with query strings are constructed. This includes the pagination
URL.

partially implements: blueprint backup-metadata

Change-Id: I0b9ef3ec7f51ed76517a22f9c0edfdce3694a36f
This commit is contained in:
Steve Leon 2014-04-25 13:21:59 -07:00 committed by Steve Leon
parent 68a6424256
commit 7170b72ceb
7 changed files with 65 additions and 23 deletions

@ -62,8 +62,12 @@ class Manager(utils.HookableMixin):
def __init__(self, api):
self.api = api
def _paginated(self, url, response_key, limit=None, marker=None):
resp, body = self.api.client.get(common.limit_url(url, limit, marker))
def _paginated(self, url, response_key, limit=None, marker=None,
query_strings=None):
query_strings = query_strings or {}
url = common.append_query_strings(url, limit=limit, marker=marker,
**query_strings)
resp, body = self.api.client.get(url)
if not body:
raise Exception("Call to " + url + " did not return a body.")
links = body.get('links', [])

@ -23,16 +23,12 @@ def check_for_exceptions(resp, body, url):
raise exceptions.from_response(resp, body, url)
def limit_url(url, limit=None, marker=None):
if not limit and not marker:
def append_query_strings(url, **query_strings):
if not query_strings:
return url
query = []
if marker:
query.append("marker=%s" % marker)
if limit:
query.append("limit=%s" % limit)
query = '?' + '&'.join(query)
return url + query
query = '&'.join('{0}={1}'.format(key, val)
for key, val in query_strings.items() if val)
return url + ('?' + query if query else "")
def quote_user_host(user, host):

@ -80,7 +80,17 @@ class BackupManagerTest(testtools.TestCase):
limit = "test-limit"
marker = "test-marker"
self.backups.list(limit, marker)
page_mock.assert_called_with("/backups", "backups", limit, marker)
page_mock.assert_called_with("/backups", "backups", limit, marker, {})
def test_list_by_datastore(self):
page_mock = mock.Mock()
self.backups._paginated = page_mock
limit = "test-limit"
marker = "test-marker"
datastore = "test-mysql"
self.backups.list(limit, marker, datastore)
page_mock.assert_called_with("/backups", "backups", limit, marker,
{'datastore': datastore})
def test_get(self):
get_mock = mock.Mock()

@ -279,9 +279,14 @@ class MangerPaginationTests(ManagerTest):
def side_effect(url):
if url == self.url:
return (None, self.body)
if url == self.next_url:
return (None, self.next_body)
return None, self.body
# In python 3 the order in the dictionary is not constant
# between runs. So we cant rely on the URL params to be
# in the same order
if ('marker=%s' % self.marker in url and
'limit=%s' % self.limit in url):
self.next_url = url
return None, self.next_body
self.manager.api.client.get = mock.Mock(side_effect=side_effect)

@ -31,15 +31,34 @@ class CommonTest(testtools.TestCase):
self.assertRaises(Exception,
common.check_for_exceptions, resp, "body")
def test_limit_url(self):
def test_append_query_strings(self):
url = "test-url"
self.assertEqual(url, common.limit_url(url, limit=None, marker=None))
self.assertEqual(url, common.append_query_strings(url))
self.assertEqual(url, common.append_query_strings(
url, limit=None, marker=None))
limit = "test-limit"
marker = "test-marker"
expected = "test-url?marker=test-marker&limit=test-limit"
self.assertEqual(expected,
common.limit_url(url, limit=limit, marker=marker))
result = common.append_query_strings(url, limit=limit, marker=marker)
self.assertTrue(result.startswith(url + '?'))
self.assertIn("limit=%s" % limit, result)
self.assertIn("marker=%s" % marker, result)
self.assertEqual(result.count('&'), 1)
opts = {}
self.assertEqual(url, common.append_query_strings(
url, limit=None, marker=None, **opts))
opts = {'key1': 'value1', 'key2': None}
result = common.append_query_strings(url, limit=None, marker=marker,
**opts)
self.assertTrue(result.startswith(url + '?'))
self.assertEqual(result.count('&'), 1)
self.assertNotIn("limit=%s" % limit, result)
self.assertIn("marker=%s" % marker, result)
self.assertIn("key1=%s" % opts['key1'], result)
self.assertNotIn("key2=%s" % opts['key2'], result)
class PaginatedTest(testtools.TestCase):

@ -38,12 +38,17 @@ class Backups(base.ManagerWithFind):
return self._get("/backups/%s" % base.getid(backup),
"backup")
def list(self, limit=None, marker=None):
def list(self, limit=None, marker=None, datastore=None):
"""Get a list of all backups.
:rtype: list of :class:`Backups`.
"""
return self._paginated("/backups", "backups", limit, marker)
query_strings = {}
if datastore:
query_strings = {'datastore': datastore}
return self._paginated("/backups", "backups", limit, marker,
query_strings)
def create(self, name, instance, description=None, parent_id=None):
"""Create a new backup from the given instance."""

@ -332,10 +332,13 @@ def do_backup_list_instance(cs, args):
@utils.arg('--limit', metavar='<limit>',
default=None,
help='Return up to N number of the most recent backups.')
@utils.arg('--datastore', metavar='<datastore>',
default=None,
help='Name or ID of the datastore to list backups for.')
@utils.service_type('database')
def do_backup_list(cs, args):
"""Lists available backups."""
wrapper = cs.backups.list(limit=args.limit)
wrapper = cs.backups.list(limit=args.limit, datastore=args.datastore)
backups = wrapper.items
while wrapper.next and not args.limit:
wrapper = cs.backups.list(marker=wrapper.next)