def test_custom_assess_status_check(self): _check = mock.MagicMock() _check.return_value = None, None _conn_check = mock.MagicMock() _conn_check.return_value = True # All is well mrc = mysql_router.MySQLRouterCharm() mrc.check_if_paused = _check mrc.check_interfaces = _check mrc.check_mandatory_config = _check mrc.check_mysql_connection = _conn_check self.assertEqual((None, None), mrc.custom_assess_status_check()) self.assertEqual(3, len(_check.mock_calls)) _conn_check.assert_called_once_with() # First checks fail _check.return_value = "blocked", "for some reason" self.assertEqual(("blocked", "for some reason"), mrc.custom_assess_status_check()) # MySQL connect fails _check.return_value = None, None _conn_check.return_value = False self.assertEqual(("blocked", "Failed to connect to MySQL"), mrc.custom_assess_status_check())
def test_upgrade_charm_lp1971565(self): # test fix for Bug LP#1971565 current_config = { "DEFAULT": { "client_ssl_mode": "NONE" }, "metadata_cache:foo": { "ttl": '5', "auth_cache_ttl": '-1', "auth_cache_refresh_interval": '2', }, "metadata_cache:jujuCluster": { "ttl": '5', }, } fake_config = FakeConfigParser(current_config) self.patch_object(mysql_router.charms_openstack.charm.OpenStackCharm, 'upgrade_charm') self.patch_object(mysql_router.configparser, "ConfigParser", return_value=fake_config) mrc = mysql_router.MySQLRouterCharm() mrc.upgrade_charm() self.assertIn('metadata_cache:foo', fake_config) self.assertIn('unknown_config_option', fake_config['DEFAULT']) self.assertEqual(fake_config['DEFAULT']['unknown_config_option'], 'warning')
def test_check_mysql_connection(self): self.patch_object(mysql_router.mysql, "MySQL8Helper") _helper = mock.MagicMock() _json_pass = '******' _pass = "******" _user = "******" _addr = "127.0.0.1" _port = 3316 _connect_timeout = 30 self.endpoint_from_flag.return_value = self.db_router self.db_router.password.return_value = _json_pass self.patch_object(mysql_router.mysql.MySQLdb, "_exceptions") self._exceptions.OperationalError = Exception _helper = mock.MagicMock() mrc = mysql_router.MySQLRouterCharm() mrc.options.base_port = _port mrc.get_db_helper = mock.MagicMock() mrc.get_db_helper.return_value = _helper # Connects self.assertTrue(mrc.check_mysql_connection()) _helper.connect.assert_called_once_with( _user, _pass, _addr, port=_port, connect_timeout=_connect_timeout) # Fails _helper.reset_mock() _helper.connect.side_effect = self._exceptions.OperationalError self.assertFalse(mrc.check_mysql_connection()) _helper.connect.assert_called_once_with( _user, _pass, _addr, port=_port, connect_timeout=_connect_timeout)
def test_cluster_address(self): _json_addr = '"10.10.10.50"' _addr = "10.10.10.50" self.endpoint_from_flag.return_value = self.db_router self.db_router.db_host.return_value = _json_addr mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.cluster_address, _addr)
def test_db_router_password(self): _json_pass = '******' _pass = "******" self.endpoint_from_flag.return_value = self.db_router self.db_router.password.return_value = _json_pass mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.db_router_password, _pass)
def test_install(self): self.patch_object(mysql_router.charms_openstack.charm.OpenStackCharm, "install", "super_install") _name = "keystone-mysql-router" self.patch_object(mysql_router.ch_core.templating, "render") self.os.path.exists.return_value = False self.group_exists.return_value = False self.user_exists.return_value = False mrc = mysql_router.MySQLRouterCharm() mrc.configure_source = mock.MagicMock() mrc.name = _name mrc.install() self.super_install.assert_called_once() mrc.configure_source.assert_called_once() self.add_group.assert_called_once_with("mysql", system_group=True) self.adduser.assert_called_once_with("mysql", home_dir="/var/lib/mysql", primary_group="mysql", shell="/usr/sbin/nologin", system_user=True) self.mkdir.assert_called_once_with("/var/lib/mysql", group="mysql", owner="mysql", perms=0o755) self.assertEqual(self.render.call_count, 2) self.subprocess.check_output.assert_called_once_with( ['systemctl', 'enable', _name], stderr=self.subprocess.STDOUT)
def test_update_config_parameters_missing_heading(self): # test fix for Bug LP#1927981 current_config = {"DEFAULT": {"client_ssl_mode": "NONE"}} fake_config = FakeConfigParser(current_config) self.patch_object(mysql_router.configparser, "ConfigParser", return_value=fake_config) # metadata_cache:jujuCluster didn't exist in the previous config so the # header needs to be created (c.f. BUG LP#1927981) _params = { "DEFAULT": { "client_ssl_mode": "PREFERRED" }, "metadata_cache:jujuCluster": { "thing": "a-thing" }, } mrc = mysql_router.MySQLRouterCharm() # should not throw a key error. mrc.update_config_parameters(_params) self.assertIn('metadata_cache:jujuCluster', fake_config) self.assertEqual(fake_config['metadata_cache:jujuCluster'], {"thing": "a-thing"})
def test_upgrade_charm_lp1927981(self): # test fix for Bug LP#1927981 current_config = { "DEFAULT": { "client_ssl_mode": "NONE" }, "metadata_cache:foo": { "ttl": '5', "auth_cache_ttl": '-1', "auth_cache_refresh_interval": '2', }, "metadata_cache:jujuCluster": { "ttl": '5', }, } fake_config = FakeConfigParser(current_config) self.patch_object(mysql_router.charms_openstack.charm.OpenStackCharm, 'upgrade_charm') self.patch_object(mysql_router.configparser, "ConfigParser", return_value=fake_config) mrc = mysql_router.MySQLRouterCharm() # should not throw a key error. mrc.upgrade_charm() self.assertIn('metadata_cache:foo', fake_config) self.assertNotIn('metadata_cache:.jujuCluster', fake_config)
def test_restart_mysqlrouter(self): _name = "keystone-mysql-router" mrc = mysql_router.MySQLRouterCharm() mrc.name = _name self.patch_object(mysql_router.ch_core.host, "service_restart") mrc.restart_mysqlrouter() self.service_restart.assert_called_once_with(_name)
def test_proxy_db_and_user_requests_no_prefix(self): mrc = mysql_router.MySQLRouterCharm() mrc.proxy_db_and_user_requests(self.keystone_shared_db, self.db_router) self.db_router.configure_proxy_db.assert_called_once_with( 'keystone', 'keystone', self.keystone_unit_ip, prefix=mrc._unprefixed)
def test_proxy_db_and_user_responses_unprefixed(self): _wait_time = 90 _json_wait_time = "90" _json_pass = '******' _pass = json.loads(_json_pass) _json_ca = '"Certificate Authority"' _ca = json.loads(_json_ca) _local_unit = "kmr/5" _port = 3316 self.db_router.password.return_value = _json_pass self.local_unit.return_value = _local_unit mrc = mysql_router.MySQLRouterCharm() mrc.options.base_port = _port self.db_router.get_prefixes.return_value = [ mrc._unprefixed, mrc.db_prefix ] # Allowed Units, wait_time and ssl_ca unset self.db_router.wait_timeout.return_value = None self.db_router.ssl_ca.return_value = None self.db_router.allowed_units.return_value = '""' mrc.proxy_db_and_user_responses(self.db_router, self.keystone_shared_db) self.keystone_shared_db.set_db_connection_info.assert_called_once_with( self.keystone_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=None, prefix=None, wait_timeout=None, db_port=_port, ssl_ca=None) # Allowed Units and wait time set correctly self.db_router.wait_timeout.return_value = _json_wait_time self.db_router.ssl_ca.return_value = _json_ca self.keystone_shared_db.set_db_connection_info.reset_mock() self.db_router.allowed_units.return_value = json.dumps(_local_unit) mrc.proxy_db_and_user_responses(self.db_router, self.keystone_shared_db) self.keystone_shared_db.set_db_connection_info.assert_called_once_with( self.keystone_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=self.keystone_unit_name, prefix=None, wait_timeout=_wait_time, db_port=_port, ssl_ca=_ca) # Confirm msyqlrouter credentials are not sent over the shared-db # relation for call in self.keystone_shared_db.set_db_connection_info.mock_calls: self.assertNotEqual(mrc.db_prefix, call.kwargs.get("prefix"))
def test_config_changed(self): _config_data = { "ttl": '5', "auth_cache_ttl": '10', "auth_cache_refresh_interval": '7', "max_connections": '1000', "debug": False, } def _fake_config(key=None): return _config_data[key] if key else _config_data self.patch_object(mysql_router.ch_core.hookenv, "config") self.patch_object(mysql_router.os.path, "exists") self.patch_object(mysql_router.ch_core.host, "restart_on_change") self.config.side_effect = _fake_config self.endpoint_from_flag.return_value = self.db_router _mock_update_config_parameters = mock.MagicMock() mrc = mysql_router.MySQLRouterCharm() mrc.name = 'foobar' mrc.update_config_parameters = _mock_update_config_parameters _metadata_config = _config_data.copy() _metadata_config.pop('max_connections') _metadata_config.pop('debug') _params = { mysql_router.METADATA_CACHE_SECTION: _metadata_config, mysql_router.DEFAULT_SECTION: { 'client_ssl_mode': "PASSTHROUGH", 'max_connections': _config_data['max_connections'], 'pid_file': '/run/mysql/mysqlrouter-foobar.pid', 'unknown_config_option': 'warning', }, mysql_router.LOGGING_SECTION: { 'level': 'INFO', }, } # Not bootstrapped yet self.exists.return_value = False mrc.config_changed() _mock_update_config_parameters.assert_not_called() # With TLS PASSTHROUGH self.db_router.ssl_ca.return_value = '"CACERT"' self.exists.return_value = True mrc.config_changed() _mock_update_config_parameters.assert_called_once_with(_params) # With TLS PREFERRED self.db_router.ssl_ca.return_value = None _params["DEFAULT"]["client_ssl_mode"] = "PREFERRED" self.exists.return_value = True _mock_update_config_parameters.reset_mock() mrc.config_changed() _mock_update_config_parameters.assert_called_once_with(_params)
def test_start_mysqlrouter(self): self.patch_object(mysql_router.ch_core.host, "service_start") _name = "keystone-mysql-router" mrc = mysql_router.MySQLRouterCharm() mrc.name = _name mrc.start_mysqlrouter() self.service_start.assert_called_once_with(_name) self.set_flag.assert_called_once_with( mysql_router.MYSQL_ROUTER_STARTED)
def test_proxy_db_and_user_responses_no_data(self): self.db_router.password.return_value = None mrc = mysql_router.MySQLRouterCharm() self.db_router.get_prefixes.return_value = [ mrc._unprefixed, mrc.db_prefix ] mrc.proxy_db_and_user_responses(self.db_router, self.keystone_shared_db) self.keystone_shared_db.set_db_connection_info.assert_not_called()
def test_update_config_parameters_regex(self): # test fix for Bug LP#1927981 current_config = { "DEFAULT": { "client_ssl_mode": "NONE" }, "metadata_cache:foo": { "ttl": '5', "auth_cache_ttl": '-1', "auth_cache_refresh_interval": '2', }, "routing:foo_x_rw": { "test": 'yes', }, "routing:foo_rw": { "test": 'no', } } fake_config = FakeConfigParser(current_config) self.patch_object(mysql_router.configparser, "ConfigParser", return_value=fake_config) # metadata_cache:jujuCluster didn't exist in the previous config so the # header needs to be created (c.f. BUG LP#1927981) _params = { "DEFAULT": { "client_ssl_mode": "PREFERRED" }, mysql_router.METADATA_CACHE_SECTION: { "thing": "a-thing" }, mysql_router.ROUTING_RW_SECTION: { "test": True, }, mysql_router.ROUTING_X_RW_SECTION: { "test": False, } } mrc = mysql_router.MySQLRouterCharm() # should not throw a key error. mrc.update_config_parameters(_params) self.assertIn('metadata_cache:foo', fake_config) self.assertNotIn(mysql_router.METADATA_CACHE_SECTION, fake_config) self.assertEqual( fake_config['metadata_cache:foo'], { "thing": "a-thing", "ttl": '5', "auth_cache_ttl": '-1', "auth_cache_refresh_interval": '2' }) self.assertEqual(fake_config['routing:foo_x_rw'], {"test": False}) self.assertEqual(fake_config['routing:foo_rw'], {"test": True})
def test_get_db_helper(self): self.patch_object(mysql_router.mysql, "MySQL8Helper") _helper = mock.MagicMock() _json_addr = '"10.10.10.70"' _json_pass = '******' self.endpoint_from_flag.return_value = self.db_router self.db_router.db_host.return_value = _json_addr self.db_router.password.return_value = _json_pass mrc = mysql_router.MySQLRouterCharm() self.MySQL8Helper.return_value = _helper self.assertEqual(_helper, mrc.get_db_helper()) self.MySQL8Helper.assert_called_once()
def test_custom_restart_function(self): self.patch_object(mysql_router.ch_core.host, "service_stop") self.patch_object(mysql_router.ch_core.host, "service_start") self.service_name = "mysql-router" _mock_check_mysql_connection = mock.MagicMock() mrc = mysql_router.MySQLRouterCharm() mrc.check_mysql_connection = _mock_check_mysql_connection mrc.custom_restart_function(self.service_name) self.service_stop.assert_called_once_with(self.service_name) self.service_start.assert_called_once_with(self.service_name) _mock_check_mysql_connection.assert_called_once()
def test_proxy_db_and_user_requests_prefixed(self): mrc = mysql_router.MySQLRouterCharm() mrc.proxy_db_and_user_requests(self.nova_shared_db, self.db_router) _calls = [ mock.call('nova', 'nova', self.nova_unit_ip, prefix="nova"), mock.call('nova_api', 'nova', self.nova_unit_ip, prefix="novaapi"), mock.call('nova_cell0', 'nova', self.nova_unit_ip, prefix="novacell0") ] self.db_router.configure_proxy_db.assert_has_calls(_calls, any_order=True)
def test_states_to_check(self): self.patch_object(mysql_router.charms_openstack.charm.OpenStackCharm, "states_to_check", "super_states") self.super_states.return_value = {} _required_rels = ["shared-db", "db-router"] mrc = mysql_router.MySQLRouterCharm() _results = mrc.states_to_check(_required_rels) _states_to_check = [x[0] for x in _results["charm"]] self.super_states.assert_called_once_with(_required_rels) self.assertTrue( mysql_router.MYSQL_ROUTER_BOOTSTRAPPED in _states_to_check) self.assertTrue(mysql_router.MYSQL_ROUTER_STARTED in _states_to_check) self.assertTrue( mysql_router.DB_ROUTER_PROXY_AVAILABLE in _states_to_check)
def test_update_config_parameters(self): self.patch_object(mysql_router.configparser, "ConfigParser") _mock_config_parser = mock.MagicMock() self.ConfigParser.return_value = _mock_config_parser _params = {"DEFAULT": {"client_ssl_mode": "PREFERRED"}} mrc = mysql_router.MySQLRouterCharm() mrc.update_config_parameters(_params) _mock_config_parser.read.assert_called_once() _mock_config_parser.__getitem__.assert_called_once_with('DEFAULT') _mock_config_parser.__getitem__().__setitem__.assert_called_once_with( 'client_ssl_mode', 'PREFERRED') _mock_config_parser.write.assert_called_once_with( self.mock_open()().__enter__())
def test_db_router_address(self): _addr = "10.10.10.30" self.get_relation_ip.return_value = _addr mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.db_router_address, _addr)
def test_shared_db_address(self): mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.shared_db_address, "127.0.0.1")
def test_proxy_db_and_user_responses_prefixed(self): _wait_time = 90 _json_wait_time = "90" _json_pass = '******' _pass = json.loads(_json_pass) _json_ca = '"Certificate Authority"' _ca = json.loads(_json_ca) _local_unit = "nmr/5" _nova = "nova" _novaapi = "novaapi" _novacell0 = "novacell0" _port = 3316 self.db_router.password.return_value = _json_pass self.local_unit.return_value = _local_unit mrc = mysql_router.MySQLRouterCharm() mrc.options.base_port = _port self.db_router.get_prefixes.return_value = [ mrc.db_prefix, _nova, _novaapi, _novacell0 ] # Allowed Units, wait time and CA unset self.db_router.wait_timeout.return_value = None self.db_router.ssl_ca.return_value = None self.db_router.allowed_units.return_value = '""' mrc.proxy_db_and_user_responses(self.db_router, self.nova_shared_db) _calls = [ mock.call(self.nova_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=None, prefix=_nova, wait_timeout=None, db_port=_port, ssl_ca=None), mock.call(self.nova_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=None, prefix=_novaapi, wait_timeout=None, db_port=_port, ssl_ca=None), mock.call(self.nova_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=None, prefix=_novacell0, wait_timeout=None, db_port=_port, ssl_ca=None), ] self.nova_shared_db.set_db_connection_info.assert_has_calls( _calls, any_order=True) # Allowed Units and wait time set correctly self.db_router.wait_timeout.return_value = _json_wait_time self.db_router.ssl_ca.return_value = _json_ca self.nova_shared_db.set_db_connection_info.reset_mock() self.db_router.allowed_units.return_value = json.dumps(_local_unit) mrc.proxy_db_and_user_responses(self.db_router, self.nova_shared_db) _calls = [ mock.call(self.nova_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=self.nova_unit_name, prefix=_nova, wait_timeout=_wait_time, db_port=_port, ssl_ca=_ca), mock.call(self.nova_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=self.nova_unit_name, prefix=_novaapi, wait_timeout=_wait_time, db_port=_port, ssl_ca=_ca), mock.call(self.nova_shared_db.relation_id, mrc.shared_db_address, _pass, allowed_units=self.nova_unit_name, prefix=_novacell0, wait_timeout=_wait_time, db_port=_port, ssl_ca=_ca), ] self.nova_shared_db.set_db_connection_info.assert_has_calls( _calls, any_order=True) # Confirm msyqlrouter credentials are not sent over the shared-db # relation for call in self.nova_shared_db.set_db_connection_info.mock_calls: self.assertNotEqual(mrc.db_prefix, call.kwargs.get("prefix"))
def test_mysqlrouter_working_dir(self): mrc = mysql_router.MySQLRouterCharm() _name = "keystone-mysql-router" mrc.name = _name self.assertEqual(mrc.mysqlrouter_working_dir, "/var/lib/mysql/{}".format(_name))
def test_mysqlrouter_home_dir(self): mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.mysqlrouter_home_dir, "/var/lib/mysql")
def test_mysqlrouter_user(self): mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.mysqlrouter_user, "mysql")
def test_db_router_endpoint(self): self.endpoint_from_flag.return_value = self.db_router mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.db_router_endpoint, self.db_router)
def test_db_prefix(self): mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.db_prefix, "mysqlrouter")
def test_bootstrap_mysqlrouter(self): _json_addr = '"10.10.10.60"' _json_pass = '******' _pass = json.loads(_json_pass) _addr = json.loads(_json_addr) _user = "******" _port = "3006" self.patch_object(mysql_router.reactive.flags, "is_flag_set") self.endpoint_from_flag.return_value = self.db_router self.db_router.password.return_value = _json_pass self.db_router.db_host.return_value = _json_addr self.is_flag_set.return_value = False mrc = mysql_router.MySQLRouterCharm() mrc.options.system_user = _user mrc.options.base_port = _port # Successful < 8.0.22 self.cmp_pkgrevno.return_value = -1 mrc.bootstrap_mysqlrouter() self.subprocess.check_output.assert_called_once_with( [ mrc.mysqlrouter_bin, "--user", _user, "--name", mrc.name, "--bootstrap", "{}:{}@{}".format( mrc.db_router_user, _pass, _addr), "--directory", mrc.mysqlrouter_working_dir, "--conf-use-sockets", "--conf-bind-address", mrc.shared_db_address, "--report-host", mrc.db_router_address, "--conf-base-port", _port ], stderr=self.stdout) self.set_flag.assert_has_calls([ mock.call(mysql_router.MYSQL_ROUTER_BOOTSTRAP_ATTEMPTED), mock.call(mysql_router.MYSQL_ROUTER_BOOTSTRAPPED) ]) self.clear_flag.assert_called_once_with( mysql_router.MYSQL_ROUTER_BOOTSTRAP_ATTEMPTED) # Successful >= 8.0.22 self.subprocess.reset_mock() self.set_flag.reset_mock() self.clear_flag.reset_mock() self.cmp_pkgrevno.return_value = 1 mrc.bootstrap_mysqlrouter() self.subprocess.check_output.assert_called_once_with( [ mrc.mysqlrouter_bin, "--user", _user, "--name", mrc.name, "--bootstrap", "{}:{}@{}".format( mrc.db_router_user, _pass, _addr), "--directory", mrc.mysqlrouter_working_dir, "--conf-use-sockets", "--conf-bind-address", mrc.shared_db_address, "--report-host", mrc.db_router_address, "--conf-base-port", _port, "--disable-rest" ], stderr=self.stdout) self.set_flag.assert_has_calls([ mock.call(mysql_router.MYSQL_ROUTER_BOOTSTRAP_ATTEMPTED), mock.call(mysql_router.MYSQL_ROUTER_BOOTSTRAPPED) ]) self.clear_flag.assert_called_once_with( mysql_router.MYSQL_ROUTER_BOOTSTRAP_ATTEMPTED) # First attempt fail self.subprocess.reset_mock() self.set_flag.reset_mock() self.subprocess.CalledProcessError = FakeException self.subprocess.check_output.side_effect = ( self.subprocess.CalledProcessError) mrc.bootstrap_mysqlrouter() self.set_flag.assert_called_once_with( mysql_router.MYSQL_ROUTER_BOOTSTRAP_ATTEMPTED) # Bail self.subprocess.reset_mock() self.set_flag.reset_mock() self.is_flag_set.return_value = True mrc.bootstrap_mysqlrouter() self.subprocess.check_output.assert_not_called() # Second attempt success self.subprocess.reset_mock() self.set_flag.reset_mock() self.clear_flag.reset_mock() self.cmp_pkgrevno.return_value = 1 self.is_flag_set.side_effect = [False, True] self.subprocess.check_output.side_effect = None mrc.bootstrap_mysqlrouter() self.subprocess.check_output.assert_called_once_with( [ mrc.mysqlrouter_bin, "--user", _user, "--name", mrc.name, "--bootstrap", "{}:{}@{}".format( mrc.db_router_user, _pass, _addr), "--directory", mrc.mysqlrouter_working_dir, "--conf-use-sockets", "--conf-bind-address", mrc.shared_db_address, "--report-host", mrc.db_router_address, "--conf-base-port", _port, "--disable-rest", "--force" ], stderr=self.stdout) self.set_flag.assert_has_calls([ mock.call(mysql_router.MYSQL_ROUTER_BOOTSTRAP_ATTEMPTED), mock.call(mysql_router.MYSQL_ROUTER_BOOTSTRAPPED) ]) self.clear_flag.assert_called_once_with( mysql_router.MYSQL_ROUTER_BOOTSTRAP_ATTEMPTED)
def test_mysqlrouter_bin(self): mrc = mysql_router.MySQLRouterCharm() self.assertEqual(mrc.mysqlrouter_bin, "/usr/bin/mysqlrouter")