Skip to content
This repository has been archived by the owner on Aug 9, 2024. It is now read-only.

Commit

Permalink
feat(charm): Add monkeypatching for CentOS 7 support
Browse files Browse the repository at this point in the history
Create monkepatches for slurmd utility methods override_service and override_default,
and SystemdNotices from the juju_systemd_notices charm library. These monkeypatches modify
these objects at charm runtime to enable CentOS 7 support within the slurmd operator.

The reason these monkeypatches are needed is because (a) CentOS operates slightly different
than Ubuntu and (b) CentOS 7 comes with Python 3.6 by default. A custom Python 3.8 interpreter
is built on the CentOS 7 machines, so additional patches are needed to point slurmd operator
service to this specific Python 3.8 interpreter and not the default Python 3.6 interpreter
that is /usr/bin/python3.

Signed-off-by: Jason C. Nucciarone <[email protected]>
  • Loading branch information
NucciTheBoss authored and jaimesouza committed Oct 23, 2023
1 parent b9b8287 commit 754fb6b
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 30 deletions.
8 changes: 7 additions & 1 deletion src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import logging
from pathlib import Path

import distro
from charms.fluentbit.v0.fluentbit import FluentbitClient
from charms.operator_libs_linux.v0.juju_systemd_notices import (
ServiceStartedEvent,
Expand All @@ -19,9 +20,14 @@
from ops.main import main
from ops.model import ActiveStatus, BlockedStatus, WaitingStatus
from slurm_ops_manager import SlurmManager
from utils import slurmd
from utils import monkeypatch, slurmd

logger = logging.getLogger(__name__)
if distro.id() == "centos":
logger.debug("Monkeypatching slurmd operator to support CentOS base")
SystemdNotices = monkeypatch.juju_systemd_notices(SystemdNotices)
slurmd = monkeypatch.slurmd_override_default(slurmd)
slurmd = monkeypatch.slurmd_override_service(slurmd)


class SlurmdCharm(CharmBase):
Expand Down
20 changes: 0 additions & 20 deletions src/constants.py

This file was deleted.

154 changes: 154 additions & 0 deletions src/utils/monkeypatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Copyright 2023 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.

"""Monkeypatch slurmd operator classes and methods to work on CentOS 7."""

import inspect
import logging
import textwrap
from pathlib import Path

from charms.operator_libs_linux.v0.juju_systemd_notices import (
SystemdNotices,
_daemon_reload,
_enable_service,
_start_service,
)
from charms.operator_libs_linux.v1 import systemd

from . import slurmd

_logger = logging.getLogger(__name__)


def juju_systemd_notices(notices: SystemdNotices) -> SystemdNotices:
"""Patch SystemdNotices object from juju_systemd_notices.
This function will patch the subscribe method of SystemdNotices
to use `/usr/bin/env python3.8` as the PYTHONEXE for running the
juju_systemd_notices daemon when on CentOS 7.
Args:
notices: SystemdNotices class reference to patch.
"""
_logger.debug("Monkeypatching SystemdNotices subscribe method")

def patched_subscribe(self) -> None: # pragma: nocover
_logger.debug("Generating systemd notice hooks for %s", self._services)
start_hooks = [Path(f"hooks/service-{service}-started") for service in self._services]
stop_hooks = [Path(f"hooks/service-{service}-stopped") for service in self._services]
for hook in start_hooks + stop_hooks:
if hook.exists():
_logger.debug("Hook %s already exists. Skipping...", hook.name)
else:
hook.symlink_to(self._charm.framework.charm_dir / "dispatch")

_logger.debug("Starting %s daemon", self._service_file.name)
if self._service_file.exists():
_logger.debug("Overwriting existing service file %s", self._service_file.name)
self._service_file.write_text(
textwrap.dedent(
f"""
[Unit]
Description=Juju systemd notices daemon
After=multi-user.target
[Service]
Type=simple
Restart=always
WorkingDirectory={self._charm.framework.charm_dir}
Environment="PYTHONPATH={self._charm.framework.charm_dir / "venv"}"
ExecStart=/usr/bin/env python3.8 {inspect.getfile(notices)} {self._charm.unit.name}
[Install]
WantedBy=multi-user.target
"""
).strip()
)
_logger.debug("Service file %s written. Reloading systemd", self._service_file.name)
_daemon_reload()
# Notices daemon is enabled so that the service will start even after machine reboot.
# This functionality is needed in the event that a charm is rebooted to apply updates.
_enable_service(self._service_file.name)
_start_service(self._service_file.name)
_logger.debug("Started %s daemon", self._service_file.name)

notices.subscribe = patched_subscribe
return notices


def slurmd_override_default(slurmd_module: slurmd) -> slurmd:
"""Patch override_default function for slurmd utility.
This function will patch the override_default function of the slurmd utility
to save
Args:
slurmd_module: slurmd utility module reference to patch.
"""
_logger.debug("Monkeypatching slurmd.override_default function")

def patched_override_default(host: str, port: int) -> None: # pragma: nocover
_logger.debug(f"Overriding /etc/default/slurmd with hostname {host} and port {port}")
Path("/etc/sysconfig/slurmd").write_text(
textwrap.dedent(
f"""
SLURMD_OPTIONS="--conf-server {host}:{port}"
PYTHONPATH={Path.cwd() / "lib"}
"""
).strip()
)

slurmd_module.override_default = patched_override_default
return slurmd_module


def slurmd_override_service(slurmd_module: slurmd) -> slurmd:
"""Patch override_service function from slurmd utility.
This function will patch the override_service function of the slurmd utility
to use `/usr/bin/env python3.8` as the PYTHONEXE for running the custom slurmd
ExecStart script in the slurmd service file.
Args:
slurmd_module: slurmd utility module reference to patch.
"""
_logger.debug("Monkeypatching slurmd.override_service function")

def patched_override_service() -> None: # pragma: nocover
_logger.debug("Overriding default slurmd service file")
if not (override_dir := Path("/etc/systemd/system/slurmd.service.d")).is_dir():
override_dir.mkdir()

overrides = override_dir / "99-slurmd-charm.conf"
overrides.write_text(
textwrap.dedent(
f"""
[Unit]
ConditionPathExists=
[Service]
Type=forking
ExecStart=
ExecStart=/usr/bin/env python3.8 {inspect.getfile(slurmd_module)}
LimitMEMLOCK=infinity
LimitNOFILE=1048576
TimeoutSec=900
"""
).strip()
)
systemd.daemon_reload()

slurmd_module.override_service = patched_override_service
return slurmd_module
11 changes: 2 additions & 9 deletions src/utils/slurmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from pathlib import Path

import charms.operator_libs_linux.v1.systemd as systemd
from constants import DISTRO_ID

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -60,7 +59,7 @@ def override_default(host: str, port: int) -> None:
textwrap.dedent(
f"""
SLURMD_OPTIONS="--conf-server {host}:{port}"
PYTHONPATH={Path.cwd() / "lib"}:{Path.cwd() / "venv"}
PYTHONPATH={Path.cwd() / "lib"}
"""
).strip()
)
Expand All @@ -78,12 +77,6 @@ def override_service() -> None:
if not (override_dir := Path("/etc/systemd/system/slurmd.service.d")).is_dir():
override_dir.mkdir()

# use an specific python version for CentOS 7
if DISTRO_ID == "centos":
python_bin = "/usr/bin/env python3.8"
else:
python_bin = "/usr/bin/python3"

overrides = override_dir / "99-slurmd-charm.conf"
overrides.write_text(
textwrap.dedent(
Expand All @@ -94,7 +87,7 @@ def override_service() -> None:
[Service]
Type=forking
ExecStart=
ExecStart={python_bin} {__file__}
ExecStart=/usr/bin/python3 {__file__}
LimitMEMLOCK=infinity
LimitNOFILE=1048576
TimeoutSec=900
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ def test_install_success(self, defer, *_) -> None:
self.assertTrue(self.harness.charm._stored.slurm_installed)
defer.assert_not_called()

@patch("slurm_ops_manager.SlurmManager.install")
@patch("pathlib.Path.read_text", return_value="v1.0.0")
@patch("ops.model.Unit.set_workload_version")
@patch("ops.model.Resources.fetch")
@patch("utils.slurmd.override_default")
@patch("utils.slurmd.override_service")
@patch("charms.operator_libs_linux.v0.juju_systemd_notices.SystemdNotices.subscribe")
@patch("ops.framework.EventBase.defer")
def test_install_success_centos(self, defer, *_) -> None:
"""Test install success behavior on CentOS."""
self.harness.charm.on.install.emit()
self.assertTrue(self.harness.charm._stored.slurm_installed)
defer.assert_not_called()

def test_service_slurmd_start(self) -> None:
"""Test service_slurmd_started event handler."""
self.harness.charm.on.service_slurmd_started.emit()
Expand Down

0 comments on commit 754fb6b

Please sign in to comment.