diff options
Diffstat (limited to 'troveclient/tests')
| -rw-r--r-- | troveclient/tests/fakes.py | 85 | ||||
| -rw-r--r-- | troveclient/tests/test_instances.py | 4 | ||||
| -rw-r--r-- | troveclient/tests/test_modules.py | 44 | ||||
| -rw-r--r-- | troveclient/tests/test_utils.py | 52 | ||||
| -rw-r--r-- | troveclient/tests/test_v1_shell.py | 187 | ||||
| -rw-r--r-- | troveclient/tests/utils.py | 16 |
6 files changed, 360 insertions, 28 deletions
diff --git a/troveclient/tests/fakes.py b/troveclient/tests/fakes.py index 820a433..6ef23ff 100644 --- a/troveclient/tests/fakes.py +++ b/troveclient/tests/fakes.py @@ -35,16 +35,32 @@ def assert_has_keys(dict, required=[], optional=[]): class FakeClient(client.Client): + URL_QUERY_SEPARATOR = '&' + URL_SEPARATOR = '?' + def __init__(self, *args, **kwargs): client.Client.__init__(self, 'username', 'password', 'project_id', 'auth_url', extensions=kwargs.get('extensions')) self.client = FakeHTTPClient(**kwargs) + def _order_url_query_str(self, url): + """Returns the url with the query strings ordered, if they exist and + there's more than one. Otherwise the url is returned unaltered. + """ + if self.URL_QUERY_SEPARATOR in url: + parts = url.split(self.URL_SEPARATOR) + if len(parts) == 2: + queries = sorted(parts[1].split(self.URL_QUERY_SEPARATOR)) + url = self.URL_SEPARATOR.join( + [parts[0], self.URL_QUERY_SEPARATOR.join(queries)]) + return url + def assert_called(self, method, url, body=None, pos=-1): """Assert than an API method was just called.""" - expected = (method, url) - called = self.client.callstack[pos][0:2] + expected = (method, utils.order_url(url)) + called = (self.client.callstack[pos][0], + utils.order_url(self.client.callstack[pos][1])) assert self.client.callstack, \ "Expected %s %s but no calls were made." % expected @@ -59,14 +75,14 @@ class FakeClient(client.Client): def assert_called_anytime(self, method, url, body=None): """Assert than an API method was called anytime in the test.""" - expected = (method, url) + expected = (method, utils.order_url(url)) assert self.client.callstack, \ "Expected %s %s but no calls were made." % expected found = False for entry in self.client.callstack: - if expected == entry[0:2]: + if expected == (entry[0], utils.order_url(entry[1])): found = True break @@ -389,6 +405,67 @@ class FakeHTTPClient(base_client.HTTPClient): def get_instances_1234_metadata_key123(self, **kw): return (200, {}, {"metadata": {}}) + def get_modules(self, **kw): + return (200, {}, {"modules": [ + { + "id": "4321", + "name": "mod1", + "type": "ping", + "datastore": 'all', + "datastore_version": 'all', + "tenant": 'all', + "auto_apply": 0, + "visible": 1}, + { + "id": "8765", + "name": "mod2", + "type": "ping", + "datastore": 'all', + "datastore_version": 'all', + "tenant": 'all', + "auto_apply": 0, + "visible": 1}]}) + + def get_modules_4321(self, **kw): + r = {'module': self.get_modules()[2]['modules'][0]} + return (200, {}, r) + + def get_modules_8765(self, **kw): + r = {'module': self.get_modules()[2]['modules'][1]} + return (200, {}, r) + + def post_modules(self, **kw): + r = {'module': self.get_modules()[2]['modules'][0]} + return (200, {}, r) + + def put_modules_4321(self, **kw): + return (200, {}, {"module": {'name': 'mod3'}}) + + def delete_modules_4321(self, **kw): + return (200, {}, None) + + def get_instances_1234_modules(self, **kw): + return (200, {}, {"modules": [{"module": {}}]}) + + def get_modules_4321_instances(self, **kw): + return self.get_instances() + + def get_instances_modules(self, **kw): + return (200, {}, None) + + def get_instances_member_1_modules(self, **kw): + return self.get_modules() + + def get_instances_member_2_modules(self, **kw): + return self.get_modules() + + def post_instances_1234_modules(self, **kw): + r = {'modules': [self.get_modules()[2]['modules'][0]]} + return (200, {}, r) + + def delete_instances_1234_modules_4321(self, **kw): + return (200, {}, None) + def get_limits(self, **kw): return (200, {}, {"limits": [ { diff --git a/troveclient/tests/test_instances.py b/troveclient/tests/test_instances.py index 6d96e0e..1e8e77d 100644 --- a/troveclient/tests/test_instances.py +++ b/troveclient/tests/test_instances.py @@ -99,7 +99,8 @@ class InstancesTest(testtools.TestCase): ['db1', 'db2'], ['u1', 'u2'], datastore="datastore", datastore_version="datastore-version", - nics=nics, slave_of='test') + nics=nics, slave_of='test', + modules=['mod_id']) self.assertEqual("/instances", p) self.assertEqual("instance", i) self.assertEqual(['db1', 'db2'], b["instance"]["databases"]) @@ -116,6 +117,7 @@ class InstancesTest(testtools.TestCase): self.assertEqual('test', b['instance']['replica_of']) self.assertNotIn('slave_of', b['instance']) self.assertTrue(mock_warn.called) + self.assertEqual([{'id': 'mod_id'}], b["instance"]["modules"]) def test_list(self): page_mock = mock.Mock() diff --git a/troveclient/tests/test_modules.py b/troveclient/tests/test_modules.py index 01ee548..5616df7 100644 --- a/troveclient/tests/test_modules.py +++ b/troveclient/tests/test_modules.py @@ -14,8 +14,10 @@ # under the License. # +import Crypto.Random import mock import testtools + from troveclient.v1 import modules @@ -52,25 +54,29 @@ class TestModules(testtools.TestCase): def side_effect_func(path, body, mod): return path, body, mod - self.modules._create = mock.Mock(side_effect=side_effect_func) - path, body, mod = self.modules.create( - self.module_name, "test", "my_contents", - description="my desc", - all_tenants=False, - datastore="ds", - datastore_version="ds-version", - auto_apply=True, - visible=True, - live_update=False) - self.assertEqual("/modules", path) - self.assertEqual("module", mod) - self.assertEqual(self.module_name, body["module"]["name"]) - self.assertEqual("ds", body["module"]["datastore"]["type"]) - self.assertEqual("ds-version", body["module"]["datastore"]["version"]) - self.assertFalse(body["module"]["all_tenants"]) - self.assertTrue(body["module"]["auto_apply"]) - self.assertTrue(body["module"]["visible"]) - self.assertFalse(body["module"]["live_update"]) + text_contents = "my_contents" + binary_contents = Crypto.Random.new().read(20) + for contents in [text_contents, binary_contents]: + self.modules._create = mock.Mock(side_effect=side_effect_func) + path, body, mod = self.modules.create( + self.module_name, "test", contents, + description="my desc", + all_tenants=False, + datastore="ds", + datastore_version="ds-version", + auto_apply=True, + visible=True, + live_update=False) + self.assertEqual("/modules", path) + self.assertEqual("module", mod) + self.assertEqual(self.module_name, body["module"]["name"]) + self.assertEqual("ds", body["module"]["datastore"]["type"]) + self.assertEqual("ds-version", + body["module"]["datastore"]["version"]) + self.assertFalse(body["module"]["all_tenants"]) + self.assertTrue(body["module"]["auto_apply"]) + self.assertTrue(body["module"]["visible"]) + self.assertFalse(body["module"]["live_update"]) def test_update(self): resp = mock.Mock() diff --git a/troveclient/tests/test_utils.py b/troveclient/tests/test_utils.py index 28f3929..b7ebb08 100644 --- a/troveclient/tests/test_utils.py +++ b/troveclient/tests/test_utils.py @@ -15,9 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. +import Crypto.Random import os import six +import tempfile import testtools + from troveclient import utils @@ -53,3 +56,52 @@ class UtilsTest(testtools.TestCase): self.assertEqual('not_unicode', utils.slugify('not_unicode')) self.assertEqual('unicode', utils.slugify(six.u('unicode'))) self.assertEqual('slugify-test', utils.slugify('SLUGIFY% test!')) + + def test_encode_decode_data(self): + text_data_str = 'This is a text string' + try: + text_data_bytes = bytes('This is a byte stream', 'utf-8') + except TypeError: + text_data_bytes = bytes('This is a byte stream') + random_data_str = Crypto.Random.new().read(12) + random_data_bytes = bytearray(Crypto.Random.new().read(12)) + special_char_str = '\x00\xFF\x00\xFF\xFF\x00' + special_char_bytes = bytearray( + [ord(item) for item in special_char_str]) + data = [text_data_str, + text_data_bytes, + random_data_str, + random_data_bytes, + special_char_str, + special_char_bytes] + + for datum in data: + # the deserialized data is always a bytearray + try: + expected_deserialized = bytearray( + [ord(item) for item in datum]) + except TypeError: + expected_deserialized = bytearray( + [item for item in datum]) + serialized_data = utils.encode_data(datum) + self.assertIsNotNone(serialized_data, "'%s' serialized is None" % + datum) + deserialized_data = utils.decode_data(serialized_data) + self.assertIsNotNone(deserialized_data, "'%s' deserialized is None" + % datum) + self.assertEqual(expected_deserialized, deserialized_data, + "Serialize/Deserialize failed") + # Now we write the data to a file and read it back in + # to make sure the round-trip doesn't change anything. + with tempfile.NamedTemporaryFile() as temp_file: + with open(temp_file.name, 'wb') as fh_w: + fh_w.write( + bytearray([ord(item) for item in serialized_data])) + with open(temp_file.name, 'rb') as fh_r: + new_serialized_data = fh_r.read() + new_deserialized_data = utils.decode_data( + new_serialized_data) + self.assertIsNotNone(new_deserialized_data, + "'%s' deserialized is None" % datum) + self.assertEqual(expected_deserialized, new_deserialized_data, + "Serialize/Deserialize with files failed") diff --git a/troveclient/tests/test_v1_shell.py b/troveclient/tests/test_v1_shell.py index 4de0e78..11767da 100644 --- a/troveclient/tests/test_v1_shell.py +++ b/troveclient/tests/test_v1_shell.py @@ -13,15 +13,26 @@ # License for the specific language governing permissions and limitations # under the License. -import six - +try: + # handle py34 + import builtins +except ImportError: + # and py27 + import __builtin__ as builtins + +import base64 import fixtures import mock +import re +import six +import testtools + import troveclient.client from troveclient import exceptions import troveclient.shell from troveclient.tests import fakes from troveclient.tests import utils +import troveclient.v1.modules import troveclient.v1.shell @@ -86,6 +97,76 @@ class ShellTest(utils.TestCase): def assert_called_anytime(self, method, url, body=None): return self.shell.cs.assert_called_anytime(method, url, body) + def test__strip_option(self): + # Format is: opt_name, opt_string, _strip_options_kwargs, + # expected_value, expected_opt_string, exception_msg + data = [ + ["volume", "volume=10", + {}, "10", "", None], + ["volume", ",volume=10,,type=mine,", + {}, "10", "type=mine", None], + ["volume", "type=mine", + {}, "", "type=mine", "Missing option 'volume'.*"], + ["volume", "type=mine", + {'is_required': False}, None, "type=mine", None], + ["volume", "volume=1, volume=2", + {}, "", "", "Option 'volume' found more than once.*"], + ["volume", "volume=1, volume=2", + {'allow_multiple': True}, ['1', '2'], "", None], + ["volume", "volume=1, volume=2,, volume=4, volume=6", + {'allow_multiple': True}, ['1', '2', '4', '6'], "", None], + ["module", ",flavor=10,,nic='net-id=net',module=test, module=test", + {'allow_multiple': True}, ['test'], + "flavor=10,,nic='net-id=net'", None], + ["nic", ",flavor=10,,nic=net-id=net, module=test", + {'quotes_required': True}, "", "", + "Invalid 'nic' option. The value must be quoted.*"], + ["nic", ",flavor=10,,nic='net-id=net', module=test", + {'quotes_required': True}, "net-id=net", + "flavor=10,, module=test", None], + ["nic", + ",nic='port-id=port',flavor=10,,nic='net-id=net', module=test", + {'quotes_required': True, 'allow_multiple': True}, + ["net-id=net", "port-id=port"], + "flavor=10,, module=test", None], + ] + + count = 0 + for datum in data: + count += 1 + opt_name = datum[0] + opts_str = datum[1] + kwargs = datum[2] + expected_value = datum[3] + expected_opt_string = datum[4] + exception_msg = datum[5] + msg = "Error (test data line %s): " % count + try: + value, opt_string = troveclient.v1.shell._strip_option( + opts_str, opt_name, **kwargs) + if exception_msg: + self.assertEqual(True, False, + "%sException not thrown, expecting %s" % + (msg, exception_msg)) + if isinstance(expected_value, list): + self.assertEqual( + set(value), set(expected_value), + "%sValue not correct" % msg) + else: + self.assertEqual(value, expected_value, + "%sValue not correct" % msg) + self.assertEqual(opt_string, expected_opt_string, + "%sOption string not correct" % msg) + except Exception as ex: + if exception_msg: + msg = ex.message if hasattr(ex, 'message') else str(ex) + self.assertThat(msg, + testtools.matchers.MatchesRegex( + exception_msg, re.DOTALL), + exception_msg, "%sWrong ex" % msg) + else: + raise + def test_instance_list(self): self.run_command('list') self.assert_called('GET', '/instances') @@ -184,6 +265,19 @@ class ShellTest(utils.TestCase): 'replica_count': 1 }}) + def test_boot_with_modules(self): + self.run_command('create test-member-1 1 --size 1 --volume_type lvm ' + '--module 4321 --module 8765') + self.assert_called_anytime( + 'POST', '/instances', + {'instance': { + 'volume': {'size': 1, 'type': 'lvm'}, + 'flavorRef': 1, + 'name': 'test-member-1', + 'replica_count': 1, + 'modules': [{'id': '4321'}, {'id': '8765'}] + }}) + def test_boot_by_flavor_name(self): self.run_command( 'create test-member-1 m1.tiny --size 1 --volume_type lvm') @@ -253,7 +347,7 @@ class ShellTest(utils.TestCase): cmd = ('cluster-create test-clstr vertica 7.1 --instance volume=2 ' '--instance flavor=2,volume=1') self.assertRaisesRegexp( - exceptions.MissingArgs, 'Missing argument\(s\): flavor', + exceptions.MissingArgs, "Missing option 'flavor'", self.run_command, cmd) def test_cluster_grow(self): @@ -301,7 +395,7 @@ class ShellTest(utils.TestCase): '--instance flavor=2,volume=1,nic=net-id=some-id,' 'port-id=some-port-id,availability_zone=2') self.assertRaisesRegexp( - exceptions.ValidationError, "Invalid 'nic' parameter. " + exceptions.ValidationError, "Invalid 'nic' option. " "The value must be quoted.", self.run_command, cmd) @@ -427,6 +521,91 @@ class ShellTest(utils.TestCase): self.run_command('metadata-show 1234 key123') self.assert_called('GET', '/instances/1234/metadata/key123') + def test_module_list(self): + self.run_command('module-list') + self.assert_called('GET', '/modules') + + def test_module_list_datastore(self): + self.run_command('module-list --datastore all') + self.assert_called('GET', '/modules?datastore=all') + + def test_module_show(self): + self.run_command('module-show 4321') + self.assert_called('GET', '/modules/4321') + + def test_module_create(self): + with mock.patch.object(builtins, 'open'): + return_value = b'mycontents' + expected_contents = str(return_value.decode('utf-8')) + mock_encode = mock.Mock(return_value=return_value) + with mock.patch.object(base64, 'b64encode', mock_encode): + self.run_command('module-create mod1 type filename') + self.assert_called_anytime( + 'POST', '/modules', + {'module': {'contents': expected_contents, + 'all_tenants': 0, + 'module_type': 'type', 'visible': 1, + 'auto_apply': 0, 'live_update': 0, + 'name': 'mod1'}}) + + def test_module_update(self): + with mock.patch.object(troveclient.v1.modules.Module, '__repr__', + mock.Mock(return_value='4321')): + self.run_command('module-update 4321 --name mod3') + self.assert_called_anytime( + 'PUT', '/modules/4321', + {'module': {'name': 'mod3'}}) + + def test_module_delete(self): + with mock.patch.object(troveclient.v1.modules.Module, '__repr__', + mock.Mock(return_value='4321')): + self.run_command('module-delete 4321') + self.assert_called_anytime('DELETE', '/modules/4321') + + def test_module_list_instance(self): + self.run_command('module-list-instance 1234') + self.assert_called_anytime('GET', '/instances/1234/modules') + + def test_module_instances(self): + with mock.patch.object(troveclient.v1.modules.Module, '__repr__', + mock.Mock(return_value='4321')): + self.run_command('module-instances 4321') + self.assert_called_anytime('GET', '/modules/4321/instances') + + def test_module_instances_clustered(self): + with mock.patch.object(troveclient.v1.modules.Module, '__repr__', + mock.Mock(return_value='4321')): + self.run_command('module-instances 4321 --include_clustered') + self.assert_called_anytime( + 'GET', '/modules/4321/instances?include_clustered=True') + + def test_cluster_modules(self): + self.run_command('cluster-modules cls-1234') + self.assert_called_anytime('GET', '/clusters/cls-1234') + + def test_module_apply(self): + self.run_command('module-apply 1234 4321 8765') + self.assert_called_anytime('POST', '/instances/1234/modules', + {'modules': + [{'id': '4321'}, {'id': '8765'}]}) + + def test_module_remove(self): + self.run_command('module-remove 1234 4321') + self.assert_called_anytime('DELETE', '/instances/1234/modules/4321') + + def test_module_query(self): + self.run_command('module-query 1234') + self.assert_called('GET', '/instances/1234/modules?from_guest=True') + + def test_module_retrieve(self): + with mock.patch.object(troveclient.v1.modules.Module, '__getattr__', + mock.Mock(return_value='4321')): + self.run_command('module-retrieve 1234') + self.assert_called( + 'GET', + '/instances/1234/modules?' + 'include_contents=True&from_guest=True') + def test_limit_list(self): self.run_command('limit-list') self.assert_called('GET', '/limits') diff --git a/troveclient/tests/utils.py b/troveclient/tests/utils.py index c3e81ac..a912649 100644 --- a/troveclient/tests/utils.py +++ b/troveclient/tests/utils.py @@ -23,6 +23,22 @@ AUTH_URL = "http://localhost:5002/auth_url" AUTH_URL_V1 = "http://localhost:5002/auth_url/v1.0" AUTH_URL_V2 = "http://localhost:5002/auth_url/v2.0" +URL_QUERY_SEPARATOR = '&' +URL_SEPARATOR = '?' + + +def order_url(url): + """Returns the url with the query strings ordered, if they exist and + there's more than one. Otherwise the url is returned unaltered. + """ + if URL_QUERY_SEPARATOR in url: + parts = url.split(URL_SEPARATOR) + if len(parts) == 2: + queries = sorted(parts[1].split(URL_QUERY_SEPARATOR)) + url = URL_SEPARATOR.join( + [parts[0], URL_QUERY_SEPARATOR.join(queries)]) + return url + def _patch_mock_to_raise_for_invalid_assert_calls(): def raise_for_invalid_assert_calls(wrapped): |
