From e101f17facfd0d99dedad8158d0af71720ea442b Mon Sep 17 00:00:00 2001 From: "Jason C. Nucciarone" Date: Sun, 6 Oct 2024 10:56:09 -0400 Subject: [PATCH 1/3] feat(editors): allow editors to set mode, user, and group of file Adds the function `set_file_permissions` which is used by the dump function of the editors to apply file mode, user, and group permissions to supported configuration files. This makes Slurm happy because then we don't need to default to root for everything, and can instead use the dedicated SlurmUser. Signed-off-by: Jason C. Nucciarone --- slurmutils/editors/cgroupconfig.py | 28 ++++++++++++++++++------- slurmutils/editors/editor.py | 29 +++++++++++++++++++++++--- slurmutils/editors/slurmconfig.py | 28 ++++++++++++++++++------- slurmutils/editors/slurmdbdconfig.py | 31 ++++++++++++++++++++-------- 4 files changed, 90 insertions(+), 26 deletions(-) diff --git a/slurmutils/editors/cgroupconfig.py b/slurmutils/editors/cgroupconfig.py index 965d94a..f7dbbf7 100644 --- a/slurmutils/editors/cgroupconfig.py +++ b/slurmutils/editors/cgroupconfig.py @@ -20,10 +20,10 @@ import os from contextlib import contextmanager from pathlib import Path -from typing import Union +from typing import Optional, Union from ..models import CgroupConfig -from .editor import dumper, loader +from .editor import dumper, loader, set_file_permissions _logger = logging.getLogger("slurmutils") @@ -40,9 +40,16 @@ def loads(content: str) -> CgroupConfig: @dumper -def dump(config: CgroupConfig, file: Union[str, os.PathLike]) -> None: +def dump( + config: CgroupConfig, + file: Union[str, os.PathLike], + mode: int = 0o644, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, +) -> None: """Dump `cgroup.conf` data model into cgroup.conf file.""" Path(file).write_text(dumps(config)) + set_file_permissions(file, mode, user, group) def dumps(config: CgroupConfig) -> str: @@ -51,12 +58,19 @@ def dumps(config: CgroupConfig) -> str: @contextmanager -def edit(file: Union[str, os.PathLike]) -> CgroupConfig: +def edit( + file: Union[str, os.PathLike], + mode: int = 0o644, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, +) -> CgroupConfig: """Edit a cgroup.conf file. Args: - file: Path to cgroup.conf file to edit. If cgroup.conf does - not exist at the specified file path, it will be created. + file: cgroup.conf file to edit. An empty config will be created if it does not exist. + mode: Access mode to apply to the cgroup.conf file. (Default: rw-r--r--) + user: User to set as owner of the cgroup.conf file. (Default: $USER) + group: Group to set as owner of the cgroup.conf file. (Default: None) """ if not os.path.exists(file): _logger.warning("file %s not found. creating new empty cgroup.conf configuration", file) @@ -65,4 +79,4 @@ def edit(file: Union[str, os.PathLike]) -> CgroupConfig: config = load(file) yield config - dump(config, file) + dump(config, file, mode, user, group) diff --git a/slurmutils/editors/editor.py b/slurmutils/editors/editor.py index becde3b..a38866c 100644 --- a/slurmutils/editors/editor.py +++ b/slurmutils/editors/editor.py @@ -15,19 +15,42 @@ """Base methods for Slurm workload manager configuration file editors.""" import logging +import os +import shutil from functools import wraps -from os import path +from pathlib import Path +from typing import Optional, Union _logger = logging.getLogger("slurmutils") +def set_file_permissions( + file: Union[str, os.PathLike], + mode: int = 0o644, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, +) -> None: + """Set file permissions to configuration file. + + Args: + file: File to apply permission settings to. + mode: Access mode to apply to file. (Default: rw-r--r--) + user: User to set as owner of file. (Default: $USER) + group: Group to set as owner of file. (Default: None) + """ + Path(file).chmod(mode=mode) + if user is None: + user = os.getuid() + shutil.chown(file, user, group) + + def loader(func): """Wrap function that loads configuration data from file.""" @wraps(func) def wrapper(*args, **kwargs): fin = args[0] - if not path.exists(fin): + if not os.path.exists(fin): raise FileNotFoundError(f"could not locate {fin}") _logger.debug("reading contents of %s", fin) @@ -42,7 +65,7 @@ def dumper(func): @wraps(func) def wrapper(*args, **kwargs): fout = args[1] - if path.exists(fout): + if os.path.exists(fout): _logger.debug("overwriting current contents of %s", fout) _logger.debug("updating contents of %s", fout) diff --git a/slurmutils/editors/slurmconfig.py b/slurmutils/editors/slurmconfig.py index 1f0aac3..b159e87 100644 --- a/slurmutils/editors/slurmconfig.py +++ b/slurmutils/editors/slurmconfig.py @@ -20,10 +20,10 @@ import os from contextlib import contextmanager from pathlib import Path -from typing import Union +from typing import Optional, Union from ..models import SlurmConfig -from .editor import dumper, loader +from .editor import dumper, loader, set_file_permissions _logger = logging.getLogger("slurmutils") @@ -40,9 +40,16 @@ def loads(content: str) -> SlurmConfig: @dumper -def dump(config: SlurmConfig, file: Union[str, os.PathLike]) -> None: +def dump( + config: SlurmConfig, + file: Union[str, os.PathLike], + mode: int = 0o644, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, +) -> None: """Dump `slurm.conf` data model into slurm.conf file.""" Path(file).write_text(dumps(config)) + set_file_permissions(file, mode, user, group) def dumps(config: SlurmConfig) -> str: @@ -51,12 +58,19 @@ def dumps(config: SlurmConfig) -> str: @contextmanager -def edit(file: Union[str, os.PathLike]) -> SlurmConfig: +def edit( + file: Union[str, os.PathLike], + mode: int = 0o644, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, +) -> SlurmConfig: """Edit a slurm.conf file. Args: - file: Path to slurm.conf file to edit. If slurm.conf does - not exist at the specified file path, it will be created. + file: slurm.conf file to edit. An empty config will be created if it does not exist. + mode: Access mode to apply to the slurm.conf file. (Default: rw-r--r--) + user: User to set as owner of the slurm.conf file. (Default: $USER) + group: Group to set as owner of the slurm.conf file. (Default: None) """ if not os.path.exists(file): _logger.warning("file %s not found. creating new empty slurm.conf configuration", file) @@ -65,4 +79,4 @@ def edit(file: Union[str, os.PathLike]) -> SlurmConfig: config = load(file) yield config - dump(config, file) + dump(config, file, mode, user, group) diff --git a/slurmutils/editors/slurmdbdconfig.py b/slurmutils/editors/slurmdbdconfig.py index 70f9a04..3229bc5 100644 --- a/slurmutils/editors/slurmdbdconfig.py +++ b/slurmutils/editors/slurmdbdconfig.py @@ -20,11 +20,10 @@ import os from contextlib import contextmanager from pathlib import Path -from typing import Union +from typing import Optional, Union -from slurmutils.models import SlurmdbdConfig - -from .editor import dumper, loader +from ..models import SlurmdbdConfig +from .editor import dumper, loader, set_file_permissions _logger = logging.getLogger("slurmutils") @@ -41,9 +40,16 @@ def loads(content: str) -> SlurmdbdConfig: @dumper -def dump(config: SlurmdbdConfig, file: Union[str, os.PathLike]) -> None: +def dump( + config: SlurmdbdConfig, + file: Union[str, os.PathLike], + mode: int = 0o644, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, +) -> None: """Dump `slurmdbd.conf` data model into slurmdbd.conf file.""" Path(file).write_text(dumps(config)) + set_file_permissions(file, mode, user, group) def dumps(config: SlurmdbdConfig) -> str: @@ -52,12 +58,19 @@ def dumps(config: SlurmdbdConfig) -> str: @contextmanager -def edit(file: Union[str, os.PathLike]) -> SlurmdbdConfig: +def edit( + file: Union[str, os.PathLike], + mode: int = 0o644, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, +) -> SlurmdbdConfig: """Edit a slurmdbd.conf file. Args: - file: Path to slurmdbd.conf file to edit. If slurmdbd.conf does - not exist at the specified file path, it will be created. + file: slurmdbd.conf file to edit. An empty config will be created if it does not exist. + mode: Access mode to apply to the slurmdbd.conf file. (Default: rw-r--r--) + user: User to set as owner of the slurmdbd.conf file. (Default: $USER) + group: Group to set as owner of the slurmdbd.conf file. (Default: None) """ if not os.path.exists(file): _logger.warning("file %s not found. creating new empty slurmdbd.conf configuration", file) @@ -66,4 +79,4 @@ def edit(file: Union[str, os.PathLike]) -> SlurmdbdConfig: config = load(file) yield config - dump(config, file) + dump(config, file, mode, user, group) From 695c3d226fe938ac678cd71027ead6f9e2c00c8c Mon Sep 17 00:00:00 2001 From: "Jason C. Nucciarone" Date: Sun, 6 Oct 2024 10:59:04 -0400 Subject: [PATCH 2/3] tests(editors): add unit test for `set_file_permissions` Also improves coverage of the `editor` module. Signed-off-by: Jason C. Nucciarone --- tests/unit/editors/constants.py | 108 ++++++++++++++++++++++++++++++ tests/unit/editors/test_editor.py | 54 +++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 tests/unit/editors/constants.py create mode 100644 tests/unit/editors/test_editor.py diff --git a/tests/unit/editors/constants.py b/tests/unit/editors/constants.py new file mode 100644 index 0000000..2081302 --- /dev/null +++ b/tests/unit/editors/constants.py @@ -0,0 +1,108 @@ +# Copyright 2024 Canonical Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 3 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +EXAMPLE_SLURM_CONFIG = """# +# `slurm.conf` file generated at 2024-01-30 17:18:36.171652 by slurmutils. +# +SlurmctldHost=juju-c9fc6f-0(10.152.28.20) +SlurmctldHost=juju-c9fc6f-1(10.152.28.100) + +ClusterName=charmed-hpc +AuthType=auth/munge +Epilog=/usr/local/slurm/epilog +Prolog=/usr/local/slurm/prolog +FirstJobId=65536 +InactiveLimit=120 +JobCompType=jobcomp/filetxt +JobCompLoc=/var/log/slurm/jobcomp +KillWait=30 +MaxJobCount=10000 +MinJobAge=3600 +PluginDir=/usr/local/lib:/usr/local/slurm/lib +ReturnToService=0 +SchedulerType=sched/backfill +SlurmctldLogFile=/var/log/slurm/slurmctld.log +SlurmdLogFile=/var/log/slurm/slurmd.log +SlurmctldPort=7002 +SlurmdPort=7003 +SlurmdSpoolDir=/var/spool/slurmd.spool +StateSaveLocation=/var/spool/slurm.state +SwitchType=switch/none +TmpFS=/tmp +WaitTime=30 + +# +# Node configurations +# +NodeName=juju-c9fc6f-2 NodeAddr=10.152.28.48 CPUs=1 RealMemory=1000 TmpDisk=10000 +NodeName=juju-c9fc6f-3 NodeAddr=10.152.28.49 CPUs=1 RealMemory=1000 TmpDisk=10000 +NodeName=juju-c9fc6f-4 NodeAddr=10.152.28.50 CPUs=1 RealMemory=1000 TmpDisk=10000 +NodeName=juju-c9fc6f-5 NodeAddr=10.152.28.51 CPUs=1 RealMemory=1000 TmpDisk=10000 + +# +# Down node configurations +# +DownNodes=juju-c9fc6f-5 State=DOWN Reason="Maintenance Mode" + +# +# Partition configurations +# +PartitionName=DEFAULT MaxTime=30 MaxNodes=10 State=UP +PartitionName=batch Nodes=juju-c9fc6f-2,juju-c9fc6f-3,juju-c9fc6f-4,juju-c9fc6f-5 MinNodes=4 MaxTime=120 AllowGroups=admin +""" + +EXAMPLE_SLURMDBD_CONFIG = """# +# `slurmdbd.conf` file generated at 2024-01-30 17:18:36.171652 by slurmutils. +# +ArchiveEvents=yes +ArchiveJobs=yes +ArchiveResvs=yes +ArchiveSteps=no +ArchiveTXN=no +ArchiveUsage=no +ArchiveScript=/usr/sbin/slurm.dbd.archive +AuthInfo=/var/run/munge/munge.socket.2 +AuthType=auth/munge +AuthAltTypes=auth/jwt +AuthAltParameters=jwt_key=16549684561684@ +DbdHost=slurmdbd-0 +DbdBackupHost=slurmdbd-1 +DebugLevel=info +PluginDir=/all/these/cool/plugins +PurgeEventAfter=1month +PurgeJobAfter=12month +PurgeResvAfter=1month +PurgeStepAfter=1month +PurgeSuspendAfter=1month +PurgeTXNAfter=12month +PurgeUsageAfter=24month +LogFile=/var/log/slurmdbd.log +PidFile=/var/run/slurmdbd.pid +SlurmUser=slurm +StoragePass=supersecretpasswd +StorageType=accounting_storage/mysql +StorageUser=slurm +StorageHost=127.0.0.1 +StoragePort=3306 +StorageLoc=slurm_acct_db +""" + +EXAMPLE_CGROUP_CONFIG = """# +# `cgroup.conf` file generated at 2024-09-18 15:10:44.652017 by slurmutils. +# +ConstrainCores=yes +ConstrainDevices=yes +ConstrainRAMSpace=yes +ConstrainSwapSpace=yes +""" diff --git a/tests/unit/editors/test_editor.py b/tests/unit/editors/test_editor.py new file mode 100644 index 0000000..4fdc55c --- /dev/null +++ b/tests/unit/editors/test_editor.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 3 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""Unit tests for base editor functions.""" + +import os +import stat +from pathlib import Path + +from constants import EXAMPLE_SLURM_CONFIG +from pyfakefs.fake_filesystem_unittest import TestCase + +from slurmutils.editors import slurmconfig +from slurmutils.editors.editor import set_file_permissions + + +class TestBaseEditor(TestCase): + """Unit tests for base editor functions.""" + + def setUp(self) -> None: + self.setUpPyfakefs() + self.fs.create_file("/etc/slurm/slurm.conf", contents=EXAMPLE_SLURM_CONFIG) + + def test_set_file_permissions(self) -> None: + """Test the `set_file_permissions` function.""" + target = Path("/etc/slurm/slurm.conf") + set_file_permissions(target, mode=0o600, user=os.getuid(), group=os.getgid()) + f_info = target.stat() + self.assertEqual("-rw-------", stat.filemode(f_info.st_mode)) + self.assertEqual(os.getuid(), f_info.st_uid) + self.assertEqual(os.getgid(), f_info.st_gid) + + def test_loader_fail(self) -> None: + """Test that `FileNotFoundError` is raised when attempting to load non-existent file.""" + self.fs.remove("/etc/slurm/slurm.conf") + with self.assertRaises(FileNotFoundError): + slurmconfig.load("/etc/slurm/slurm.conf") + + def test_dumper_first_write(self) -> None: + """Test that `dumper` succeeds when there is no pre-existing config file.""" + self.fs.remove("/etc/slurm/slurm.conf") + slurmconfig.dump(slurmconfig.loads(EXAMPLE_SLURM_CONFIG), "/etc/slurm/slurm.conf") From 33866634d7c900a5e38d9a576a4ae498f8633bd8 Mon Sep 17 00:00:00 2001 From: "Jason C. Nucciarone" Date: Sun, 6 Oct 2024 10:59:59 -0400 Subject: [PATCH 3/3] tests(editors): use `pyfakefs` instead of creating temp file Use `pyfakefs` to create a mock Slurm configuration file where we would expect it to be located on a node. Also helps for testing file permissions as we don't need to mess with privileges on the host filesystem. Signed-off-by: Jason C. Nucciarone --- tests/unit/editors/test_cgroupconfig.py | 32 +++------ tests/unit/editors/test_slurmconfig.py | 81 +++++------------------ tests/unit/editors/test_slurmdbdconfig.py | 59 +++-------------- tox.ini | 1 + 4 files changed, 38 insertions(+), 135 deletions(-) diff --git a/tests/unit/editors/test_cgroupconfig.py b/tests/unit/editors/test_cgroupconfig.py index b0c8082..c30cc02 100644 --- a/tests/unit/editors/test_cgroupconfig.py +++ b/tests/unit/editors/test_cgroupconfig.py @@ -15,30 +15,23 @@ """Unit tests for the cgroup.conf editor.""" -import unittest -from pathlib import Path -from slurmutils.editors import cgroupconfig +from constants import EXAMPLE_CGROUP_CONFIG +from pyfakefs.fake_filesystem_unittest import TestCase -EXAMPLE_CGROUP_CONF = """# -# `cgroup.conf` file generated at 2024-09-18 15:10:44.652017 by slurmutils. -# -ConstrainCores=yes -ConstrainDevices=yes -ConstrainRAMSpace=yes -ConstrainSwapSpace=yes -""" +from slurmutils.editors import cgroupconfig -class TestCgroupConfigEditor(unittest.TestCase): +class TestCgroupConfigEditor(TestCase): """Unit tests for cgroup.conf file editor.""" def setUp(self) -> None: - Path("cgroup.conf").write_text(EXAMPLE_CGROUP_CONF) + self.setUpPyfakefs() + self.fs.create_file("/etc/slurm/cgroup.conf", contents=EXAMPLE_CGROUP_CONFIG) def test_loads(self) -> None: """Test `loads` method of the cgroupconfig module.""" - config = cgroupconfig.loads(EXAMPLE_CGROUP_CONF) + config = cgroupconfig.loads(EXAMPLE_CGROUP_CONFIG) self.assertEqual(config.constrain_cores, "yes") self.assertEqual(config.constrain_devices, "yes") self.assertEqual(config.constrain_ram_space, "yes") @@ -46,24 +39,21 @@ def test_loads(self) -> None: def test_dumps(self) -> None: """Test `dumps` method of the cgroupconfig module.""" - config = cgroupconfig.loads(EXAMPLE_CGROUP_CONF) + config = cgroupconfig.loads(EXAMPLE_CGROUP_CONFIG) # The new config and old config should not be equal since the # timestamps in the header will be different. - self.assertNotEqual(cgroupconfig.dumps(config), EXAMPLE_CGROUP_CONF) + self.assertNotEqual(cgroupconfig.dumps(config), EXAMPLE_CGROUP_CONFIG) def test_edit(self) -> None: """Test `edit` context manager from the cgroupconfig module.""" - with cgroupconfig.edit("cgroup.conf") as config: + with cgroupconfig.edit("/etc/slurm/cgroup.conf") as config: config.constrain_cores = "no" config.constrain_devices = "no" config.constrain_ram_space = "no" config.constrain_swap_space = "no" - config = cgroupconfig.load("cgroup.conf") + config = cgroupconfig.load("/etc/slurm/cgroup.conf") self.assertEqual(config.constrain_cores, "no") self.assertEqual(config.constrain_devices, "no") self.assertEqual(config.constrain_ram_space, "no") self.assertEqual(config.constrain_swap_space, "no") - - def tearDown(self) -> None: - Path("cgroup.conf").unlink() diff --git a/tests/unit/editors/test_slurmconfig.py b/tests/unit/editors/test_slurmconfig.py index 86abe5c..037218d 100644 --- a/tests/unit/editors/test_slurmconfig.py +++ b/tests/unit/editors/test_slurmconfig.py @@ -15,72 +15,24 @@ """Unit tests for the slurm.conf editor.""" -import unittest -from pathlib import Path + +from constants import EXAMPLE_SLURM_CONFIG +from pyfakefs.fake_filesystem_unittest import TestCase from slurmutils.editors import slurmconfig from slurmutils.models import DownNodes, Node, Partition -example_slurm_conf = """# -# `slurm.conf` file generated at 2024-01-30 17:18:36.171652 by slurmutils. -# -SlurmctldHost=juju-c9fc6f-0(10.152.28.20) -SlurmctldHost=juju-c9fc6f-1(10.152.28.100) - -ClusterName=charmed-hpc -AuthType=auth/munge -Epilog=/usr/local/slurm/epilog -Prolog=/usr/local/slurm/prolog -FirstJobId=65536 -InactiveLimit=120 -JobCompType=jobcomp/filetxt -JobCompLoc=/var/log/slurm/jobcomp -KillWait=30 -MaxJobCount=10000 -MinJobAge=3600 -PluginDir=/usr/local/lib:/usr/local/slurm/lib -ReturnToService=0 -SchedulerType=sched/backfill -SlurmctldLogFile=/var/log/slurm/slurmctld.log -SlurmdLogFile=/var/log/slurm/slurmd.log -SlurmctldPort=7002 -SlurmdPort=7003 -SlurmdSpoolDir=/var/spool/slurmd.spool -StateSaveLocation=/var/spool/slurm.state -SwitchType=switch/none -TmpFS=/tmp -WaitTime=30 - -# -# Node configurations -# -NodeName=juju-c9fc6f-2 NodeAddr=10.152.28.48 CPUs=1 RealMemory=1000 TmpDisk=10000 -NodeName=juju-c9fc6f-3 NodeAddr=10.152.28.49 CPUs=1 RealMemory=1000 TmpDisk=10000 -NodeName=juju-c9fc6f-4 NodeAddr=10.152.28.50 CPUs=1 RealMemory=1000 TmpDisk=10000 -NodeName=juju-c9fc6f-5 NodeAddr=10.152.28.51 CPUs=1 RealMemory=1000 TmpDisk=10000 - -# -# Down node configurations -# -DownNodes=juju-c9fc6f-5 State=DOWN Reason="Maintenance Mode" -# -# Partition configurations -# -PartitionName=DEFAULT MaxTime=30 MaxNodes=10 State=UP -PartitionName=batch Nodes=juju-c9fc6f-2,juju-c9fc6f-3,juju-c9fc6f-4,juju-c9fc6f-5 MinNodes=4 MaxTime=120 AllowGroups=admin -""" - - -class TestSlurmConfigEditor(unittest.TestCase): +class TestSlurmConfigEditor(TestCase): """Unit tests for slurm.conf file editor.""" def setUp(self) -> None: - Path("slurm.conf").write_text(example_slurm_conf) + self.setUpPyfakefs() + self.fs.create_file("/etc/slurm/slurm.conf", contents=EXAMPLE_SLURM_CONFIG) def test_loads(self) -> None: """Test `loads` method of the slurmconfig module.""" - config = slurmconfig.loads(example_slurm_conf) + config = slurmconfig.loads(EXAMPLE_SLURM_CONFIG) self.assertListEqual( config.slurmctld_host, ["juju-c9fc6f-0(10.152.28.20)", "juju-c9fc6f-1(10.152.28.100)"] ) @@ -115,15 +67,15 @@ def test_loads(self) -> None: def test_dumps(self) -> None: """Test `dumps` method of the slurmconfig module.""" - config = slurmconfig.loads(example_slurm_conf) + config = slurmconfig.loads(EXAMPLE_SLURM_CONFIG) # The new config and old config should not be equal since the # timestamps in the header will be different. - self.assertNotEqual(slurmconfig.dumps(config), example_slurm_conf) + self.assertNotEqual(slurmconfig.dumps(config), EXAMPLE_SLURM_CONFIG) def test_edit(self) -> None: """Test `edit` context manager from the slurmconfig module.""" # Test descriptors for `slurm.conf` configuration options. - with slurmconfig.edit("slurm.conf") as config: + with slurmconfig.edit("/etc/slurm/slurm.conf") as config: del config.inactive_limit config.max_job_count = 20000 config.proctrack_type = "proctrack/linuxproc" @@ -132,7 +84,7 @@ def test_edit(self) -> None: del config.nodes["juju-c9fc6f-2"] config.nodes.update(new_node.dict()) - config = slurmconfig.load("slurm.conf") + config = slurmconfig.load("/etc/slurm/slurm.conf") self.assertIsNone(config.inactive_limit) self.assertEqual(config.max_job_count, "20000") self.assertEqual(config.proctrack_type, "proctrack/linuxproc") @@ -142,14 +94,14 @@ def test_edit(self) -> None: ) self.assertEqual(config.nodes["batch-0"]["NodeAddr"], "10.152.28.48") - with slurmconfig.edit("slurm.conf") as config: + with slurmconfig.edit("/etc/slurm/slurm.conf") as config: del config.nodes del config.frontend_nodes del config.down_nodes del config.node_sets del config.partitions - config = slurmconfig.load("slurm.conf") + config = slurmconfig.load("/etc/slurm/slurm.conf") self.assertDictEqual(config.nodes, {}) self.assertDictEqual(config.frontend_nodes, {}) self.assertListEqual(config.down_nodes, []) @@ -226,7 +178,7 @@ def test_edit(self) -> None: ), ] - with slurmconfig.edit("slurm.conf") as config: + with slurmconfig.edit("/etc/slurm/slurm.conf") as config: for node in new_nodes: config.nodes.update(node.dict()) @@ -287,7 +239,7 @@ def test_update(self): }, } - config = slurmconfig.loads(example_slurm_conf) + config = slurmconfig.loads(EXAMPLE_SLURM_CONFIG) updates = slurmconfig.SlurmConfig.from_dict(config_updates) config.update(updates) @@ -378,6 +330,3 @@ def test_update(self): }, }, ) - - def tearDown(self): - Path("slurm.conf").unlink() diff --git a/tests/unit/editors/test_slurmdbdconfig.py b/tests/unit/editors/test_slurmdbdconfig.py index 4bbf1be..c8b6446 100644 --- a/tests/unit/editors/test_slurmdbdconfig.py +++ b/tests/unit/editors/test_slurmdbdconfig.py @@ -15,57 +15,23 @@ """Unit tests for the slurmdbd.conf editor.""" -import unittest -from pathlib import Path -from slurmutils.editors import slurmdbdconfig +from constants import EXAMPLE_SLURMDBD_CONFIG +from pyfakefs.fake_filesystem_unittest import TestCase -example_slurmdbd_conf = """# -# `slurmdbd.conf` file generated at 2024-01-30 17:18:36.171652 by slurmutils. -# -ArchiveEvents=yes -ArchiveJobs=yes -ArchiveResvs=yes -ArchiveSteps=no -ArchiveTXN=no -ArchiveUsage=no -ArchiveScript=/usr/sbin/slurm.dbd.archive -AuthInfo=/var/run/munge/munge.socket.2 -AuthType=auth/munge -AuthAltTypes=auth/jwt -AuthAltParameters=jwt_key=16549684561684@ -DbdHost=slurmdbd-0 -DbdBackupHost=slurmdbd-1 -DebugLevel=info -PluginDir=/all/these/cool/plugins -PurgeEventAfter=1month -PurgeJobAfter=12month -PurgeResvAfter=1month -PurgeStepAfter=1month -PurgeSuspendAfter=1month -PurgeTXNAfter=12month -PurgeUsageAfter=24month -LogFile=/var/log/slurmdbd.log -PidFile=/var/run/slurmdbd.pid -SlurmUser=slurm -StoragePass=supersecretpasswd -StorageType=accounting_storage/mysql -StorageUser=slurm -StorageHost=127.0.0.1 -StoragePort=3306 -StorageLoc=slurm_acct_db -""" +from slurmutils.editors import slurmdbdconfig -class TestSlurmdbdConfigEditor(unittest.TestCase): +class TestSlurmdbdConfigEditor(TestCase): """Unit tests for the slurmdbd.conf file editor.""" def setUp(self) -> None: - Path("slurmdbd.conf").write_text(example_slurmdbd_conf) + self.setUpPyfakefs() + self.fs.create_file("/etc/slurm/slurmdbd.conf", contents=EXAMPLE_SLURMDBD_CONFIG) def test_loads(self) -> None: """Test `loads` method of the slurmdbdconfig module.""" - config = slurmdbdconfig.loads(example_slurmdbd_conf) + config = slurmdbdconfig.loads(EXAMPLE_SLURMDBD_CONFIG) self.assertListEqual(config.plugin_dir, ["/all/these/cool/plugins"]) self.assertDictEqual(config.auth_alt_parameters, {"jwt_key": "16549684561684@"}) self.assertEqual(config.slurm_user, "slurm") @@ -73,26 +39,23 @@ def test_loads(self) -> None: def test_dumps(self) -> None: """Test `dumps` method of the slurmdbdconfig module.""" - config = slurmdbdconfig.loads(example_slurmdbd_conf) + config = slurmdbdconfig.loads(EXAMPLE_SLURMDBD_CONFIG) # The new config and old config should not be equal since the # timestamps in the header will be different. - self.assertNotEqual(slurmdbdconfig.dumps(config), example_slurmdbd_conf) + self.assertNotEqual(slurmdbdconfig.dumps(config), EXAMPLE_SLURMDBD_CONFIG) def test_edit(self) -> None: """Test `edit` context manager from the slurmdbdconfig module.""" - with slurmdbdconfig.edit("slurmdbd.conf") as config: + with slurmdbdconfig.edit("/etc/slurm/slurmdbd.conf") as config: config.archive_usage = "yes" config.log_file = "/var/spool/slurmdbd.log" config.debug_flags = ["DB_EVENT", "DB_JOB", "DB_USAGE"] del config.auth_alt_types del config.auth_alt_parameters - config = slurmdbdconfig.load("slurmdbd.conf") + config = slurmdbdconfig.load("/etc/slurm/slurmdbd.conf") self.assertEqual(config.archive_usage, "yes") self.assertEqual(config.log_file, "/var/spool/slurmdbd.log") self.assertListEqual(config.debug_flags, ["DB_EVENT", "DB_JOB", "DB_USAGE"]) self.assertIsNone(config.auth_alt_types) self.assertIsNone(config.auth_alt_parameters) - - def tearDown(self) -> None: - Path("slurmdbd.conf").unlink() diff --git a/tox.ini b/tox.ini index ce0c397..f8389d3 100644 --- a/tox.ini +++ b/tox.ini @@ -52,6 +52,7 @@ commands = description = Run unit tests deps = pytest + pyfakefs coverage[toml] commands = coverage run \