367 lines
15 KiB
Python
367 lines
15 KiB
Python
import contextlib
|
|
import copy
|
|
import datetime
|
|
import json
|
|
import tempfile
|
|
import shutil
|
|
import yaml
|
|
|
|
from mock import patch, call
|
|
|
|
import charmhelpers.contrib.openstack.deferred_events as deferred_events
|
|
import tests.utils
|
|
|
|
|
|
class TestDB(object):
|
|
'''Test KV store for unitdata testing'''
|
|
def __init__(self):
|
|
self.data = {}
|
|
self.flushed = False
|
|
|
|
def get(self, key, default=None):
|
|
result = self.data.get(key, default)
|
|
if not result:
|
|
return default
|
|
return json.loads(result)
|
|
|
|
def set(self, key, value):
|
|
self.data[key] = json.dumps(value)
|
|
return value
|
|
|
|
def flush(self):
|
|
self.flushed = True
|
|
|
|
|
|
class TestHookData(object):
|
|
|
|
def __init__(self, kv):
|
|
self.kv = kv
|
|
|
|
@contextlib.contextmanager
|
|
def __call__(self):
|
|
yield self.kv, True, True
|
|
|
|
|
|
class DeferredCharmServiceEventsTestCase(tests.utils.BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(DeferredCharmServiceEventsTestCase, self).setUp()
|
|
self.tmp_dir = tempfile.mkdtemp()
|
|
self.addCleanup(lambda: shutil.rmtree(self.tmp_dir))
|
|
self.patch_object(deferred_events.hookenv, 'service_name')
|
|
self.service_name.return_value = 'myapp'
|
|
self.patch_object(deferred_events.unitdata, 'HookData')
|
|
self.db = TestDB()
|
|
self.HookData.return_value = TestHookData(self.db)
|
|
self.exp_event_a = deferred_events.ServiceEvent(
|
|
timestamp=123,
|
|
service='svcA',
|
|
reason='ReasonA',
|
|
action='restart',
|
|
policy_requestor_name='myapp',
|
|
policy_requestor_type='charm')
|
|
self.exp_event_b = deferred_events.ServiceEvent(
|
|
timestamp=223,
|
|
service='svcB',
|
|
reason='ReasonB',
|
|
action='restart')
|
|
self.exp_event_c = deferred_events.ServiceEvent(
|
|
timestamp=323,
|
|
service='svcB',
|
|
reason='ReasonB',
|
|
action='restart')
|
|
self.base_expect_events = [
|
|
self.exp_event_a,
|
|
self.exp_event_b,
|
|
self.exp_event_c]
|
|
self.event_file_pair = []
|
|
for index, event in enumerate(self.base_expect_events):
|
|
event_file = '{}/{}.deferred'.format('/tmpdir', str(index))
|
|
self.event_file_pair.append((
|
|
event_file,
|
|
event))
|
|
|
|
def test_matching_request_event(self):
|
|
self.assertTrue(
|
|
self.exp_event_b.matching_request(
|
|
self.exp_event_c))
|
|
self.assertFalse(
|
|
self.exp_event_a.matching_request(
|
|
self.exp_event_b))
|
|
|
|
@patch.object(deferred_events.glob, "glob")
|
|
def test_deferred_events_files(self, glob):
|
|
defer_files = [
|
|
'/var/lib/policy-rc.d/charm-myapp/1612346300.deferred',
|
|
'/var/lib/policy-rc.d/charm-myapp/1612346322.deferred',
|
|
'/var/lib/policy-rc.d/charm-myapp/1612346360.deferred']
|
|
|
|
glob.return_value = defer_files
|
|
self.assertEqual(
|
|
deferred_events.deferred_events_files(),
|
|
defer_files)
|
|
|
|
def test_read_event_file(self):
|
|
with tempfile.NamedTemporaryFile('w') as ftmp:
|
|
yaml.dump(vars(self.exp_event_a), ftmp)
|
|
ftmp.flush()
|
|
self.assertEqual(
|
|
deferred_events.read_event_file(ftmp.name),
|
|
self.exp_event_a)
|
|
|
|
@patch.object(deferred_events, "deferred_events_files")
|
|
def test_deferred_events(self, deferred_events_files):
|
|
event_files = []
|
|
expect = []
|
|
for index, event in enumerate(self.base_expect_events):
|
|
event_file = '{}/{}.deferred'.format(self.tmp_dir, str(index))
|
|
with open(event_file, 'w') as f:
|
|
yaml.dump(vars(event), f)
|
|
event_files.append(event_file)
|
|
expect.append((
|
|
event_file,
|
|
event))
|
|
deferred_events_files.return_value = event_files
|
|
self.assertEqual(
|
|
deferred_events.deferred_events(),
|
|
expect)
|
|
|
|
@patch.object(deferred_events, "deferred_events")
|
|
def test_duplicate_event_files(self, _deferred_events):
|
|
_deferred_events.return_value = self.event_file_pair
|
|
self.assertEqual(
|
|
deferred_events.duplicate_event_files(self.exp_event_b),
|
|
['/tmpdir/1.deferred', '/tmpdir/2.deferred'])
|
|
self.assertEqual(
|
|
deferred_events.duplicate_event_files(deferred_events.ServiceEvent(
|
|
timestamp=223,
|
|
service='svcX',
|
|
reason='ReasonX',
|
|
action='restart')),
|
|
[])
|
|
|
|
@patch.object(deferred_events.uuid, "uuid1")
|
|
def test_get_event_record_file(self, uuid1):
|
|
uuid1.return_value = '89eb8258'
|
|
self.assertEqual(
|
|
deferred_events.get_event_record_file(
|
|
'charm',
|
|
'neutron-ovs'),
|
|
'/var/lib/policy-rc.d/charm-neutron-ovs-89eb8258.deferred')
|
|
|
|
@patch.object(deferred_events, "get_event_record_file")
|
|
@patch.object(deferred_events, "duplicate_event_files")
|
|
@patch.object(deferred_events, "init_policy_log_dir")
|
|
def test_save_event(self, init_policy_log_dir, duplicate_event_files, get_event_record_file):
|
|
duplicate_event_files.return_value = []
|
|
test_file = '{}/test_save_event.yaml'.format(self.tmp_dir)
|
|
get_event_record_file.return_value = test_file
|
|
deferred_events.save_event(self.exp_event_a)
|
|
with open(test_file, 'r') as f:
|
|
contents = yaml.load(f)
|
|
self.assertEqual(contents, vars(self.exp_event_a))
|
|
|
|
@patch.object(deferred_events.os, "remove")
|
|
@patch.object(deferred_events, "read_event_file")
|
|
@patch.object(deferred_events, "deferred_events_files")
|
|
def test_clear_deferred_events(self, deferred_events_files, read_event_file,
|
|
remove):
|
|
deferred_events_files.return_value = ['/tmp/file1']
|
|
read_event_file.return_value = self.exp_event_a
|
|
deferred_events.clear_deferred_events('svcB', 'restart')
|
|
self.assertFalse(remove.called)
|
|
deferred_events.clear_deferred_events('svcA', 'restart')
|
|
remove.assert_called_once_with('/tmp/file1')
|
|
|
|
@patch.object(deferred_events.os, "mkdir")
|
|
@patch.object(deferred_events.os.path, "exists")
|
|
def test_init_policy_log_dir(self, exists, mkdir):
|
|
exists.return_value = True
|
|
deferred_events.init_policy_log_dir()
|
|
self.assertFalse(mkdir.called)
|
|
exists.return_value = False
|
|
deferred_events.init_policy_log_dir()
|
|
mkdir.assert_called_once_with('/var/lib/policy-rc.d')
|
|
|
|
@patch.object(deferred_events, "deferred_events")
|
|
def test_get_deferred_events(self, _deferred_events):
|
|
_deferred_events.return_value = self.event_file_pair
|
|
self.assertEqual(
|
|
deferred_events.get_deferred_events(),
|
|
self.base_expect_events)
|
|
|
|
@patch.object(deferred_events, "get_deferred_events")
|
|
def test_get_deferred_restarts(self, get_deferred_events):
|
|
test_events = copy.deepcopy(self.base_expect_events)
|
|
test_events.append(
|
|
deferred_events.ServiceEvent(
|
|
timestamp=523,
|
|
service='svcD',
|
|
reason='StopReasonD',
|
|
action='stop'))
|
|
get_deferred_events.return_value = test_events
|
|
self.assertEqual(
|
|
deferred_events.get_deferred_restarts(),
|
|
self.base_expect_events)
|
|
|
|
@patch.object(deferred_events, 'clear_deferred_events')
|
|
def test_clear_deferred_restarts(self, clear_deferred_events):
|
|
deferred_events.clear_deferred_restarts(['svcA', 'svcB'])
|
|
clear_deferred_events.assert_Called_once_with(
|
|
['svcA', 'svcB'],
|
|
'restart')
|
|
|
|
@patch.object(deferred_events, 'clear_deferred_restarts')
|
|
def test_process_svc_restart(self, clear_deferred_restarts):
|
|
deferred_events.process_svc_restart('svcA')
|
|
clear_deferred_restarts.assert_called_once_with(
|
|
['svcA'])
|
|
|
|
@patch.object(deferred_events.hookenv, 'config')
|
|
def test_is_restart_permitted(self, config):
|
|
config.return_value = None
|
|
self.assertTrue(deferred_events.is_restart_permitted())
|
|
config.return_value = True
|
|
self.assertTrue(deferred_events.is_restart_permitted())
|
|
config.return_value = False
|
|
self.assertFalse(deferred_events.is_restart_permitted())
|
|
|
|
@patch.object(deferred_events.time, 'time')
|
|
@patch.object(deferred_events, 'save_event')
|
|
@patch.object(deferred_events, 'is_restart_permitted')
|
|
def test_check_and_record_restart_request(self, is_restart_permitted,
|
|
save_event, time):
|
|
time.return_value = 123
|
|
is_restart_permitted.return_value = False
|
|
deferred_events.check_and_record_restart_request(
|
|
'svcA',
|
|
['/tmp/test1.conf', '/tmp/test2.conf'])
|
|
save_event.assert_called_once_with(deferred_events.ServiceEvent(
|
|
timestamp=123,
|
|
service='svcA',
|
|
reason='File(s) changed: /tmp/test1.conf, /tmp/test2.conf',
|
|
action='restart'))
|
|
|
|
@patch.object(deferred_events.time, 'time')
|
|
@patch.object(deferred_events, 'save_event')
|
|
@patch.object(deferred_events.host, 'service_restart')
|
|
@patch.object(deferred_events, 'is_restart_permitted')
|
|
def test_deferrable_svc_restart(self, is_restart_permitted,
|
|
service_restart, save_event, time):
|
|
time.return_value = 123
|
|
is_restart_permitted.return_value = True
|
|
deferred_events.deferrable_svc_restart('svcA', reason='ReasonA')
|
|
service_restart.assert_called_once_with('svcA')
|
|
service_restart.reset_mock()
|
|
is_restart_permitted.return_value = False
|
|
deferred_events.deferrable_svc_restart('svcA', reason='ReasonA')
|
|
self.assertFalse(service_restart.called)
|
|
save_event.assert_called_once_with(deferred_events.ServiceEvent(
|
|
timestamp=123,
|
|
service='svcA',
|
|
reason='ReasonA',
|
|
action='restart'))
|
|
|
|
@patch.object(deferred_events.policy_rcd, 'add_policy_block')
|
|
@patch.object(deferred_events.policy_rcd, 'remove_policy_file')
|
|
@patch.object(deferred_events, 'is_restart_permitted')
|
|
@patch.object(deferred_events.policy_rcd, 'install_policy_rcd')
|
|
def test_configure_deferred_restarts(self, install_policy_rcd,
|
|
is_restart_permitted,
|
|
remove_policy_file, add_policy_block):
|
|
is_restart_permitted.return_value = True
|
|
deferred_events.configure_deferred_restarts(['svcA', 'svcB'])
|
|
remove_policy_file.assert_called_once_with()
|
|
install_policy_rcd.assert_called_once_with()
|
|
|
|
remove_policy_file.reset_mock()
|
|
install_policy_rcd.reset_mock()
|
|
is_restart_permitted.return_value = False
|
|
deferred_events.configure_deferred_restarts(['svcA', 'svcB'])
|
|
self.assertFalse(remove_policy_file.called)
|
|
install_policy_rcd.assert_called_once_with()
|
|
add_policy_block.assert_has_calls([
|
|
call('svcA', ['stop', 'restart', 'try-restart']),
|
|
call('svcB', ['stop', 'restart', 'try-restart'])])
|
|
|
|
@patch.object(deferred_events.subprocess, 'check_output')
|
|
def test_get_service_start_time(self, check_output):
|
|
check_output.return_value = (
|
|
b'ActiveEnterTimestamp=Tue 2021-02-02 13:19:55 UTC')
|
|
expect = datetime.datetime.strptime(
|
|
'Tue 2021-02-02 13:19:55 UTC',
|
|
'%a %Y-%m-%d %H:%M:%S %Z')
|
|
self.assertEqual(
|
|
deferred_events.get_service_start_time('svcA'),
|
|
expect)
|
|
check_output.assert_called_once_with(
|
|
['systemctl', 'show', 'svcA', '--property=ActiveEnterTimestamp'])
|
|
|
|
@patch.object(deferred_events, 'get_deferred_restarts')
|
|
@patch.object(deferred_events, 'clear_deferred_restarts')
|
|
@patch.object(deferred_events.hookenv, 'log')
|
|
@patch.object(deferred_events, 'get_service_start_time')
|
|
def test_check_restart_timestamps(self, get_service_start_time, log,
|
|
clear_deferred_restarts,
|
|
get_deferred_restarts):
|
|
deferred_restarts = [
|
|
# 'Tue 2021-02-02 10:19:55 UTC'
|
|
deferred_events.ServiceEvent(
|
|
timestamp=1612261195.0,
|
|
service='svcA',
|
|
reason='ReasonA',
|
|
action='restart')]
|
|
get_deferred_restarts.return_value = deferred_restarts
|
|
get_service_start_time.return_value = datetime.datetime.strptime(
|
|
'Tue 2021-02-02 13:19:55 UTC',
|
|
'%a %Y-%m-%d %H:%M:%S %Z')
|
|
deferred_events.check_restart_timestamps()
|
|
clear_deferred_restarts.assert_called_once_with(['svcA'])
|
|
|
|
clear_deferred_restarts.reset_mock()
|
|
get_service_start_time.return_value = datetime.datetime.strptime(
|
|
'Tue 2021-02-02 10:10:55 UTC',
|
|
'%a %Y-%m-%d %H:%M:%S %Z')
|
|
deferred_events.check_restart_timestamps()
|
|
self.assertFalse(clear_deferred_restarts.called)
|
|
log.assert_called_once_with(
|
|
('Restart still required, svcA was started at 2021-02-02 10:10:55,'
|
|
' restart was requested after that at 2021-02-02 10:19:55'),
|
|
level='DEBUG')
|
|
|
|
def test_set_deferred_hook(self):
|
|
deferred_events.set_deferred_hook('config-changed')
|
|
self.assertEqual(self.db.get('deferred-hooks'), ['config-changed'])
|
|
deferred_events.set_deferred_hook('leader-settings-changed')
|
|
self.assertEqual(
|
|
self.db.get('deferred-hooks'),
|
|
['config-changed', 'leader-settings-changed'])
|
|
|
|
def test_get_deferred_hook(self):
|
|
deferred_events.set_deferred_hook('config-changed')
|
|
self.assertEqual(
|
|
deferred_events.get_deferred_hooks(),
|
|
['config-changed'])
|
|
|
|
def test_clear_deferred_hooks(self):
|
|
deferred_events.set_deferred_hook('config-changed')
|
|
deferred_events.set_deferred_hook('leader-settings-changed')
|
|
self.assertEqual(
|
|
deferred_events.get_deferred_hooks(),
|
|
['config-changed', 'leader-settings-changed'])
|
|
deferred_events.clear_deferred_hooks()
|
|
self.assertEqual(
|
|
deferred_events.get_deferred_hooks(),
|
|
[])
|
|
|
|
def test_clear_deferred_hook(self):
|
|
deferred_events.set_deferred_hook('config-changed')
|
|
deferred_events.set_deferred_hook('leader-settings-changed')
|
|
self.assertEqual(
|
|
deferred_events.get_deferred_hooks(),
|
|
['config-changed', 'leader-settings-changed'])
|
|
deferred_events.clear_deferred_hook('leader-settings-changed')
|
|
self.assertEqual(
|
|
deferred_events.get_deferred_hooks(),
|
|
['config-changed'])
|