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) 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_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_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") 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 \