From bd361644f73b7a688d45f67621b1a08f513fa024 Mon Sep 17 00:00:00 2001 From: Juan Manuel Olle Date: Tue, 8 Apr 2014 16:38:36 -0300 Subject: [PATCH] Optional size parameter for volume creation This patch makes optional the parameter size for creating a volume from a snapshot or other volume. The parameter is still required for other kind of volume creation. Change-Id: I75dcf0e647f6e82a2407bcf23ef359f5f87323e8 Closes-Bug: #1304463 --- cinderclient/tests/fakes.py | 38 ++++++++++++++++++++-- cinderclient/tests/v2/fakes.py | 4 ++- cinderclient/tests/v2/test_shell.py | 49 ++++++++++++++++++++++++++--- cinderclient/v2/shell.py | 13 +++++++- 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/cinderclient/tests/fakes.py b/cinderclient/tests/fakes.py index 5a3937c58..eede6945d 100644 --- a/cinderclient/tests/fakes.py +++ b/cinderclient/tests/fakes.py @@ -34,7 +34,22 @@ def assert_has_keys(dict, required=[], optional=[]): class FakeClient(object): - def assert_called(self, method, url, body=None, pos=-1, **kwargs): + def _dict_match(self, partial, real): + + result = True + try: + for key, value in partial.items(): + if type(value) is dict: + result = self._dict_match(value, real[key]) + else: + assert real[key] == value + result = True + except (AssertionError, KeyError): + result = False + return result + + def assert_called(self, method, url, body=None, + partial_body=None, pos=-1, **kwargs): """ Assert than an API method was just called. """ @@ -50,7 +65,17 @@ class FakeClient(object): if body is not None: assert self.client.callstack[pos][2] == body - def assert_called_anytime(self, method, url, body=None): + if partial_body is not None: + try: + assert self._dict_match(partial_body, + self.client.callstack[pos][2]) + except AssertionError: + print(self.client.callstack[pos][2]) + print("does not contain") + print(partial_body) + raise + + def assert_called_anytime(self, method, url, body=None, partial_body=None): """ Assert than an API method was called anytime in the test. """ @@ -77,6 +102,15 @@ class FakeClient(object): print(body) raise + if partial_body is not None: + try: + assert self._dict_match(partial_body, entry[2]) + except AssertionError: + print(entry[2]) + print("does not contain") + print(partial_body) + raise + def clear_callstack(self): self.client.callstack = [] diff --git a/cinderclient/tests/v2/fakes.py b/cinderclient/tests/v2/fakes.py index 494ed2896..ac4a62c8d 100644 --- a/cinderclient/tests/v2/fakes.py +++ b/cinderclient/tests/v2/fakes.py @@ -376,7 +376,9 @@ class FakeHTTPClient(base_client.HTTPClient): return self.post_volumes_1234_action(body, **kw) def post_volumes(self, **kw): - return (202, {}, {'volume': {}}) + size = kw['body']['volume'].get('size', 1) + volume = _stub_volume(id='1234', size=size) + return (202, {}, {'volume': volume}) def delete_volumes_1234(self, **kw): return (202, {}, None) diff --git a/cinderclient/tests/v2/test_shell.py b/cinderclient/tests/v2/test_shell.py index a52bd10d4..69b36313e 100644 --- a/cinderclient/tests/v2/test_shell.py +++ b/cinderclient/tests/v2/test_shell.py @@ -14,13 +14,13 @@ # under the License. import fixtures +import httpretty from cinderclient import client from cinderclient import shell from cinderclient.tests import utils from cinderclient.tests.v2 import fakes from cinderclient.tests.fixture_data import keystone_client -import httpretty class ShellTest(utils.TestCase): @@ -66,11 +66,15 @@ class ShellTest(utils.TestCase): def run_command(self, cmd): self.shell.main(cmd.split()) - def assert_called(self, method, url, body=None, **kwargs): - return self.shell.cs.assert_called(method, url, body, **kwargs) + def assert_called(self, method, url, body=None, + partial_body=None, **kwargs): + return self.shell.cs.assert_called(method, url, body, + partial_body, **kwargs) - def assert_called_anytime(self, method, url, body=None): - return self.shell.cs.assert_called_anytime(method, url, body) + def assert_called_anytime(self, method, url, body=None, + partial_body=None): + return self.shell.cs.assert_called_anytime(method, url, body, + partial_body) @httpretty.activate def test_list(self): @@ -103,6 +107,41 @@ class ShellTest(utils.TestCase): self.run_command('availability-zone-list') self.assert_called('GET', '/os-availability-zone') + @httpretty.activate + def test_create_volume_from_snapshot(self): + self.register_keystone_auth_fixture() + expected = {'volume': {'size': None}} + + expected['volume']['snapshot_id'] = '1234' + self.run_command('create --snapshot-id=1234') + self.assert_called_anytime('POST', '/volumes', partial_body=expected) + self.assert_called('GET', '/volumes/1234') + + expected['volume']['size'] = 2 + self.run_command('create --snapshot-id=1234 2') + self.assert_called_anytime('POST', '/volumes', partial_body=expected) + self.assert_called('GET', '/volumes/1234') + + @httpretty.activate + def test_create_volume_from_volume(self): + self.register_keystone_auth_fixture() + expected = {'volume': {'size': None}} + + expected['volume']['source_volid'] = '1234' + self.run_command('create --source-volid=1234') + self.assert_called_anytime('POST', '/volumes', partial_body=expected) + self.assert_called('GET', '/volumes/1234') + + expected['volume']['size'] = 2 + self.run_command('create --source-volid=1234 2') + self.assert_called_anytime('POST', '/volumes', partial_body=expected) + self.assert_called('GET', '/volumes/1234') + + @httpretty.activate + def test_create_size_required_if_not_snapshot_or_clone(self): + self.register_keystone_auth_fixture() + self.assertRaises(SystemExit, self.run_command, 'create') + @httpretty.activate def test_show(self): self.register_keystone_auth_fixture() diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index a5c0f2d6c..68b721c62 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -195,10 +195,21 @@ def do_show(cs, args): utils.print_dict(info) +class CheckSizeArgForCreate(argparse.Action): + def __call__(self, parser, args, values, option_string=None): + if (values or args.snapshot_id or args.source_volid) is None: + parser.error('Size is a required parameter if snapshot ' + 'or source volume is not specified.') + setattr(args, self.dest, values) + + @utils.arg('size', metavar='', + nargs='?', type=int, - help='Size of volume, in GBs.') + action=CheckSizeArgForCreate, + help='Size of volume, in GBs. (Required unless ' + 'snapshot-id/source-volid is specified).') @utils.arg('--snapshot-id', metavar='', default=None,