Implementation of deferred restarts

Add deferred restart actions and config.

Change-Id: I334df5ca932e9f94e128d9fa66c1ab91d60233b4
This commit is contained in:
Liam Young 2021-01-27 17:57:28 +00:00
parent 548a675f5a
commit 9b11d24d40
15 changed files with 266 additions and 85 deletions

View File

@ -26,3 +26,30 @@ pause:
resume: resume:
descrpition: | descrpition: |
Resume the neutron-openvswitch unit. This action will start neutron-openvswitch services. Resume the neutron-openvswitch unit. This action will start neutron-openvswitch services.
restart-services:
description: |
Restarts services this charm manages.
params:
deferred-only:
type: boolean
default: false
description: |
Restart all deferred services.
services:
type: string
default: ""
description: |
List of services to restart.
run-hooks:
type: boolean
default: true
description: |
Run any hooks which have been deferred.
run-deferred-hooks:
description: |
Run deferable hooks and restart services.
.
NOTE: Service will be restarted as needed irrespective of enable-auto-restarts
show-deferred-events:
descrpition: |
Show the outstanding restarts

132
actions/os_actions.py Executable file
View File

@ -0,0 +1,132 @@
#!/usr/bin/env python3
#
# Copyright 2021 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
sys.path.append('hooks/')
import charmhelpers.contrib.openstack.deferred_events as deferred_events
import charmhelpers.contrib.openstack.utils as os_utils
from charmhelpers.core.hookenv import (
action_get,
action_fail,
)
import neutron_ovs_hooks
from neutron_ovs_utils import (
assess_status,
pause_unit_helper,
resume_unit_helper,
register_configs,
)
def pause(args):
"""Pause the neutron-openvswitch services.
@raises Exception should the service fail to stop.
"""
pause_unit_helper(register_configs(),
exclude_services=['openvswitch-switch'])
def resume(args):
"""Resume the neutron-openvswitch services.
@raises Exception should the service fail to start."""
resume_unit_helper(register_configs(),
exclude_services=['openvswitch-switch'])
def restart(args):
"""Restart services.
:param args: Unused
:type args: List[str]
"""
deferred_only = action_get("deferred-only")
services = action_get("services").split()
# Check input
if deferred_only and services:
action_fail("Cannot set deferred-only and services")
return
if not (deferred_only or services):
action_fail("Please specify deferred-only or services")
return
if action_get('run-hooks'):
_run_deferred_hooks()
if deferred_only:
os_utils.restart_services_action(deferred_only=True)
else:
os_utils.restart_services_action(services=services)
assess_status(register_configs())
def _run_deferred_hooks():
"""Run supported deferred hooks as needed.
Run supported deferred hooks as needed. If support for deferring a new
hook is added to the charm then this method will need updating.
"""
if not deferred_events.is_restart_permitted():
deferred_hooks = deferred_events.get_deferred_hooks()
if deferred_hooks and 'config-changed' in deferred_hooks:
neutron_ovs_hooks.config_changed(check_deferred_restarts=False)
deferred_events.clear_deferred_hook('config-changed')
def run_deferred_hooks(args):
"""Run deferred hooks.
:param args: Unused
:type args: List[str]
"""
_run_deferred_hooks()
os_utils.restart_services_action(deferred_only=True)
assess_status(register_configs())
def show_deferred_events(args):
"""Show the deferred events.
:param args: Unused
:type args: List[str]
"""
os_utils.show_deferred_events_action_helper()
# A dictionary of all the defined actions to callables (which take
# parsed arguments).
ACTIONS = {"pause": pause, "resume": resume, "restart-services": restart,
"show-deferred-events": show_deferred_events,
"run-deferred-hooks": run_deferred_hooks}
def main(args):
action_name = os.path.basename(args[0])
try:
action = ACTIONS[action_name]
except KeyError:
s = "Action {} undefined".format(action_name)
action_fail(s)
return s
else:
try:
action(args)
except Exception as e:
action_fail("Action {} failed: {}".format(action_name, str(e)))
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@ -1 +1 @@
pause_resume.py os_actions.py

View File

@ -1,66 +0,0 @@
#!/usr/bin/env python3
#
# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
sys.path.append('hooks/')
from charmhelpers.core.hookenv import action_fail
from neutron_ovs_utils import (
pause_unit_helper,
resume_unit_helper,
register_configs,
)
def pause(args):
"""Pause the neutron-openvswitch services.
@raises Exception should the service fail to stop.
"""
pause_unit_helper(register_configs(),
exclude_services=['openvswitch-switch'])
def resume(args):
"""Resume the neutron-openvswitch services.
@raises Exception should the service fail to start."""
resume_unit_helper(register_configs(),
exclude_services=['openvswitch-switch'])
# A dictionary of all the defined actions to callables (which take
# parsed arguments).
ACTIONS = {"pause": pause, "resume": resume}
def main(args):
action_name = os.path.basename(args[0])
try:
action = ACTIONS[action_name]
except KeyError:
s = "Action {} undefined".format(action_name)
action_fail(s)
return s
else:
try:
action(args)
except Exception as e:
action_fail("Action {} failed: {}".format(action_name, str(e)))
if __name__ == "__main__":
sys.exit(main(sys.argv))

1
actions/restart-services Symbolic link
View File

@ -0,0 +1 @@
os_actions.py

View File

@ -1 +1 @@
pause_resume.py os_actions.py

1
actions/run-deferred-hooks Symbolic link
View File

@ -0,0 +1 @@
os_actions.py

View File

@ -0,0 +1 @@
os_actions.py

View File

@ -445,3 +445,9 @@ options:
in an expected data plane outage while the service restarts. in an expected data plane outage while the service restarts.
. .
(Available from Mitaka) (Available from Mitaka)
enable-auto-restarts:
type: boolean
default: True
description: |
Allow the charm and packages to restart services automatically when
required.

View File

@ -22,14 +22,18 @@ from copy import deepcopy
from charmhelpers.contrib.openstack import context as os_context from charmhelpers.contrib.openstack import context as os_context
from charmhelpers.contrib.openstack.utils import ( from charmhelpers.contrib.openstack.utils import (
pausable_restart_on_change as restart_on_change, os_restart_on_change as restart_on_change,
series_upgrade_prepare, series_upgrade_prepare,
series_upgrade_complete, series_upgrade_complete,
is_unit_paused_set, is_hook_allowed,
CompareOpenStackReleases, CompareOpenStackReleases,
os_release, os_release,
) )
from charmhelpers.contrib.openstack.deferred_events import (
configure_deferred_restarts,
)
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
Hooks, Hooks,
UnregisteredHookError, UnregisteredHookError,
@ -72,6 +76,7 @@ from neutron_ovs_utils import (
determine_purge_packages, determine_purge_packages,
purge_sriov_systemd_files, purge_sriov_systemd_files,
use_fqdn_hint, use_fqdn_hint,
deferrable_services,
) )
hooks = Hooks() hooks = Hooks()
@ -127,11 +132,16 @@ def upgrade_charm():
@restart_on_change({cfg: services @restart_on_change({cfg: services
for cfg, services in restart_map().items() for cfg, services in restart_map().items()
if cfg != OVS_DEFAULT}) if cfg != OVS_DEFAULT})
def config_changed(): def config_changed(check_deferred_restarts=True):
configure_deferred_restarts(deferrable_services())
# policy_rcd.remove_policy_file()
# if we are paused, delay doing any config changed hooks. # if we are paused, delay doing any config changed hooks.
# It is forced on the resume. # It is forced on the resume.
if is_unit_paused_set(): allowed, reason = is_hook_allowed(
log("Unit is pause or upgrading. Skipping config_changed", "WARN") 'config-changed',
check_deferred_restarts=check_deferred_restarts)
if not allowed:
log(reason, "WARN")
return return
install_packages() install_packages()
@ -257,7 +267,8 @@ def amqp_changed():
@hooks.hook('neutron-control-relation-changed') @hooks.hook('neutron-control-relation-changed')
@restart_on_change(restart_map(), stopstart=True) @restart_on_change(restart_map(),
stopstart=True)
def restart_check(): def restart_check():
CONFIGS.write_all() CONFIGS.write_all()

View File

@ -32,6 +32,10 @@ from charmhelpers.contrib.openstack.utils import (
os_release, os_release,
sequence_status_check_functions, sequence_status_check_functions,
) )
from charmhelpers.contrib.openstack.deferred_events import (
deferrable_svc_restart,
check_restart_timestamps,
)
from charmhelpers.core.unitdata import kv from charmhelpers.core.unitdata import kv
from collections import OrderedDict from collections import OrderedDict
import neutron_ovs_context import neutron_ovs_context
@ -482,6 +486,28 @@ def services(exclude_services=None):
return list(s_set) return list(s_set)
def deferrable_services():
"""Services which should be stopped from restarting.
All services from services() are deferable. But the charm may
install a package which install a service that the charm does not add
to its restart_map. In that case it will be missing from
self.services. However one of the jobs of deferred events is to ensure
that packages updates outside of charms also do not restart services.
To ensure there is a complete list take the services from services()
and also add in a known list of networking services.
NOTE: It does not matter if one of the services in the list is not
installed on the system.
"""
_svcs = services()
_svcs.extend(['ovs-vswitchd', 'ovsdb-server', 'ovs-vswitchd-dpdk',
'openvswitch-switch'])
return list(set(_svcs))
def determine_ports(): def determine_ports():
"""Assemble a list of API ports for services the charm is managing """Assemble a list of API ports for services the charm is managing
@ -558,7 +584,9 @@ def enable_ovs_dpdk():
) )
if ((values_changed and any(values_changed)) and if ((values_changed and any(values_changed)) and
not is_unit_paused_set()): not is_unit_paused_set()):
service_restart('openvswitch-switch') deferrable_svc_restart(
'openvswitch-switch',
restart_reason='DPDK Config changed')
def enable_hw_offload(): def enable_hw_offload():
@ -571,7 +599,9 @@ def enable_hw_offload():
] ]
if ((values_changed and any(values_changed)) and if ((values_changed and any(values_changed)) and
not is_unit_paused_set()): not is_unit_paused_set()):
service_restart('openvswitch-switch') deferrable_svc_restart(
'openvswitch-switch',
restart_reason='Hardware offload config changed')
def install_tmpfilesd(): def install_tmpfilesd():
@ -908,6 +938,7 @@ def assess_status(configs):
@param configs: a templating.OSConfigRenderer() object @param configs: a templating.OSConfigRenderer() object
@returns None - this function is executed for its side-effect @returns None - this function is executed for its side-effect
""" """
check_restart_timestamps()
exclude_services = [] exclude_services = []
if is_unit_paused_set(): if is_unit_paused_set():
exclude_services = ['openvswitch-switch'] exclude_services = ['openvswitch-switch']

View File

@ -55,11 +55,13 @@ target_deploy_status:
workload-status-message: Vault needs to be initialized workload-status-message: Vault needs to be initialized
tests: tests:
- zaza.openstack.charm_tests.neutron.tests.NeutronOVSDeferredRestartTest
- zaza.openstack.charm_tests.neutron.tests.NeutronNetworkingTest - zaza.openstack.charm_tests.neutron.tests.NeutronNetworkingTest
- zaza.openstack.charm_tests.neutron.tests.NeutronOpenvSwitchTest - zaza.openstack.charm_tests.neutron.tests.NeutronOpenvSwitchTest
- zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest - zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest
- zaza.openstack.charm_tests.neutron.tests.NeutronBridgePortMappingTest - zaza.openstack.charm_tests.neutron.tests.NeutronBridgePortMappingTest
- migrate-ovn: - migrate-ovn:
- zaza.openstack.charm_tests.neutron.tests.NeutronOVSDeferredRestartTest
- zaza.openstack.charm_tests.neutron.tests.NeutronNetworkingTest - zaza.openstack.charm_tests.neutron.tests.NeutronNetworkingTest
- zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest - zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest
- zaza.openstack.charm_tests.neutron.tests.NeutronBridgePortMappingTest - zaza.openstack.charm_tests.neutron.tests.NeutronBridgePortMappingTest

View File

@ -52,6 +52,7 @@ TO_PATCH = [
'purge_packages', 'purge_packages',
'determine_purge_packages', 'determine_purge_packages',
'is_container', 'is_container',
'is_hook_allowed',
] ]
NEUTRON_CONF_DIR = "/etc/neutron" NEUTRON_CONF_DIR = "/etc/neutron"
@ -66,6 +67,7 @@ class NeutronOVSHooksTests(CharmTestCase):
self.config.side_effect = self.test_config.get self.config.side_effect = self.test_config.get
self.is_container.return_value = False self.is_container.return_value = False
hooks.hooks._config_save = False hooks.hooks._config_save = False
self.is_hook_allowed.return_value = (True, '')
def _call_hook(self, hookname): def _call_hook(self, hookname):
hooks.hooks.execute([ hooks.hooks.execute([
@ -117,13 +119,25 @@ class NeutronOVSHooksTests(CharmTestCase):
self.CONFIGS.write.assert_not_called() self.CONFIGS.write.assert_not_called()
self.assertEqual(0, mock_restart.call_count) self.assertEqual(0, mock_restart.call_count)
def test_config_changed_dvr(self): @patch.object(hooks, 'deferrable_services')
@patch.object(hooks, 'configure_deferred_restarts')
def test_config_changed_dvr(self, mock_configure_deferred_restarts,
mock_deferrable_services):
mock_deferrable_services.return_value = ['ovs-vswitchd']
self._call_hook('config-changed') self._call_hook('config-changed')
self.install_packages.assert_called_with() self.install_packages.assert_called_with()
self.assertTrue(self.CONFIGS.write_all.called) self.assertTrue(self.CONFIGS.write_all.called)
self.configure_ovs.assert_called_with() self.configure_ovs.assert_called_with()
mock_deferrable_services.assert_called_once_with()
mock_configure_deferred_restarts.assert_called_once_with(
['ovs-vswitchd'])
def test_config_changed_sysctl_overrides(self): @patch.object(hooks, 'deferrable_services')
@patch.object(hooks, 'configure_deferred_restarts')
def test_config_changed_sysctl_overrides(self,
mock_configure_deferred_restarts,
mock_deferrable_services):
mock_deferrable_services.return_value = ['ovs-vswitchd']
self.test_config.set( self.test_config.set(
'sysctl', 'sysctl',
'{foo : bar}' '{foo : bar}'
@ -132,8 +146,15 @@ class NeutronOVSHooksTests(CharmTestCase):
self.create_sysctl.assert_called_with( self.create_sysctl.assert_called_with(
'{foo : bar}', '{foo : bar}',
'/etc/sysctl.d/50-openvswitch.conf') '/etc/sysctl.d/50-openvswitch.conf')
mock_deferrable_services.assert_called_once_with()
mock_configure_deferred_restarts.assert_called_once_with(
['ovs-vswitchd'])
def test_config_changed_sysctl_container(self): @patch.object(hooks, 'deferrable_services')
@patch.object(hooks, 'configure_deferred_restarts')
def test_config_changed_sysctl_container(self,
mock_configure_deferred_restarts,
mock_deferrable_services):
self.test_config.set( self.test_config.set(
'sysctl', 'sysctl',
'{foo : bar}' '{foo : bar}'
@ -142,8 +163,13 @@ class NeutronOVSHooksTests(CharmTestCase):
self._call_hook('config-changed') self._call_hook('config-changed')
self.create_sysctl.assert_not_called() self.create_sysctl.assert_not_called()
@patch.object(hooks, 'deferrable_services')
@patch.object(hooks, 'configure_deferred_restarts')
@patch.object(hooks, 'neutron_plugin_joined') @patch.object(hooks, 'neutron_plugin_joined')
def test_config_changed_rocky_upgrade(self, _plugin_joined): def test_config_changed_rocky_upgrade(self,
_plugin_joined,
mock_configure_deferred_restarts,
mock_deferrable_services):
self.determine_purge_packages.return_value = ['python-neutron'] self.determine_purge_packages.return_value = ['python-neutron']
self.relation_ids.return_value = ['neutron-plugin:42'] self.relation_ids.return_value = ['neutron-plugin:42']
self._call_hook('config-changed') self._call_hook('config-changed')

View File

@ -69,6 +69,7 @@ TO_PATCH = [
'modprobe', 'modprobe',
'is_container', 'is_container',
'is_unit_paused_set', 'is_unit_paused_set',
'deferrable_svc_restart',
] ]
head_pkg = 'linux-headers-3.15.0-5-generic' head_pkg = 'linux-headers-3.15.0-5-generic'
@ -1062,7 +1063,8 @@ class TestNeutronOVSUtils(CharmTestCase):
DummyContext(return_value={'shared_secret': 'supersecret'}) DummyContext(return_value={'shared_secret': 'supersecret'})
self.assertEqual(nutils.get_shared_secret(), 'supersecret') self.assertEqual(nutils.get_shared_secret(), 'supersecret')
def test_assess_status(self): @patch.object(nutils, 'check_restart_timestamps')
def test_assess_status(self, mock_check_restart_timestamps):
with patch.object(nutils, 'assess_status_func') as asf: with patch.object(nutils, 'assess_status_func') as asf:
callee = MagicMock() callee = MagicMock()
asf.return_value = callee asf.return_value = callee
@ -1072,6 +1074,7 @@ class TestNeutronOVSUtils(CharmTestCase):
self.os_application_version_set.assert_called_with( self.os_application_version_set.assert_called_with(
nutils.VERSION_PACKAGE nutils.VERSION_PACKAGE
) )
mock_check_restart_timestamps.assert_called_once_with()
@patch('charmhelpers.contrib.openstack.context.config') @patch('charmhelpers.contrib.openstack.context.config')
def test_check_ext_port_data_port_config(self, mock_config): def test_check_ext_port_data_port_config(self, mock_config):
@ -1196,7 +1199,9 @@ class TestNeutronOVSUtils(CharmTestCase):
_check_call.assert_called_once_with( _check_call.assert_called_once_with(
nutils.UPDATE_ALTERNATIVES + [nutils.OVS_DPDK_BIN] nutils.UPDATE_ALTERNATIVES + [nutils.OVS_DPDK_BIN]
) )
self.service_restart.assert_called_with('openvswitch-switch') self.deferrable_svc_restart.assert_called_with(
'openvswitch-switch',
restart_reason='DPDK Config changed')
@patch.object(nutils, 'is_unit_paused_set') @patch.object(nutils, 'is_unit_paused_set')
@patch.object(nutils.subprocess, 'check_call') @patch.object(nutils.subprocess, 'check_call')
@ -1229,7 +1234,9 @@ class TestNeutronOVSUtils(CharmTestCase):
_check_call.assert_called_once_with( _check_call.assert_called_once_with(
nutils.UPDATE_ALTERNATIVES + [nutils.OVS_DPDK_BIN] nutils.UPDATE_ALTERNATIVES + [nutils.OVS_DPDK_BIN]
) )
self.service_restart.assert_called_with('openvswitch-switch') self.deferrable_svc_restart.assert_called_with(
'openvswitch-switch',
restart_reason='DPDK Config changed')
@patch.object(nutils.context, 'NeutronAPIContext') @patch.object(nutils.context, 'NeutronAPIContext')
@patch.object(nutils, 'is_container') @patch.object(nutils, 'is_container')
@ -1298,7 +1305,9 @@ class TestNeutronOVSUtils(CharmTestCase):
call('other_config:hw-offload', 'true'), call('other_config:hw-offload', 'true'),
call('other_config:max-idle', '30000'), call('other_config:max-idle', '30000'),
]) ])
self.service_restart.assert_called_once_with('openvswitch-switch') self.deferrable_svc_restart.assert_called_once_with(
'openvswitch-switch',
restart_reason='Hardware offload config changed')
@patch.object(nutils, 'set_Open_vSwitch_column_value') @patch.object(nutils, 'set_Open_vSwitch_column_value')
def test_enable_hw_offload_unit_paused(self, _ovs_set): def test_enable_hw_offload_unit_paused(self, _ovs_set):

View File

@ -19,7 +19,7 @@ from test_utils import CharmTestCase
with patch('neutron_ovs_utils.register_configs') as configs: with patch('neutron_ovs_utils.register_configs') as configs:
configs.return_value = 'test-config' configs.return_value = 'test-config'
import pause_resume as actions import os_actions as actions
class PauseTestCase(CharmTestCase): class PauseTestCase(CharmTestCase):