# Copyright 2015 Tesora 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. import logging import mock import six import django from django.conf import settings from django.urls import reverse from trove_dashboard import api from trove_dashboard.content.database_configurations \ import config_param_manager from trove_dashboard.test import helpers as test INDEX_URL = reverse('horizon:project:database_configurations:index') CREATE_URL = reverse('horizon:project:database_configurations:create') DETAIL_URL = 'horizon:project:database_configurations:detail' ADD_URL = 'horizon:project:database_configurations:add' class DatabaseConfigurationsTests(test.TestCase): @test.create_mocks({api.trove: ('configuration_list',)}) def test_index(self): self.mock_configuration_list.return_value = ( self.database_configurations.list()) res = self.client.get(INDEX_URL) self.mock_configuration_list.assert_called_once_with( test.IsHttpRequest()) self.assertTemplateUsed(res, 'project/database_configurations/index.html') @test.create_mocks({api.trove: ('configuration_list',)}) def test_index_exception(self): self.mock_configuration_list.side_effect = self.exceptions.trove res = self.client.get(INDEX_URL) self.mock_configuration_list.assert_called_once_with( test.IsHttpRequest()) self.assertTemplateUsed( res, 'project/database_configurations/index.html') self.assertEqual(res.status_code, 200) self.assertMessageCount(res, error=1) @test.create_mocks({ api.trove: ('datastore_list', 'datastore_version_list')}) def test_create_configuration(self): self.mock_datastore_list.return_value = self.datastores.list() self.mock_datastore_version_list.return_value = ( self.datastore_versions.list()) res = self.client.get(CREATE_URL) self.mock_datastore_list.assert_called_once_with(test.IsHttpRequest()) self.assert_mock_multiple_calls_with_same_arguments( self.mock_datastore_version_list, 4, mock.call(test.IsHttpRequest(), test.IsA(str))) self.assertTemplateUsed(res, 'project/database_configurations/create.html') @test.create_mocks({api.trove: ('datastore_list',)}) def test_create_configuration_exception_on_datastore(self): self.mock_datastore_list.side_effect = self.exceptions.trove toSuppress = ["trove_dashboard.content." "database_configurations.forms", ] # Suppress expected log messages in the test output loggers = [] for cls in toSuppress: logger = logging.getLogger(cls) loggers.append((logger, logger.getEffectiveLevel())) logger.setLevel(logging.CRITICAL) try: res = self.client.get(CREATE_URL) self.mock_datastore_list.assert_called_once_with( test.IsHttpRequest()) self.assertEqual(res.status_code, 302) finally: # Restore the previous log levels for (log, level) in loggers: log.setLevel(level) @test.create_mocks({ api.trove: ('datastore_list', 'datastore_version_list', 'configuration_create')}) def _test_create_test_configuration( self, config_description=u''): self.mock_datastore_list.return_value = self.datastores.list() self.mock_datastore_version_list.return_value = ( self.datastore_versions.list()) self.mock_configuration_create.return_value = ( self.database_configurations.first()) name = u'config1' values = "{}" ds = self._get_test_datastore('mysql') dsv = self._get_test_datastore_version(ds.id, '5.5') config_datastore = ds.name config_datastore_version = dsv.name post = { 'method': 'CreateConfigurationForm', 'name': name, 'description': config_description, 'datastore': (config_datastore + ',' + config_datastore_version)} res = self.client.post(CREATE_URL, post) self.mock_datastore_list.assert_called_once_with(test.IsHttpRequest()) self.assert_mock_multiple_calls_with_same_arguments( self.mock_datastore_version_list, 4, mock.call(test.IsHttpRequest(), test.IsA(str))) self.mock_configuration_create.assert_called_once_with( test.IsHttpRequest(), name, values, description=config_description, datastore=config_datastore, datastore_version=config_datastore_version) self.assertNoFormErrors(res) self.assertMessageCount(success=1) def test_create_test_configuration(self): self._test_create_test_configuration(u'description of config1') def test_create_test_configuration_with_no_description(self): self._test_create_test_configuration() @test.create_mocks({ api.trove: ('datastore_list', 'datastore_version_list', 'configuration_create')}) def test_create_test_configuration_exception(self): self.mock_datastore_list.return_value = self.datastores.list() self.mock_datastore_version_list.return_value = ( self.datastore_versions.list()) self.mock_configuration_create.side_effect = self.exceptions.trove name = u'config1' values = "{}" config_description = u'description of config1' ds = self._get_test_datastore('mysql') dsv = self._get_test_datastore_version(ds.id, '5.5') config_datastore = ds.name config_datastore_version = dsv.name post = {'method': 'CreateConfigurationForm', 'name': name, 'description': config_description, 'datastore': config_datastore + ',' + config_datastore_version} res = self.client.post(CREATE_URL, post) self.mock_datastore_list.assert_called_once_with(test.IsHttpRequest()) self.assert_mock_multiple_calls_with_same_arguments( self.mock_datastore_version_list, 4, mock.call(test.IsHttpRequest(), test.IsA(str))) self.mock_configuration_create.assert_called_once_with( test.IsHttpRequest(), name, values, description=config_description, datastore=config_datastore, datastore_version=config_datastore_version) self.assertRedirectsNoFollow(res, INDEX_URL) @test.create_mocks({api.trove: ('configuration_get', 'configuration_instances',)}) def test_details_tab(self): config = self.database_configurations.first() self.mock_configuration_get.return_value = config details_url = self._get_url_with_arg(DETAIL_URL, config.id) url = details_url + '?tab=configuration_details__details' res = self.client.get(url) self.mock_configuration_get.assert_called_once_with( test.IsHttpRequest(), config.id) self.assertTemplateUsed(res, 'project/database_configurations/details.html') @test.create_mocks({api.trove: ('configuration_get',)}) def test_overview_tab_exception(self): config = self.database_configurations.first() self.mock_configuration_get.side_effect = self.exceptions.trove details_url = self._get_url_with_arg(DETAIL_URL, config.id) url = details_url + '?tab=configuration_details__overview' res = self.client.get(url) self.mock_configuration_get.assert_called_once_with( test.IsHttpRequest(), config.id) self.assertRedirectsNoFollow(res, INDEX_URL) @test.create_mocks({ api.trove: ('configuration_parameters_list',), config_param_manager.ConfigParamManager: ('get_configuration', 'configuration_get',)}) def test_add_parameter(self): config = self.database_configurations.first() self.mock_get_configuration.return_value = config self.mock_configuration_get.return_value = config ds = self._get_test_datastore('mysql') dsv = self._get_test_datastore_version(ds.id, '5.5') self.mock_configuration_parameters_list.return_value = ( self.configuration_parameters.list()) res = self.client.get(self._get_url_with_arg(ADD_URL, 'id')) self.mock_get_configuration.assert_called_once() self.mock_configuration_get.assert_called_once_with( test.IsHttpRequest()) self.mock_configuration_parameters_list.assert_called_once_with( test.IsHttpRequest(), ds.name, dsv.name) self.assertTemplateUsed( res, 'project/database_configurations/add_parameter.html') @test.create_mocks({ api.trove: ('configuration_parameters_list',), config_param_manager.ConfigParamManager: ('get_configuration', 'configuration_get',)}) def test_add_parameter_exception_on_parameters(self): try: config = self.database_configurations.first() self.mock_get_configuration.return_value = config self.mock_configuration_get.return_value = config ds = self._get_test_datastore('mysql') dsv = self._get_test_datastore_version(ds.id, '5.5') self.mock_configuration_parameters_list.side_effect = ( self.exceptions.trove) toSuppress = ["trove_dashboard.content." "database_configurations.forms", ] # Suppress expected log messages in the test output loggers = [] for cls in toSuppress: logger = logging.getLogger(cls) loggers.append((logger, logger.getEffectiveLevel())) logger.setLevel(logging.CRITICAL) try: res = self.client.get( self._get_url_with_arg(ADD_URL, config.id)) self.mock_get_configuration.assert_called_once() self.mock_configuration_get.assert_called_once_with( test.IsHttpRequest()) (self.mock_configuration_parameters_list .assert_called_once_with( test.IsHttpRequest(), ds.name, dsv.name)) self.assertEqual(res.status_code, 302) finally: # Restore the previous log levels for (log, level) in loggers: log.setLevel(level) finally: config_param_manager.delete(config.id) @test.create_mocks({ api.trove: ('configuration_parameters_list',), config_param_manager.ConfigParamManager: ('get_configuration', 'add_param', 'configuration_get',)}) def test_add_new_parameter(self): config = self.database_configurations.first() self.mock_get_configuration.return_value = config try: self.mock_configuration_get.return_value = config ds = self._get_test_datastore('mysql') dsv = self._get_test_datastore_version(ds.id, '5.5') self.mock_configuration_parameters_list.return_value = ( self.configuration_parameters.list()) name = self.configuration_parameters.first().name value = 1 self.mock_add_param.return_value = value post = { 'method': 'AddParameterForm', 'name': name, 'value': value} res = self.client.post(self._get_url_with_arg(ADD_URL, config.id), post) self.mock_get_configuration.assert_called_once() self.mock_configuration_get.assert_called_once_with( test.IsHttpRequest()) self.mock_configuration_parameters_list.assert_called_once_with( test.IsHttpRequest(), ds.name, dsv.name) self.mock_add_param.assert_called_once_with(name, value) self.assertNoFormErrors(res) self.assertMessageCount(success=1) finally: config_param_manager.delete(config.id) @test.create_mocks({ api.trove: ('configuration_get', 'configuration_parameters_list',), config_param_manager: ('get',)}) def test_add_parameter_invalid_value(self): try: config = self.database_configurations.first() # setup the configuration parameter manager config_param_mgr = config_param_manager.ConfigParamManager( config.id) config_param_mgr.configuration = config config_param_mgr.original_configuration_values = \ dict.copy(config.values) self.mock_get.return_value = config_param_mgr self.mock_configuration_parameters_list.return_value = ( self.configuration_parameters.list()) name = self.configuration_parameters.first().name value = "non-numeric" post = { 'method': 'AddParameterForm', 'name': name, 'value': value} res = self.client.post(self._get_url_with_arg(ADD_URL, config.id), post) self.assert_mock_multiple_calls_with_same_arguments( self.mock_get, 2, mock.call(test.IsHttpRequest(), test.IsA(six.string_types))) self.assert_mock_multiple_calls_with_same_arguments( self.mock_configuration_parameters_list, 2, mock.call(test.IsHttpRequest(), test.IsA(six.string_types), test.IsA(six.string_types))) self.assertFormError(res, "form", 'value', ['Value must be a number.']) finally: config_param_manager.delete(config.id) @test.create_mocks({api.trove: ('configuration_get', 'configuration_instances',)}) def test_values_tab_discard_action(self): config = self.database_configurations.first() self.mock_configuration_get.return_value = config details_url = self._get_url_with_arg(DETAIL_URL, config.id) url = details_url + '?tab=configuration_details__value' self._test_create_altered_config_params(config, url) # get the state of the configuration before discard action changed_configuration_values = \ dict.copy(config_param_manager.get(self.request, config.id) .get_configuration().values) res = self.client.post(url, {'action': u"values__discard_changes"}) self.mock_configuration_get.assert_called_once_with( test.IsHttpRequest(), config.id) if django.VERSION >= (1, 9): url = settings.TESTSERVER + url self.assertRedirectsNoFollow(res, url) # get the state of the configuration after discard action restored_configuration_values = \ dict.copy(config_param_manager.get(self.request, config.id) .get_configuration().values) self.assertTrue(config_param_manager.dict_has_changes( changed_configuration_values, restored_configuration_values)) @test.create_mocks({api.trove: ('configuration_instances', 'configuration_update',), config_param_manager: ('get',)}) def test_values_tab_apply_action(self): # NOTE(zhaochao): we cannot use copy.deepcopy() under Python 3, # because of the lazy-loading feature of the troveclient Resource # objects. copy.deepcopy will use hasattr to search for the # '__setstate__' attribute of the resource object. As the resource # object is lazy loading, searching attributes relys on the 'is_load' # property, unfortunately this property is also not loaded at the # moment, then we're getting in an infinite loop there. Python will # raise RuntimeError saying "maximum recursion depth exceeded", this is # ignored under Python 2.x, but reraised under Python 3 by hasattr(). # # Temporarily importing troveclient and reconstructing a configuration # object from the original config object's dict info will make this # case (and the next) working under Python 3. original_config = self.database_configurations.first() from troveclient.v1 import configurations config = configurations.Configuration( configurations.Configurations(None), original_config.to_dict()) # Making sure the newly constructed config object is the same as # the original one. self.assertEqual(config, original_config) # setup the configuration parameter manager config_param_mgr = config_param_manager.ConfigParamManager( config.id) config_param_mgr.configuration = config config_param_mgr.original_configuration_values = \ dict.copy(config.values) self.mock_get.return_value = config_param_mgr self.mock_configuration_update.return_value = None details_url = self._get_url_with_arg(DETAIL_URL, config.id) url = details_url + '?tab=configuration_details__value' self._test_create_altered_config_params(config, url) # apply changes res = self.client.post(url, {'action': u"values__apply_changes"}) self.assert_mock_multiple_calls_with_same_arguments( self.mock_get, 11, mock.call(test.IsHttpRequest(), config.id)) self.mock_configuration_update.assert_called_once_with( test.IsHttpRequest(), config.id, config_param_mgr.to_json()) if django.VERSION >= (1, 9): url = settings.TESTSERVER + url self.assertRedirectsNoFollow(res, url) @test.create_mocks({api.trove: ('configuration_instances', 'configuration_update',), config_param_manager: ('get',)}) def test_values_tab_apply_action_exception(self): # NOTE(zhaochao) Please refer to the comment at the beginning of the # 'test_values_tab_apply_action' about not using copy.deepcopy() for # details. original_config = self.database_configurations.first() from troveclient.v1 import configurations config = configurations.Configuration( configurations.Configurations(None), original_config.to_dict()) # Making sure the newly constructed config object is the same as # the original one. self.assertEqual(config, original_config) # setup the configuration parameter manager config_param_mgr = config_param_manager.ConfigParamManager( config.id) config_param_mgr.configuration = config config_param_mgr.original_configuration_values = \ dict.copy(config.values) self.mock_get.return_value = config_param_mgr self.mock_configuration_update.side_effect = self.exceptions.trove details_url = self._get_url_with_arg(DETAIL_URL, config.id) url = details_url + '?tab=configuration_details__value' self._test_create_altered_config_params(config, url) # apply changes res = self.client.post(url, {'action': u"values__apply_changes"}) self.assert_mock_multiple_calls_with_same_arguments( self.mock_get, 11, mock.call(test.IsHttpRequest(), config.id)) self.mock_configuration_update.assert_called_once_with( test.IsHttpRequest(), config.id, config_param_mgr.to_json()) if django.VERSION >= (1, 9): url = settings.TESTSERVER + url self.assertRedirectsNoFollow(res, url) self.assertEqual(res.status_code, 302) def _test_create_altered_config_params(self, config, url): # determine the number of configuration group parameters in the list res = self.client.get(url) table_data = res.context['table'].data number_params = len(table_data) config_param = table_data[0] # delete the first parameter action_string = u"values__delete__%s" % config_param.name form_data = {'action': action_string} res = self.client.post(url, form_data) self.assertRedirectsNoFollow(res, url) # verify the test number of parameters is reduced by 1 res = self.client.get(url) table_data = res.context['table'].data new_number_params = len(table_data) self.assertEqual((number_params - 1), new_number_params) @test.create_mocks({api.trove: ('configuration_instances',), config_param_manager: ('get',)}) def test_instances_tab(self): try: config = self.database_configurations.first() # setup the configuration parameter manager config_param_mgr = config_param_manager.ConfigParamManager( config.id) config_param_mgr.configuration = config config_param_mgr.original_configuration_values = \ dict.copy(config.values) self.mock_get.return_value = config_param_mgr self.mock_configuration_instances.return_value = ( self.configuration_instances.list()) details_url = self._get_url_with_arg(DETAIL_URL, config.id) url = details_url + '?tab=configuration_details__instance' res = self.client.get(url) self.assert_mock_multiple_calls_with_same_arguments( self.mock_get, 2, mock.call(test.IsHttpRequest(), config.id)) self.mock_configuration_instances.assert_called_once_with( test.IsHttpRequest(), config.id) table_data = res.context['instances_table'].data self.assertItemsEqual( self.configuration_instances.list(), table_data) self.assertTemplateUsed( res, 'project/database_configurations/details.html') finally: config_param_manager.delete(config.id) @test.create_mocks({api.trove: ('configuration_instances',), config_param_manager: ('get',)}) def test_instances_tab_exception(self): try: config = self.database_configurations.first() # setup the configuration parameter manager config_param_mgr = config_param_manager.ConfigParamManager( config.id) config_param_mgr.configuration = config config_param_mgr.original_configuration_values = \ dict.copy(config.values) self.mock_get.return_value = config_param_mgr self.mock_configuration_instances.side_effect = ( self.exceptions.trove) details_url = self._get_url_with_arg(DETAIL_URL, config.id) url = details_url + '?tab=configuration_details__instance' res = self.client.get(url) self.assert_mock_multiple_calls_with_same_arguments( self.mock_get, 2, mock.call(test.IsHttpRequest(), config.id)) self.mock_configuration_instances.assert_called_once_with( test.IsHttpRequest(), config.id) table_data = res.context['instances_table'].data self.assertNotEqual(len(self.configuration_instances.list()), len(table_data)) self.assertTemplateUsed( res, 'project/database_configurations/details.html') finally: config_param_manager.delete(config.id) def _get_url_with_arg(self, url, arg): return reverse(url, args=[arg]) def _get_test_datastore(self, datastore_name): for ds in self.datastores.list(): if ds.name == datastore_name: return ds return None def _get_test_datastore_version(self, datastore_id, datastore_version_name): for dsv in self.datastore_versions.list(): if (dsv.datastore == datastore_id and dsv.name == datastore_version_name): return dsv return None