class MongoDBInstanceTestCase(TestCase): """ Test cases for MongoDBInstanceMixin and OpenEdXDatabaseMixin """ def setUp(self): super().setUp() self.instance = None def tearDown(self): if self.instance: with patch( 'instance.tests.models.factories.openedx_instance.OpenEdXInstance._write_metadata_to_consul', return_value=(1, True) ): self.instance.deprovision_mongo() super().tearDown() def check_mongo(self): """ Check that the instance mongo user has access to the external mongo database """ mongo = pymongo.MongoClient(settings.DEFAULT_INSTANCE_MONGO_URL) for database in self.instance.mongo_database_names: self.assertTrue(mongo[database].authenticate(self.instance.mongo_user, self.instance.mongo_pass)) def check_mongo_vars_not_set(self, appserver): """ Check that the given OpenEdXAppServer does not point to a mongo database """ for var in ('EDXAPP_MONGO_USER', 'EDXAPP_MONGO_PASSWORD' 'EDXAPP_MONGO_HOSTS', 'EDXAPP_MONGO_PORT', 'EDXAPP_MONGO_DB_NAME', 'FORUM_MONGO_USER', 'FORUM_MONGO_PASSWORD', 'FORUM_MONGO_HOSTS', 'FORUM_MONGO_PORT', 'FORUM_MONGO_DATABASE'): self.assertNotIn(var, appserver.configuration_settings) def check_mongo_vars_set(self, appserver, expected_hosts, expected_replica_set=None): """ Check that the given OpenEdXAppServer is using the expected mongo settings. """ ansible_vars = appserver.configuration_settings ansible_vars_data = yaml.safe_load(ansible_vars) self.assertIn('EDXAPP_MONGO_USER: {0}'.format(self.instance.mongo_user), ansible_vars) self.assertIn('EDXAPP_MONGO_PASSWORD: {0}'.format(self.instance.mongo_pass), ansible_vars) self.assertIn('EDXAPP_MONGO_PORT: {0}'.format(MONGODB_SERVER_DEFAULT_PORT), ansible_vars) self.assertIn('EDXAPP_MONGO_DB_NAME: {0}'.format(self.instance.mongo_database_name), ansible_vars) if isinstance(ansible_vars_data['EDXAPP_MONGO_HOSTS'], list): rendered_edxapp_mongo_hosts = ansible_vars_data['EDXAPP_MONGO_HOSTS'] else: rendered_edxapp_mongo_hosts = ansible_vars_data['EDXAPP_MONGO_HOSTS'].split(',') self.assertSetEqual(set(expected_hosts), set(rendered_edxapp_mongo_hosts)) if expected_replica_set: self.assertIn('EDXAPP_MONGO_REPLICA_SET: {0}'.format(expected_replica_set), ansible_vars) self.assertIn('FORUM_MONGO_REPLICA_SET: {0}'.format(expected_replica_set), ansible_vars) self.assertIn('FORUM_MONGO_URL', ansible_vars_data) self.assertSetEqual(set(ansible_vars_data['FORUM_MONGO_HOSTS']), set(expected_hosts)) else: self.assertNotIn('EDXAPP_MONGO_REPLICA_SET', ansible_vars) self.assertNotIn('FORUM_MONGO_REPLICA_SET', ansible_vars) self.assertEqual(ansible_vars_data['FORUM_MONGO_HOSTS'], expected_hosts) self.assertIn('FORUM_MONGO_USER: {0}'.format(self.instance.mongo_user), ansible_vars) self.assertIn('FORUM_MONGO_PASSWORD: {0}'.format(self.instance.mongo_pass), ansible_vars) self.assertIn('FORUM_MONGO_PORT: {0}'.format(MONGODB_SERVER_DEFAULT_PORT), ansible_vars) self.assertIn('FORUM_MONGO_DATABASE: {0}'.format(self.instance.forum_database_name), ansible_vars) def test_provision_mongo(self, mock_consul): """ Provision mongo databases """ self.instance = OpenEdXInstanceFactory() self.instance.provision_mongo() self.check_mongo() def test_provision_mongo_again(self, mock_consul): """ Only create the databases once """ self.instance = OpenEdXInstanceFactory() self.instance.provision_mongo() self.assertIs(self.instance.mongo_provisioned, True) mongo_user = self.instance.mongo_user mongo_pass = self.instance.mongo_pass self.instance.provision_mongo() self.assertEqual(self.instance.mongo_user, mongo_user) self.assertEqual(self.instance.mongo_pass, mongo_pass) self.check_mongo() def test_provision_mongo_no_mongodb_server(self, mock_consul): """ Don't provision a mongo database if instance has no MongoDB server """ mongo = pymongo.MongoClient(settings.DEFAULT_INSTANCE_MONGO_URL) self.instance = OpenEdXInstanceFactory() self.instance.mongodb_server = None self.instance.save() self.instance.provision_mongo() databases = mongo.database_names() for database in self.instance.mongo_database_names: self.assertNotIn(database, databases) @override_settings(DEFAULT_INSTANCE_MONGO_URL='mongodb://*****:*****@mongo.opencraft.com') def test_ansible_settings_mongo(self, mock_consul): """ Add mongo ansible vars if instance has a MongoDB server """ # Delete MongoDBServer object created during the migrations to allow the settings override # to take effect. MongoDBServer.objects.all().delete() self.instance = OpenEdXInstanceFactory() appserver = make_test_appserver(self.instance) self.check_mongo_vars_set(appserver, expected_hosts=['mongo.opencraft.com']) @override_settings( DEFAULT_INSTANCE_MONGO_URL=None, DEFAULT_MONGO_REPLICA_SET_NAME="test_name", DEFAULT_MONGO_REPLICA_SET_USER="******", DEFAULT_MONGO_REPLICA_SET_PASSWORD="******", DEFAULT_MONGO_REPLICA_SET_PRIMARY="test.opencraft.hosting", DEFAULT_MONGO_REPLICA_SET_HOSTS="test.opencraft.hosting,test1.opencraft.hosting,test2.opencraft.hosting" ) @ddt.data( ('open-release/ficus', 'open-release/ficus'), ('open-release/ficus', 'opencraft-release/ficus'), ('open-release/ginkgo', 'open-release/ginkgo'), ) @ddt.unpack def test_ansible_settings_no_replica_set(self, openedx_release, configuration_version, mock_consul): """ Prior to Hawthorn, edx configuration does not support MongoDB replica sets, and the mongo hosts must be a single host, provided as a list of strings. """ # Delete MongoDBServer object created during the migrations to allow the settings override # to take effect. MongoDBServer.objects.all().delete() self.instance = OpenEdXInstanceFactory(openedx_release=openedx_release, configuration_version=configuration_version) appserver = make_test_appserver(self.instance) self.check_mongo_vars_set(appserver, expected_hosts=["test.opencraft.hosting"]) @override_settings( DEFAULT_INSTANCE_MONGO_URL=None, DEFAULT_MONGO_REPLICA_SET_NAME="test_name", DEFAULT_MONGO_REPLICA_SET_USER="******", DEFAULT_MONGO_REPLICA_SET_PASSWORD="******", DEFAULT_MONGO_REPLICA_SET_PRIMARY="test.opencraft.hosting", DEFAULT_MONGO_REPLICA_SET_HOSTS="test.opencraft.hosting,test1.opencraft.hosting,test2.opencraft.hosting" ) @ddt.data( ('open-release/ginkgo', 'opencraft-release/ginkgo'), (settings.OPENEDX_RELEASE_STABLE_REF, settings.STABLE_CONFIGURATION_VERSION), (settings.DEFAULT_OPENEDX_RELEASE, settings.DEFAULT_CONFIGURATION_VERSION), ) @ddt.unpack def test_ansible_settings_use_replica_set(self, openedx_release, configuration_version, mock_consul): """ Add mongo ansible vars if instance has a MongoDB replica set Also, the mongo hosts are provied as a comma-separated string. """ # Delete MongoDBServer object created during the migrations to allow the settings override # to take effect. MongoDBServer.objects.all().delete() self.instance = OpenEdXInstanceFactory(openedx_release=openedx_release, configuration_version=configuration_version) appserver = make_test_appserver(self.instance) self.check_mongo_vars_set(appserver, expected_hosts=['test.opencraft.hosting', 'test1.opencraft.hosting', 'test2.opencraft.hosting'], expected_replica_set='test_name') def test_ansible_settings_no_mongo_server(self, mock_consul): """ Don't add mongo ansible vars if instance has no MongoDB server """ self.instance = OpenEdXInstanceFactory() self.instance.mongodb_server = None self.instance.save() appserver = make_test_appserver(self.instance) self.check_mongo_vars_not_set(appserver) @override_settings( DEFAULT_INSTANCE_MONGO_URL=None, DEFAULT_MONGO_REPLICA_SET_NAME="test_name", DEFAULT_MONGO_REPLICA_SET_USER="******", DEFAULT_MONGO_REPLICA_SET_PASSWORD="******", DEFAULT_MONGO_REPLICA_SET_PRIMARY="test.opencraft.hosting", DEFAULT_MONGO_REPLICA_SET_HOSTS="test.opencraft.hosting,test1.opencraft.hosting,test2.opencraft.hosting" ) def test__get_main_database_url(self, mock_consul): """ Main database url should be extracted from primary replica set MongoDBServer """ self.instance = OpenEdXInstanceFactory() self.assertEqual( self.instance._get_main_database_url(), "mongodb://*****:*****@test.opencraft.hosting" ) @patch('instance.models.mixins.database.MongoDBInstanceMixin._get_main_database_url') @patch('instance.models.mixins.database.pymongo.MongoClient', autospec=True) def test_deprovision_mongo(self, mock_mongo_client_cls, mock_get_main_db_url, mock_consul): """ Test deprovision_mongo calls drop_database. """ self.instance = OpenEdXInstanceFactory() self.instance.mongo_provisioned = True self.instance.deprovision_mongo() for database in self.instance.mongo_database_names: mock_mongo_client_cls().drop_database.assert_any_call(database) @patch('instance.models.mixins.database.pymongo.MongoClient', autospec=True) @patch('instance.models.mixins.database.MongoDBInstanceMixin._get_main_database_url') @patch('instance.models.mixins.database.pymongo.MongoClient.drop_database', side_effect=PyMongoError()) def test_ignore_errors_deprovision_mongo(self, mock_mongo_client_cls, *mock_methods): """ Test mongo is set as deprovision when ignoring errors. """ self.instance = OpenEdXInstanceFactory() self.instance.mongo_provisioned = True self.instance.deprovision_mongo(ignore_errors=True) self.assertFalse(self.instance.mongo_provisioned)