Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions src/reactive/containerd.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,10 @@ def upgrade_charm():
if is_state("containerd.resource.installed"):
# if a resource is currently overriding the deb,
# upgrade containerd from the apt packages
reinstall_containerd()
# to make sure we get latest systemd services, latest configs,
# and dependencies like runc if needed -- but don't restart into
# those services
reinstall_containerd(restart=False)

# Re-render config in case the template has changed in the new charm.
config_changed()
Expand Down Expand Up @@ -518,11 +521,35 @@ def install_containerd():
config_changed()


def reinstall_containerd():
@contextlib.contextmanager
def _apt_restart_services(restart: bool):
"""
Context manager to conditionally restart services after apt operations.

Args:
restart: whether to restart services after apt operations
"""
original = os.environ.get("NEEDRESTART_SUSPEND")
log("Services will {}be restarted after apt operations.".format("" if restart else "not "))
if not restart:
os.environ.update({"NEEDRESTART_SUSPEND": "1"})
else:
os.environ.pop("NEEDRESTART_SUSPEND", None)
try:
yield
finally:
if original is None:
os.environ.pop("NEEDRESTART_SUSPEND", None)
else:
os.environ["NEEDRESTART_SUSPEND"] = original


def reinstall_containerd(restart: bool = True):
"""Install and hold containerd with apt."""
apt_update(fatal=True)
apt_unhold(CONTAINERD_PACKAGE)
apt_install([CONTAINERD_PACKAGE, "--reinstall"], fatal=True)
with _apt_restart_services(restart):
apt_install([CONTAINERD_PACKAGE, "--reinstall"], fatal=True)
apt_hold(CONTAINERD_PACKAGE)
set_state("containerd.installed")
remove_state("containerd.resource.evaluated")
Expand Down
1 change: 0 additions & 1 deletion tests/integration/test_containerd_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from utils import JujuRun
import yaml


log = logging.getLogger(__name__)


Expand Down
1 change: 0 additions & 1 deletion tests/unit/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import charms.unit_test


charms.unit_test.patch_reactive()
charms.unit_test.patch_module("requests")
40 changes: 40 additions & 0 deletions tests/unit/test_containerd_reactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,43 @@ def test_needs_gpu_reboot_true(check_output, is_state, remove_state, set_state):
check_output.assert_called_once_with(["nvidia-smi"], stderr=STDOUT)
set_state.assert_called_once_with("containerd.nvidia.needs_reboot")
remove_state.assert_not_called()


@pytest.mark.parametrize(
"restart,initial_env,expected_during,expected_after",
[
(True, {}, None, None),
(False, {}, "1", None),
(True, {"NEEDRESTART_SUSPEND": "original"}, None, "original"),
(False, {"NEEDRESTART_SUSPEND": "original"}, "1", "original"),
],
ids=[
"restart=True, no initial env",
"restart=False, no initial env",
"restart=True, with initial env",
"restart=False, with initial env",
],
)
@mock.patch.object(containerd, "log")
@mock.patch.dict(os.environ, {}, clear=True)
def test_apt_restart_services(mock_log, restart, initial_env, expected_during, expected_after):
"""Verify _apt_restart_services behavior with various configurations."""
# Setup initial environment from parameters
os.environ.update(initial_env)

with containerd._apt_restart_services(restart=restart):
# Check value during context
if expected_during is None:
assert "NEEDRESTART_SUSPEND" not in os.environ
else:
assert os.environ["NEEDRESTART_SUSPEND"] == expected_during

# Check value after context
if expected_after is None:
assert "NEEDRESTART_SUSPEND" not in os.environ
else:
assert os.environ["NEEDRESTART_SUSPEND"] == expected_after

# Check log call
log_fmt = "" if restart else "not "
mock_log.assert_called_once_with(f"Services will {log_fmt}be restarted after apt operations.")