950 lines
36 KiB
Python
950 lines
36 KiB
Python
import os
|
|
import mock
|
|
import json
|
|
import unittest
|
|
import sys
|
|
import shutil
|
|
import tempfile
|
|
|
|
from collections import OrderedDict
|
|
|
|
sys.modules['MySQLdb'] = mock.Mock()
|
|
from charmhelpers.contrib.database import mysql # noqa
|
|
|
|
|
|
class MysqlTests(unittest.TestCase):
|
|
def setUp(self):
|
|
super(MysqlTests, self).setUp()
|
|
|
|
def test_connect_host_defined(self):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
with mock.patch.object(mysql, 'log'):
|
|
helper.connect(user='user', password='password', host='1.1.1.1')
|
|
mysql.MySQLdb.connect.assert_called_with(
|
|
passwd='password', host='1.1.1.1', user='user', connect_timeout=30)
|
|
|
|
def test_connect_host_not_defined(self):
|
|
helper = mysql.MySQLHelper('foo', 'bar')
|
|
with mock.patch.object(mysql, 'log'):
|
|
helper.connect(user='user', password='password')
|
|
mysql.MySQLdb.connect.assert_called_with(
|
|
passwd='password', host='localhost', user='user',
|
|
connect_timeout=30)
|
|
|
|
def test_connect_port_defined(self):
|
|
helper = mysql.MySQLHelper('foo', 'bar')
|
|
with mock.patch.object(mysql, 'log'):
|
|
helper.connect(user='user', password='password', port=3316)
|
|
mysql.MySQLdb.connect.assert_called_with(
|
|
passwd='password', host='localhost', user='user', port=3316,
|
|
connect_timeout=30)
|
|
|
|
def test_connect_new_default_timeout(self):
|
|
helper = mysql.MySQLHelper('foo', 'bar', connect_timeout=10)
|
|
with mock.patch.object(mysql, 'log'):
|
|
helper.connect(user='user', password='password', port=3316)
|
|
mysql.MySQLdb.connect.assert_called_with(
|
|
passwd='password', host='localhost', user='user', port=3316,
|
|
connect_timeout=10)
|
|
|
|
def test_connect_new_default_override(self):
|
|
helper = mysql.MySQLHelper('foo', 'bar', connect_timeout=10)
|
|
with mock.patch.object(mysql, 'log'):
|
|
helper.connect(user='user', password='password', port=3316,
|
|
connect_timeout=20)
|
|
mysql.MySQLdb.connect.assert_called_with(
|
|
passwd='password', host='localhost', user='user', port=3316,
|
|
connect_timeout=20)
|
|
|
|
@mock.patch.object(mysql.MySQLHelper, 'normalize_address')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password')
|
|
@mock.patch.object(mysql.MySQLHelper, 'grant_exists')
|
|
@mock.patch.object(mysql, 'relation_get')
|
|
@mock.patch.object(mysql, 'related_units')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_allowed_units(self, mock_log, mock_related_units,
|
|
mock_relation_get,
|
|
mock_grant_exists,
|
|
mock_get_password,
|
|
mock_normalize_address):
|
|
|
|
# echo
|
|
mock_normalize_address.side_effect = lambda addr: addr
|
|
|
|
def mock_rel_get(unit, rid):
|
|
if unit == 'unit/0':
|
|
# Non-prefixed settings
|
|
d = {'private-address': '10.0.0.1',
|
|
'hostname': 'hostA'}
|
|
elif unit == 'unit/1':
|
|
# Containing prefixed settings
|
|
d = {'private-address': '10.0.0.2',
|
|
'dbA_hostname': json.dumps(['10.0.0.2', '2001:db8:1::2'])}
|
|
elif unit == 'unit/2':
|
|
# No hostname
|
|
d = {'private-address': '10.0.0.3'}
|
|
elif unit == 'unit/3':
|
|
# Prefixed hostname
|
|
d = {'private-address': '10.0.0.4',
|
|
'PRE_hostname': json.dumps(['10.0.0.4', '2001:db8:1::4'])}
|
|
return d
|
|
|
|
mock_relation_get.side_effect = mock_rel_get
|
|
mock_related_units.return_value = ['unit/0', 'unit/1', 'unit/2']
|
|
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
units = helper.get_allowed_units('dbA', 'userA')
|
|
|
|
calls = [mock.call('dbA', 'userA', 'hostA'),
|
|
mock.call('dbA', 'userA', '10.0.0.2'),
|
|
mock.call('dbA', 'userA', '2001:db8:1::2'),
|
|
mock.call('dbA', 'userA', '10.0.0.3')]
|
|
|
|
helper.grant_exists.assert_has_calls(calls, any_order=True)
|
|
self.assertEqual(units, set(['unit/0', 'unit/1', 'unit/2']))
|
|
|
|
# With prefix
|
|
calls = [mock.call('dbB', 'userB', 'hostA'),
|
|
mock.call('dbB', 'userB', '10.0.0.2'),
|
|
mock.call('dbB', 'userB', '10.0.0.3'),
|
|
mock.call('dbB', 'userB', '2001:db8:1::4'),
|
|
mock.call('dbB', 'userB', '10.0.0.4')]
|
|
|
|
mock_related_units.return_value = [
|
|
'unit/0', 'unit/1', 'unit/2', 'unit/3']
|
|
units = helper.get_allowed_units('dbB', 'userB', prefix="PRE")
|
|
helper.grant_exists.assert_has_calls(calls, any_order=True)
|
|
self.assertEqual(units, set(['unit/0', 'unit/1', 'unit/2', 'unit/3']))
|
|
|
|
@mock.patch('charmhelpers.contrib.network.ip.log',
|
|
lambda *args, **kwargs: None)
|
|
@mock.patch('charmhelpers.contrib.network.ip.ns_query')
|
|
@mock.patch('charmhelpers.contrib.network.ip.socket')
|
|
@mock.patch.object(mysql, 'unit_get')
|
|
@mock.patch.object(mysql, 'config_get')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_normalize_address(self, mock_log, mock_config_get, mock_unit_get,
|
|
mock_socket, mock_ns_query):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
# prefer-ipv6
|
|
mock_config_get.return_value = False
|
|
# echo
|
|
mock_socket.gethostbyname.side_effect = lambda addr: addr
|
|
|
|
mock_unit_get.return_value = '10.0.0.1'
|
|
out = helper.normalize_address('10.0.0.1')
|
|
self.assertEqual('127.0.0.1', out)
|
|
mock_config_get.assert_called_with('prefer-ipv6')
|
|
|
|
mock_unit_get.return_value = '10.0.0.1'
|
|
out = helper.normalize_address('10.0.0.2')
|
|
self.assertEqual('10.0.0.2', out)
|
|
mock_config_get.assert_called_with('prefer-ipv6')
|
|
|
|
out = helper.normalize_address('2001:db8:1::1')
|
|
self.assertEqual('2001:db8:1::1', out)
|
|
mock_config_get.assert_called_with('prefer-ipv6')
|
|
|
|
mock_socket.gethostbyname.side_effect = Exception
|
|
mock_ns_query.return_value = None
|
|
out = helper.normalize_address('unresolvable')
|
|
self.assertEqual('unresolvable', out)
|
|
mock_config_get.assert_called_with('prefer-ipv6')
|
|
|
|
# prefer-ipv6
|
|
mock_config_get.return_value = True
|
|
mock_socket.gethostbyname.side_effect = 'other'
|
|
out = helper.normalize_address('unresolvable')
|
|
self.assertEqual('unresolvable', out)
|
|
mock_config_get.assert_called_with('prefer-ipv6')
|
|
|
|
def test_passwd_keys(self):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
self.assertEqual(list(helper.passwd_keys(None)), ['mysql.passwd'])
|
|
self.assertEqual(list(helper.passwd_keys('auser')),
|
|
['mysql-auser.passwd', 'auser.passwd'])
|
|
|
|
@mock.patch.object(mysql.MySQLHelper, 'migrate_passwords_to_leader_storage')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password_on_disk')
|
|
@mock.patch.object(mysql, 'leader_get')
|
|
def test_get_mysql_password_no_peer_passwd(self, mock_leader_get,
|
|
mock_get_disk_pw,
|
|
mock_migrate_pw):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
store = {}
|
|
mock_leader_get.side_effect = lambda key: store.get(key)
|
|
mock_get_disk_pw.return_value = "disk-passwd"
|
|
self.assertEqual(helper.get_mysql_password(), "disk-passwd")
|
|
self.assertTrue(mock_migrate_pw.called)
|
|
|
|
@mock.patch.object(mysql.MySQLHelper, 'migrate_passwords_to_leader_storage')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password_on_disk')
|
|
@mock.patch.object(mysql, 'leader_get')
|
|
def test_get_mysql_password_peer_passwd(self, mock_leader_get,
|
|
mock_get_disk_pw, mock_migrate_pw):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
store = {'mysql-userA.passwd': 'passwdA'}
|
|
mock_leader_get.side_effect = lambda key: store.get(key)
|
|
mock_get_disk_pw.return_value = "disk-passwd"
|
|
self.assertEqual(helper.get_mysql_password(username='userA'),
|
|
"passwdA")
|
|
self.assertTrue(mock_migrate_pw.called)
|
|
|
|
@mock.patch.object(mysql.MySQLHelper, 'migrate_passwords_to_leader_storage')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password_on_disk')
|
|
@mock.patch.object(mysql, 'leader_get')
|
|
def test_get_mysql_password_peer_passwd_legacy(self, mock_leader_get,
|
|
mock_get_disk_pw,
|
|
mock_migrate_pw):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
store = {'userA.passwd': 'passwdA'}
|
|
mock_leader_get.side_effect = lambda key: store.get(key)
|
|
mock_get_disk_pw.return_value = "disk-passwd"
|
|
self.assertEqual(helper.get_mysql_password(username='userA'),
|
|
"passwdA")
|
|
self.assertTrue(mock_migrate_pw.called)
|
|
|
|
@mock.patch.object(mysql.MySQLHelper, 'migrate_passwords_to_leader_storage')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password_on_disk')
|
|
@mock.patch.object(mysql, 'leader_get')
|
|
def test_get_mysql_password_peer_passwd_all(self, mock_leader_get,
|
|
mock_get_disk_pw,
|
|
mock_migrate_pw):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
# Add * so we can identify that the new format key takes precedence
|
|
# if found.
|
|
store = {'mysql-userA.passwd': 'passwdA',
|
|
'userA.passwd': 'passwdA*'}
|
|
mock_leader_get.side_effect = lambda key: store.get(key)
|
|
mock_get_disk_pw.return_value = "disk-passwd"
|
|
self.assertEqual(helper.get_mysql_password(username='userA'),
|
|
"passwdA")
|
|
self.assertTrue(mock_migrate_pw.called)
|
|
|
|
@mock.patch.object(mysql.MySQLHelper, 'set_mysql_password')
|
|
def test_set_mysql_root_password(self, mock_set_passwd):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
helper.set_mysql_root_password(password='1234')
|
|
mock_set_passwd.assert_called_with(
|
|
'root',
|
|
'1234',
|
|
current_password=None)
|
|
|
|
@mock.patch.object(mysql.MySQLHelper, 'set_mysql_password')
|
|
def test_set_mysql_root_password_cur_passwd(self, mock_set_passwd):
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
helper.set_mysql_root_password(password='1234', current_password='abc')
|
|
mock_set_passwd.assert_called_with(
|
|
'root',
|
|
'1234',
|
|
current_password='abc')
|
|
|
|
@mock.patch.object(mysql, 'log', lambda *args, **kwargs: None)
|
|
@mock.patch.object(mysql, 'is_leader')
|
|
@mock.patch.object(mysql, 'leader_get')
|
|
@mock.patch.object(mysql, 'leader_set')
|
|
@mock.patch.object(mysql, 'CompareHostReleases')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password')
|
|
@mock.patch.object(mysql.MySQLHelper, 'connect')
|
|
def test_set_mysql_password(self, mock_connect, mock_get_passwd,
|
|
mock_compare_releases, mock_leader_set,
|
|
mock_leader_get, mock_is_leader):
|
|
mock_connection = mock.MagicMock()
|
|
mock_cursor = mock.MagicMock()
|
|
mock_connection.cursor.return_value = mock_cursor
|
|
mock_get_passwd.return_value = 'asdf'
|
|
mock_is_leader.return_value = True
|
|
mock_leader_get.return_value = '1234'
|
|
mock_compare_releases.return_value = 'artful'
|
|
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
helper.connection = mock_connection
|
|
|
|
helper.set_mysql_password(username='root', password='1234')
|
|
|
|
mock_connect.assert_has_calls(
|
|
[mock.call(user='root', password='asdf'), # original password
|
|
mock.call(user='root', password='1234')]) # new password
|
|
mock_leader_get.assert_has_calls([mock.call('mysql.passwd')])
|
|
mock_leader_set.assert_has_calls(
|
|
[mock.call(settings={'mysql.passwd': '1234'})]
|
|
)
|
|
SQL_UPDATE_PASSWD = ("UPDATE mysql.user SET password = "
|
|
"PASSWORD( %s ) WHERE user = %s;")
|
|
mock_cursor.assert_has_calls(
|
|
[mock.call.execute(SQL_UPDATE_PASSWD, ('1234', 'root')),
|
|
mock.call.execute('FLUSH PRIVILEGES;'),
|
|
mock.call.close(),
|
|
mock.call.execute('select 1;'),
|
|
mock.call.close()]
|
|
)
|
|
mock_get_passwd.assert_called_once_with(None)
|
|
|
|
# make sure for the non-leader leader-set is not called
|
|
mock_is_leader.return_value = False
|
|
mock_leader_set.reset_mock()
|
|
mock_get_passwd.reset_mock()
|
|
helper.set_mysql_password(username='root', password='1234')
|
|
mock_leader_set.assert_not_called()
|
|
mock_get_passwd.assert_called_once_with(None)
|
|
|
|
mock_get_passwd.reset_mock()
|
|
mock_compare_releases.return_value = 'bionic'
|
|
helper.set_mysql_password(username='root', password='1234')
|
|
SQL_UPDATE_PASSWD = ("UPDATE mysql.user SET "
|
|
"authentication_string = "
|
|
"PASSWORD( %s ) WHERE user = %s;")
|
|
mock_cursor.assert_has_calls(
|
|
[mock.call.execute(SQL_UPDATE_PASSWD, ('1234', 'root')),
|
|
mock.call.execute('FLUSH PRIVILEGES;'),
|
|
mock.call.close(),
|
|
mock.call.execute('select 1;'),
|
|
mock.call.close()]
|
|
)
|
|
mock_get_passwd.assert_called_once_with(None)
|
|
|
|
# Test supplying the current password
|
|
mock_is_leader.return_value = False
|
|
mock_connect.reset_mock()
|
|
mock_get_passwd.reset_mock()
|
|
helper.set_mysql_password(
|
|
username='root',
|
|
password='1234',
|
|
current_password='currpass')
|
|
self.assertFalse(mock_get_passwd.called)
|
|
mock_connect.assert_has_calls(
|
|
[mock.call(user='root', password='currpass'), # original password
|
|
mock.call(user='root', password='1234')]) # new password
|
|
|
|
@mock.patch.object(mysql, 'leader_get')
|
|
@mock.patch.object(mysql, 'leader_set')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password')
|
|
@mock.patch.object(mysql.MySQLHelper, 'connect')
|
|
def test_set_mysql_password_fail_to_connect(self, mock_connect,
|
|
mock_get_passwd,
|
|
mock_leader_set,
|
|
mock_leader_get):
|
|
|
|
class FakeOperationalError(Exception):
|
|
pass
|
|
|
|
def fake_connect(*args, **kwargs):
|
|
raise FakeOperationalError('foobar')
|
|
|
|
mysql.MySQLdb.OperationalError = FakeOperationalError
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
mock_connect.side_effect = fake_connect
|
|
self.assertRaises(mysql.MySQLSetPasswordError,
|
|
helper.set_mysql_password,
|
|
username='root', password='1234')
|
|
|
|
@mock.patch.object(mysql, 'lsb_release')
|
|
@mock.patch.object(mysql, 'leader_get')
|
|
@mock.patch.object(mysql, 'leader_set')
|
|
@mock.patch.object(mysql.MySQLHelper, 'get_mysql_password')
|
|
@mock.patch.object(mysql.MySQLHelper, 'connect')
|
|
def test_set_mysql_password_fail_to_connect2(self, mock_connect,
|
|
mock_get_passwd,
|
|
mock_leader_set,
|
|
mock_leader_get,
|
|
mock_lsb_release):
|
|
|
|
class FakeOperationalError(Exception):
|
|
def __str__(self):
|
|
return 'some-error'
|
|
|
|
operational_error = FakeOperationalError('foobar')
|
|
|
|
def fake_connect(user, password):
|
|
# fail for the new password
|
|
if user == 'root' and password == '1234':
|
|
raise operational_error
|
|
else:
|
|
return mock.MagicMock()
|
|
|
|
mysql.MySQLdb.OperationalError = FakeOperationalError
|
|
helper = mysql.MySQLHelper('foo', 'bar', host='hostA')
|
|
helper.connection = mock.MagicMock()
|
|
mock_connect.side_effect = fake_connect
|
|
mock_lsb_release.return_value = {
|
|
'DISTRIB_CODENAME': 'bionic',
|
|
}
|
|
with self.assertRaises(mysql.MySQLSetPasswordError) as cm:
|
|
helper.set_mysql_password(username='root', password='1234')
|
|
|
|
ex = cm.exception
|
|
self.assertEqual(ex.args[0], 'Cannot connect using new password: some-error')
|
|
self.assertEqual(ex.args[1], operational_error)
|
|
|
|
@mock.patch.object(mysql, 'is_leader')
|
|
@mock.patch.object(mysql, 'leader_set')
|
|
def test_migrate_passwords_to_leader_storage(self, mock_leader_set,
|
|
mock_is_leader):
|
|
files = {'mysql.passwd': '1',
|
|
'userA.passwd': '2',
|
|
'mysql-userA.passwd': '3'}
|
|
store = {}
|
|
|
|
def _store(settings):
|
|
store.update(settings)
|
|
|
|
mock_is_leader.return_value = True
|
|
|
|
tmpdir = tempfile.mkdtemp('charm-helpers-unit-tests')
|
|
try:
|
|
root_tmplt = "%s/mysql.passwd" % (tmpdir)
|
|
helper = mysql.MySQLHelper(root_tmplt, None, host='hostA')
|
|
for f in files:
|
|
with open(os.path.join(tmpdir, f), 'w') as fd:
|
|
fd.write(files[f])
|
|
|
|
mock_leader_set.side_effect = _store
|
|
helper.migrate_passwords_to_leader_storage()
|
|
|
|
calls = [mock.call(settings={'mysql.passwd': '1'}),
|
|
mock.call(settings={'userA.passwd': '2'}),
|
|
mock.call(settings={'mysql-userA.passwd': '3'})]
|
|
|
|
mock_leader_set.assert_has_calls(calls,
|
|
any_order=True)
|
|
finally:
|
|
shutil.rmtree(tmpdir)
|
|
|
|
# Note that legacy key/val is NOT overwritten
|
|
self.assertEqual(store, {'mysql.passwd': '1',
|
|
'userA.passwd': '2',
|
|
'mysql-userA.passwd': '3'})
|
|
|
|
@mock.patch.object(mysql, 'log', lambda *args, **kwargs: None)
|
|
@mock.patch.object(mysql, 'is_leader')
|
|
@mock.patch.object(mysql, 'leader_set')
|
|
def test_migrate_passwords_to_leader_storage_not_leader(self, mock_leader_set,
|
|
mock_is_leader):
|
|
mock_is_leader.return_value = False
|
|
tmpdir = tempfile.mkdtemp('charm-helpers-unit-tests')
|
|
try:
|
|
root_tmplt = "%s/mysql.passwd" % (tmpdir)
|
|
helper = mysql.MySQLHelper(root_tmplt, None, host='hostA')
|
|
helper.migrate_passwords_to_leader_storage()
|
|
finally:
|
|
shutil.rmtree(tmpdir)
|
|
mock_leader_set.assert_not_called()
|
|
|
|
|
|
class MySQLConfigHelperTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(MySQLConfigHelperTests, self).setUp()
|
|
self.config_data = {}
|
|
self.config = mock.MagicMock()
|
|
mysql.config_get = self.config
|
|
self.config.side_effect = self._fake_config
|
|
|
|
def _fake_config(self, key=None):
|
|
if key:
|
|
try:
|
|
return self.config_data[key]
|
|
except KeyError:
|
|
return None
|
|
else:
|
|
return self.config_data
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_innodb_pool_fixed(self, log, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': "50%",
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_buffer_pool_size(),
|
|
helper.human_to_bytes("50G"))
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_innodb_pool_not_set(self, mog, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': '',
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_buffer_pool_size(),
|
|
helper.DEFAULT_INNODB_BUFFER_SIZE_MAX)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_innodb_buffer_unset(self, mog, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': None,
|
|
'dataset-size': None,
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_buffer_pool_size(),
|
|
helper.DEFAULT_INNODB_BUFFER_SIZE_MAX)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_innodb_buffer_unset_small(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': None,
|
|
'dataset-size': None,
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_buffer_pool_size(),
|
|
int(helper.human_to_bytes(mem.return_value) *
|
|
helper.DEFAULT_INNODB_BUFFER_FACTOR))
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_innodb_dataset_size(self, mog, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'dataset-size': "10G",
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_buffer_pool_size(),
|
|
int(helper.human_to_bytes("10G")))
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_tuning_level(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'tuning-level': 'safest',
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_flush_log_at_trx_commit(),
|
|
1
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_tuning_level_fast(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'tuning-level': 'fast',
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_flush_log_at_trx_commit(),
|
|
2
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_tuning_level_unsafe(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'tuning-level': 'unsafe',
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_flush_log_at_trx_commit(),
|
|
0
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_innodb_valid_values(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'innodb-change-buffering': 'all',
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertEqual(
|
|
helper.get_innodb_change_buffering(),
|
|
'all'
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_get_innodb_invalid_values(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'innodb-change-buffering': 'invalid',
|
|
}
|
|
|
|
helper = mysql.MySQLConfigHelper()
|
|
|
|
self.assertTrue(helper.get_innodb_change_buffering() is None)
|
|
|
|
|
|
class PerconaTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(PerconaTests, self).setUp()
|
|
self.config_data = {}
|
|
self.config = mock.MagicMock()
|
|
mysql.config_get = self.config
|
|
self.config.side_effect = self._fake_config
|
|
|
|
def _fake_config(self, key=None):
|
|
if key:
|
|
try:
|
|
return self.config_data[key]
|
|
except KeyError:
|
|
return None
|
|
else:
|
|
return self.config_data
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_innodb_pool_fixed(self, log, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': "50%",
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(mysql_config.get('innodb_buffer_pool_size'),
|
|
helper.human_to_bytes("50G"))
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_innodb_pool_not_set(self, mog, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': '',
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_buffer_pool_size'),
|
|
helper.DEFAULT_INNODB_BUFFER_SIZE_MAX)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_innodb_buffer_unset(self, mog, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': None,
|
|
'dataset-size': None,
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_buffer_pool_size'),
|
|
helper.DEFAULT_INNODB_BUFFER_SIZE_MAX)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_innodb_buffer_unset_small(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'innodb-buffer-pool-size': None,
|
|
'dataset-size': None,
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_buffer_pool_size'),
|
|
int(helper.human_to_bytes(mem.return_value) *
|
|
helper.DEFAULT_INNODB_BUFFER_FACTOR))
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_innodb_dataset_size(self, mog, mem):
|
|
mem.return_value = "100G"
|
|
self.config_data = {
|
|
'dataset-size': "10G",
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_buffer_pool_size'),
|
|
int(helper.human_to_bytes("10G")))
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_wait_timeout(self, mog, mem):
|
|
mem.return_value = "100G"
|
|
|
|
timeout = 314
|
|
self.config_data = {
|
|
'wait-timeout': timeout,
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('wait_timeout'),
|
|
timeout)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_tuning_level(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'tuning-level': 'safest',
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_flush_log_at_trx_commit'),
|
|
1
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_tuning_level_fast(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'tuning-level': 'fast',
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_flush_log_at_trx_commit'),
|
|
2
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_tuning_level_unsafe(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'tuning-level': 'unsafe',
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_flush_log_at_trx_commit'),
|
|
0
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_innodb_valid_values(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'innodb-change-buffering': 'all',
|
|
'innodb-io-capacity': 100,
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_change_buffering'),
|
|
'all'
|
|
)
|
|
|
|
self.assertEqual(
|
|
mysql_config.get('innodb_io_capacity'),
|
|
100
|
|
)
|
|
|
|
@mock.patch.object(mysql.MySQLConfigHelper, 'get_mem_total')
|
|
@mock.patch.object(mysql, 'log')
|
|
def test_parse_config_innodb_invalid_values(self, mog, mem):
|
|
mem.return_value = "512M"
|
|
self.config_data = {
|
|
'innodb-change-buffering': 'invalid',
|
|
}
|
|
|
|
helper = mysql.PerconaClusterHelper()
|
|
mysql_config = helper.parse_config()
|
|
|
|
self.assertTrue('innodb_change_buffering' not in mysql_config)
|
|
self.assertTrue('innodb_io_capacity' not in mysql_config)
|
|
|
|
|
|
class Mysql8Tests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(Mysql8Tests, self).setUp()
|
|
self.template = "/tmp/mysql-passwd.txt"
|
|
self.connection = mock.MagicMock()
|
|
self.cursor = mock.MagicMock()
|
|
self.connection.cursor.return_value = self.cursor
|
|
self.helper = mysql.MySQL8Helper(
|
|
rpasswdf_template=self.template,
|
|
upasswdf_template=self.template)
|
|
self.helper.connection = self.connection
|
|
self.user = "user"
|
|
self.host = "10.5.0.21"
|
|
self.password = "passwd"
|
|
self.db = "mydb"
|
|
|
|
def test_grant_exists(self):
|
|
# With backticks
|
|
self.cursor.fetchall.return_value = (
|
|
("GRANT USAGE ON *.* TO `{}`@`{}`".format(self.user, self.host),),
|
|
("GRANT ALL PRIVILEGES ON `{}`.* TO `{}`@`{}`"
|
|
.format(self.db, self.user, self.host),))
|
|
self.assertTrue(self.helper.grant_exists(self.db, self.user, self.host))
|
|
|
|
self.cursor.execute.assert_called_with(
|
|
"SHOW GRANTS FOR '{}'@'{}'".format(self.user, self.host))
|
|
|
|
# With single quotes
|
|
self.cursor.fetchall.return_value = (
|
|
("GRANT USAGE ON *.* TO '{}'@'{}'".format(self.user, self.host),),
|
|
("GRANT ALL PRIVILEGES ON '{}'.* TO '{}'@'{}'"
|
|
.format(self.db, self.user, self.host),))
|
|
self.assertTrue(self.helper.grant_exists(self.db, self.user, self.host))
|
|
|
|
# Grant not there
|
|
self.cursor.fetchall.return_value = (
|
|
("GRANT USAGE ON *.* TO '{}'@'{}'".format("someuser", "notmyhost"),),
|
|
("GRANT ALL PRIVILEGES ON '{}'.* TO '{}'@'{}'"
|
|
.format("somedb", "someuser", "notmyhost"),))
|
|
self.assertFalse(self.helper.grant_exists(self.db, self.user, self.host))
|
|
|
|
def test_create_grant(self):
|
|
self.helper.grant_exists = mock.MagicMock(return_value=False)
|
|
self.helper.create_user = mock.MagicMock()
|
|
|
|
self.helper.create_grant(self.db, self.user, self.host, self.password)
|
|
self.cursor.execute.assert_called_with(
|
|
"GRANT ALL PRIVILEGES ON `{}`.* TO '{}'@'{}'"
|
|
.format(self.db, self.user, self.host))
|
|
self.helper.create_user.assert_called_with(self.user, self.host, self.password)
|
|
|
|
def test_create_user(self):
|
|
self.helper.create_user(self.user, self.host, self.password)
|
|
self.cursor.execute.assert_called_with(
|
|
"CREATE USER '{}'@'{}' IDENTIFIED BY '{}'".
|
|
format(self.user, self.host, self.password))
|
|
|
|
def test_create_router_grant(self):
|
|
self.helper.create_user = mock.MagicMock()
|
|
|
|
self.helper.create_router_grant(self.user, self.host, self.password)
|
|
_calls = [
|
|
mock.call("GRANT CREATE USER ON *.* TO '{}'@'{}' WITH GRANT OPTION"
|
|
.format(self.user, self.host)),
|
|
mock.call("GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE ON "
|
|
"mysql_innodb_cluster_metadata.* TO '{}'@'{}'"
|
|
.format(self.user, self.host)),
|
|
mock.call("GRANT SELECT ON mysql.user TO '{}'@'{}'"
|
|
.format(self.user, self.host)),
|
|
mock.call("GRANT SELECT ON "
|
|
"performance_schema.replication_group_members TO "
|
|
"'{}'@'{}'".format(self.user, self.host)),
|
|
mock.call("GRANT SELECT ON "
|
|
"performance_schema.replication_group_member_stats TO "
|
|
"'{}'@'{}'".format(self.user, self.host)),
|
|
mock.call("GRANT SELECT ON "
|
|
"performance_schema.global_variables TO "
|
|
"'{}'@'{}'".format(self.user, self.host))]
|
|
|
|
self.cursor.execute.assert_has_calls(_calls)
|
|
self.helper.create_user.assert_called_with(self.user, self.host, self.password)
|
|
|
|
def test_configure_router(self):
|
|
self.helper.create_user = mock.MagicMock()
|
|
self.helper.create_router_grant = mock.MagicMock()
|
|
self.helper.normalize_address = mock.MagicMock(return_value=self.host)
|
|
self.helper.get_mysql_password = mock.MagicMock(return_value=self.password)
|
|
|
|
self.assertEqual(self.password, self.helper.configure_router(self.host, self.user))
|
|
self.helper.create_user.assert_called_with(self.user, self.host, self.password)
|
|
self.helper.create_router_grant.assert_called_with(self.user, self.host, self.password)
|
|
|
|
|
|
class MysqlHelperTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(MysqlHelperTests, self).setUp()
|
|
|
|
def test_get_prefix(self):
|
|
_tests = {
|
|
"prefix1": "prefix1_username",
|
|
"prefix2": "prefix2_database",
|
|
"prefix3": "prefix3_hostname"}
|
|
|
|
for key in _tests.keys():
|
|
self.assertEqual(
|
|
key,
|
|
mysql.get_prefix(_tests[key]))
|
|
|
|
def test_get_db_data(self):
|
|
_unprefixed = "myprefix"
|
|
# Test relation data has every variation of shared-db/db-router data
|
|
_relation_data = {
|
|
"egress-subnets": "10.5.0.43/32",
|
|
"ingress-address": "10.5.0.43",
|
|
"nova_database": "nova",
|
|
"nova_hostname": "10.5.0.43",
|
|
"nova_username": "nova",
|
|
"novaapi_database": "nova_api",
|
|
"novaapi_hostname": "10.5.0.43",
|
|
"novaapi_username": "nova",
|
|
"novacell0_database": "nova_cell0",
|
|
"novacell0_hostname": "10.5.0.43",
|
|
"novacell0_username": "nova",
|
|
"private-address": "10.5.0.43",
|
|
"database": "keystone",
|
|
"username": "keystone",
|
|
"hostname": "10.5.0.43",
|
|
"mysqlrouter_username":
|
|
"mysqlrouteruser",
|
|
"mysqlrouter_hostname": "10.5.0.43"}
|
|
|
|
_expected_data = OrderedDict([
|
|
('nova', OrderedDict([('database', 'nova'),
|
|
('hostname', '10.5.0.43'),
|
|
('username', 'nova')])),
|
|
('novaapi', OrderedDict([('database', 'nova_api'),
|
|
('hostname', '10.5.0.43'),
|
|
('username', 'nova')])),
|
|
('novacell0', OrderedDict([('database', 'nova_cell0'),
|
|
('hostname', '10.5.0.43'),
|
|
('username', 'nova')])),
|
|
('mysqlrouter', OrderedDict([('username', 'mysqlrouteruser'),
|
|
('hostname', '10.5.0.43')])),
|
|
('myprefix', OrderedDict([('hostname', '10.5.0.43'),
|
|
('database', 'keystone'),
|
|
('username', 'keystone')]))])
|
|
|
|
_results = mysql.get_db_data(_relation_data, unprefixed=_unprefixed)
|
|
|
|
for prefix in _expected_data.keys():
|
|
for key in _expected_data[prefix].keys():
|
|
self.assertEqual(
|
|
_results[prefix][key], _expected_data[prefix][key])
|