Ensure that c.c.unitdata.kv is properly mocked out

As unit tests run concurrently, it's important that all tests use a
mocked out version of the kv() store.  Otherwise, unit tests can race
and fail due to SQLite lock erros.  See linked bug for details.

Change-Id: I7e16a566531a7faf9d3a960c3df524fd46976a2a
Closes-Bug: #1905760
This commit is contained in:
Alex Kavanagh 2020-11-26 20:21:31 +00:00
parent da304cbea6
commit 2962cc9a9a
3 changed files with 89 additions and 11 deletions

View File

@ -9,9 +9,8 @@ import yaml
import charmhelpers.contrib.openstack.ha.utils as ch_ha_utils
from charmhelpers.contrib.database.mysql import PerconaClusterHelper
from charmhelpers.core.unitdata import kv
from test_utils import CharmTestCase
from test_utils import CharmTestCase, FakeKvStore
sys.modules['MySQLdb'] = mock.Mock()
# python-apt is not installed as part of test-requirements but is imported by
@ -24,7 +23,8 @@ with mock.patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
import percona_hooks as hooks
TO_PATCH = ['log', 'config',
TO_PATCH = ['log',
'config',
'get_db_helper',
'relation_ids',
'relation_set',
@ -52,7 +52,8 @@ TO_PATCH = ['log', 'config',
'client_node_is_ready',
'relation_set',
'relation_get',
'install_mysql_ocf']
'install_mysql_ocf',
'kv']
class TestSharedDBRelation(CharmTestCase):
@ -763,6 +764,8 @@ class TestUpgradeCharm(CharmTestCase):
'leader_get',
'notify_bootstrapped',
'mark_seeded',
'kv',
'is_unit_upgrading_set',
]
def print_log(self, msg, level=None):
@ -773,6 +776,7 @@ class TestUpgradeCharm(CharmTestCase):
self.config.side_effect = self.test_config.get
self.log.side_effect = self.print_log
self.tmpdir = tempfile.mkdtemp()
self.is_unit_upgrading_set.return_value = False
def tearDown(self):
CharmTestCase.tearDown(self)
@ -853,6 +857,7 @@ class TestConfigs(CharmTestCase):
default_config[k] = None
return default_config
@mock.patch.object(hooks, 'is_unit_upgrading_set')
@mock.patch.object(os, 'makedirs')
@mock.patch.object(hooks, 'get_cluster_host_ip')
@mock.patch.object(hooks, 'get_wsrep_provider_options')
@ -867,7 +872,9 @@ class TestConfigs(CharmTestCase):
parse_config,
get_wsrep_provider_options,
get_cluster_host_ip,
makedirs):
makedirs,
mock_is_unit_upgrading_set):
mock_is_unit_upgrading_set.return_value = False
parse_config.return_value = {'key_buffer': '32M'}
get_cluster_host_ip.return_value = '10.1.1.1'
get_wsrep_provider_options.return_value = None
@ -905,6 +912,7 @@ class TestConfigs(CharmTestCase):
context,
perms=0o444)
@mock.patch.object(hooks, 'is_unit_upgrading_set')
@mock.patch.object(os, 'makedirs')
@mock.patch.object(hooks, 'get_cluster_host_ip')
@mock.patch.object(hooks, 'get_wsrep_provider_options')
@ -919,7 +927,9 @@ class TestConfigs(CharmTestCase):
parse_config,
get_wsrep_provider_options,
get_cluster_host_ip,
makedirs):
makedirs,
mock_is_unit_upgrading_set):
mock_is_unit_upgrading_set.return_value = False
parse_config.return_value = {'key_buffer': '32M'}
get_cluster_host_ip.return_value = '10.1.1.1'
get_wsrep_provider_options.return_value = None
@ -960,6 +970,7 @@ class TestConfigs(CharmTestCase):
context,
perms=0o444)
@mock.patch.object(hooks, 'is_unit_upgrading_set')
@mock.patch.object(os, 'makedirs')
@mock.patch.object(hooks, 'get_cluster_host_ip')
@mock.patch.object(hooks, 'get_wsrep_provider_options')
@ -975,7 +986,9 @@ class TestConfigs(CharmTestCase):
parse_config,
get_wsrep_provider_options,
get_cluster_host_ip,
makedirs):
makedirs,
mock_is_unit_upgrading_set):
mock_is_unit_upgrading_set.return_value = False
parse_config.return_value = {'key_buffer': '32M'}
get_cluster_host_ip.return_value = '10.1.1.1'
get_wsrep_provider_options.return_value = None
@ -1025,9 +1038,11 @@ class TestClusterRelation(CharmTestCase):
CharmTestCase.setUp(self, hooks, TO_PATCH)
self.config.side_effect = self.test_config.get
self.is_leader.return_value = False
kvstore = kv()
kvstore.set('initial_client_update_done', True)
self.kvstore = FakeKvStore()
self.kvstore.set('initial_client_update_done', True)
self.kv.return_value = self.kvstore
@mock.patch('percona_utils.kv')
@mock.patch.object(hooks, 'config_changed')
@mock.patch.object(hooks, 'get_cluster_host_ip')
@mock.patch('percona_utils.notify_bootstrapped')
@ -1044,13 +1059,15 @@ class TestClusterRelation(CharmTestCase):
mock_leader_get, mock_get_wsrep_value,
mock_notify_bootstrapped,
mock_cluster_host_ip,
mock_config_changed):
mock_config_changed,
mock_kv):
def fake_leader_get(k):
return {
'bootstrap-uuid': '1-2-3-4',
'wsrep_cluster_state_uuid': '1-2-3-4',
}[k]
mock_kv.return_value = self.kvstore
mock_leader_get.side_effect = fake_leader_get
mock_get_wsrep_value.side_effect = fake_leader_get
mock_is_bootstrapped.return_value = True

View File

@ -8,7 +8,7 @@ from charmhelpers.fetch import SourceConfigError
import percona_utils
from test_utils import CharmTestCase, patch_open
from test_utils import CharmTestCase, patch_open, FakeKvStore
os.environ['JUJU_UNIT_NAME'] = 'percona-cluster/2'
@ -556,10 +556,16 @@ class UtilsTestsStatus(CharmTestCase):
'distributed_wait',
'cluster_ready',
'seeded',
'kv',
]
def setUp(self):
super(UtilsTestsStatus, self).setUp(percona_utils, self.TO_PATCH)
self._kvstore = FakeKvStore()
self.kv.return_value = self._kvstore
_m = mock.patch("charmhelpers.core.unitdata.kv")
self.mock_kv = _m.start()
self.addCleanup(_m.stop)
@mock.patch.object(percona_utils, 'seeded')
def test_single_unit(self, mock_seeded):

View File

@ -1,6 +1,7 @@
import io
import os
import logging
import sys
import unittest
import yaml
@ -8,6 +9,8 @@ import yaml
from contextlib import contextmanager
from mock import patch, MagicMock
from charmhelpers.core.unitdata import Record
def load_config():
'''
@ -143,3 +146,55 @@ def patch_open():
with patch('builtins.open', stub_open):
yield mock_open, mock_file
class FakeKvStore():
def __init__(self):
self._store = {}
self._closed = False
self._flushed = False
def close(self):
self._closed = True
self._flushed = True
def get(self, key, default=None, record=False):
if key not in self._store:
return default
if record:
return Record(self._store[key])
return self._store[key]
def getrange(self, *args, **kwargs):
raise NotImplementedError
def update(self, mapping, prefix=""):
for k, v in mapping.items():
self.set("%s%s" % (prefix, k), v)
def unset(self, key):
if key in self._store:
del self._store[key]
def unsetrange(self, keys=None, prefix=""):
raise NotImplementedError
def set(self, key, value):
self._store[key] = value
return value
def delta(self, mapping, prefix):
raise NotImplementedError
def hook_scope(self, name=""):
raise NotImplementedError
def flush(self, save=True):
self._flushed = True
def gethistory(self, key, deserialize=False):
raise NotImplementedError
def debug(self, fh=sys.stderr):
raise NotImplementedError