summaryrefslogtreecommitdiff
path: root/troveclient/tests
diff options
context:
space:
mode:
Diffstat (limited to 'troveclient/tests')
-rw-r--r--troveclient/tests/fakes.py85
-rw-r--r--troveclient/tests/test_instances.py4
-rw-r--r--troveclient/tests/test_modules.py44
-rw-r--r--troveclient/tests/test_utils.py52
-rw-r--r--troveclient/tests/test_v1_shell.py187
-rw-r--r--troveclient/tests/utils.py16
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):