Skip to content

Commit 66cfd25

Browse files
committed
rework frontend and topology api
1 parent ff205f7 commit 66cfd25

File tree

5 files changed

+708
-0
lines changed

5 files changed

+708
-0
lines changed

pyslurm/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
ReservationFlags,
2929
ReservationReoccurrence,
3030
)
31+
from pyslurm.core.topology import Topology
32+
from pyslurm.core.frontend import Frontend, Frontends
3133
from pyslurm.core import error
3234
from pyslurm.core.error import (
3335
PyslurmError,

pyslurm/core/frontend.pxd

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#########################################################################
2+
# reservation.pxd - interface to work with reservations in slurm
3+
#########################################################################
4+
# Copyright (C) 2025 Toni Harzendorf <[email protected]>
5+
#
6+
# This file is part of PySlurm
7+
#
8+
# PySlurm is free software; you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation; either version 2 of the License, or
11+
# (at your option) any later version.
12+
13+
# PySlurm is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License along
19+
# with PySlurm; if not, write to the Free Software Foundation, Inc.,
20+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21+
#
22+
# cython: c_string_type=unicode, c_string_encoding=default
23+
# cython: language_level=3
24+
25+
from libc.string cimport memcpy, memset
26+
from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t
27+
from libc.stdlib cimport free
28+
from pyslurm cimport slurm
29+
from pyslurm.slurm cimport (
30+
front_end_info_t,
31+
front_end_info_msg_t,
32+
update_front_end_msg_t,
33+
slurm_free_front_end_info_msg,
34+
slurm_load_front_end,
35+
slurm_update_front_end,
36+
slurm_init_update_front_end_msg,
37+
slurm_node_state_string_complete,
38+
xfree,
39+
try_xmalloc,
40+
)
41+
from pyslurm.utils cimport cstr
42+
from pyslurm.utils cimport ctime
43+
from pyslurm.utils.ctime cimport time_t
44+
from pyslurm.utils.uint cimport (
45+
u32,
46+
u32_parse,
47+
u64_parse_bool_flag,
48+
u64_set_bool_flag,
49+
)
50+
from pyslurm.xcollections cimport MultiClusterMap
51+
52+
53+
cdef extern void slurm_free_update_front_end_msg(update_front_end_msg_t *msg)
54+
cdef extern void slurm_free_front_end_info_members(front_end_info_t *front_end)
55+
56+
57+
cdef class Frontends(dict):
58+
cdef:
59+
front_end_info_msg_t *info
60+
front_end_info_t tmp_info
61+
62+
63+
cdef class Frontend:
64+
cdef:
65+
front_end_info_t *info
66+
update_front_end_msg_t *umsg
67+
68+
cdef readonly cluster
69+
70+
@staticmethod
71+
cdef Frontend from_ptr(front_end_info_t *in_ptr)

pyslurm/core/frontend.pyx

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
#########################################################################
2+
# frontend.pyx - slurm frontend api
3+
#########################################################################
4+
# Copyright (C) 2025 Toni Harzendorf <[email protected]>
5+
#
6+
# This file is part of PySlurm
7+
#
8+
# PySlurm is free software; you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation; either version 2 of the License, or
11+
# (at your option) any later version.
12+
13+
# PySlurm is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License along
19+
# with PySlurm; if not, write to the Free Software Foundation, Inc.,
20+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21+
#
22+
# cython: c_string_type=unicode, c_string_encoding=default
23+
# cython: language_level=3
24+
25+
from typing import Union, Any
26+
from pyslurm.utils import cstr
27+
from pyslurm.utils import ctime
28+
from pyslurm.utils.uint import u32_parse
29+
from pyslurm import settings
30+
from pyslurm import xcollections
31+
from pyslurm.utils.helpers import instance_to_dict, uid_to_name
32+
from pyslurm.core.node import _node_state_from_str
33+
from pyslurm.utils.ctime import _raw_time
34+
from pyslurm.core.error import RPCError verify_rpc
35+
36+
37+
cdef class Frontends(dict):
38+
39+
def __dealloc__(self):
40+
slurm_free_front_end_info_msg(self.info)
41+
self.info = NULL
42+
43+
def __cinit__(self):
44+
self.info = NULL
45+
46+
def __init__(self):
47+
super().__init__()
48+
49+
@staticmethod
50+
def load():
51+
"""Load all Frontends in the system.
52+
53+
Returns:
54+
(pyslurm.Frontends): Collection of [pyslurm.Frontend][]
55+
objects.
56+
57+
Raises:
58+
(pyslurm.RPCError): When getting all the Frontends from the
59+
slurmctld failed.
60+
"""
61+
cdef:
62+
Frontends frontends = Frontends()
63+
Frontend frontend
64+
65+
verify_rpc(slurm_load_front_end(0, &frontends.info))
66+
67+
memset(&frontends.tmp_info, 0, sizeof(front_end_info_t))
68+
for cnt in range(frontends.info.record_count):
69+
frontend = Frontend.from_ptr(&frontends.info.front_end_array[cnt])
70+
frontends.info.front_end_array[cnt] = frontends.tmp_info
71+
frontends[frontend.name] = frontend
72+
73+
frontends.info.record_count = 0
74+
return frontends
75+
76+
77+
78+
cdef class Frontend:
79+
80+
def __cinit__(self):
81+
self.info = NULL
82+
self.umsg = NULL
83+
84+
def __init__(self, name=None, **kwargs):
85+
self._alloc_impl()
86+
self.name = name
87+
self.cluster = settings.LOCAL_CLUSTER
88+
for k, v in kwargs.items():
89+
setattr(self, k, v)
90+
91+
def _alloc_impl(self):
92+
self._alloc_info()
93+
self._alloc_umsg()
94+
95+
def _alloc_info(self):
96+
if not self.info:
97+
self.info = <front_end_info_t*>try_xmalloc(sizeof(front_end_info_t))
98+
if not self.info:
99+
raise MemoryError("xmalloc failed for front_end_info_t")
100+
101+
def _alloc_umsg(self):
102+
if not self.umsg:
103+
self.umsg = <update_front_end_msg_t*>try_xmalloc(sizeof(update_front_end_msg_t))
104+
if not self.umsg:
105+
raise MemoryError("xmalloc failed for update_front_end_msg_t")
106+
slurm_init_update_front_end_msg(self.umsg)
107+
108+
def _dealloc_umsg(self):
109+
slurm_free_update_front_end_msg(self.umsg)
110+
self.umsg = NULL
111+
112+
def _dealloc_impl(self):
113+
self._dealloc_umsg()
114+
slurm_free_front_end_info_members(self.info)
115+
xfree(self.info)
116+
self.info = NULL
117+
118+
def __dealloc__(self):
119+
self._dealloc_impl()
120+
121+
def __setattr__(self, name, val):
122+
self._alloc_umsg()
123+
Frontend.__dict__[name].__set__(self, val)
124+
125+
def __repr__(self):
126+
return f'pyslurm.{self.__class__.__name__}({self.name})'
127+
128+
@staticmethod
129+
cdef Frontend from_ptr(front_end_info_t *in_ptr):
130+
cdef Frontend wrap = Frontend.__new__(Frontend)
131+
wrap._alloc_info()
132+
wrap.cluster = settings.LOCAL_CLUSTER
133+
memcpy(wrap.info, in_ptr, sizeof(front_end_info_t))
134+
return wrap
135+
136+
def _error_or_name(self):
137+
if not self.name:
138+
raise RPCError(msg="No Frontend name was specified. "
139+
"Did you set the `name` attribute on the "
140+
"Frontend instance?")
141+
return self.name
142+
143+
def to_dict(self):
144+
"""Frontend information formatted as a dictionary.
145+
146+
Returns:
147+
(dict): Frontend information as dict
148+
149+
Examples:
150+
>>> import pyslurm
151+
>>> frontend = pyslurm.Frontend.load("frontend")
152+
>>> frontend_dict = frontend.to_dict()
153+
>>> print(frontend_dict)
154+
"""
155+
return instance_to_dict(self)
156+
157+
@staticmethod
158+
def load(name):
159+
"""Load information for a specific Frontend.
160+
161+
Args:
162+
name (str):
163+
The name of the Frontend to load.
164+
165+
Returns:
166+
(pyslurm.Frontend): Returns a new Frontend instance.
167+
168+
Raises:
169+
(pyslurm.RPCError): If requesting the Frontend information from
170+
the slurmctld was not successful.
171+
172+
Examples:
173+
>>> import pyslurm
174+
>>> frontend = pyslurm.Frontend.load("frontend-node")
175+
"""
176+
frontend = Frontends.load().get(name)
177+
if not frontend:
178+
raise RPCError(msg=f"Frontend '{name}' doesn't exist")
179+
180+
return frontend
181+
182+
def modify(self, Frontend changes=None):
183+
"""Modify a Frontend.
184+
185+
Args:
186+
changes (pyslurm.Frontend, optional=None):
187+
Another Frontend object that contains all the changes to
188+
apply. This is optional - you can also directly modify a
189+
Frontend object and just call `modify()`, and the changes
190+
will be sent to `slurmctld`.
191+
192+
Raises:
193+
(pyslurm.RPCError): When updating the Frontend was not
194+
successful.
195+
196+
Examples:
197+
>>> import pyslurm
198+
>>>
199+
>>> frontend = pyslurm.Frontend.load("frontend-node1")
200+
>>> frontend.state = "DRAIN"
201+
>>> frontend.reason = "A Problem"
202+
>>>
203+
>>> # Now send the changes to the Controller:
204+
>>> frontend.modify()
205+
"""
206+
cdef Frontend updates = changes if changes is not None else self
207+
if not updates.umsg:
208+
return
209+
210+
updates._alloc_umsg()
211+
cstr.fmalloc(&updates.umsg.name, self._error_or_name())
212+
verify_rpc(slurm_update_front_end(updates.umsg))
213+
214+
# Make sure we clean the object from any previous changes.
215+
updates._dealloc_umsg()
216+
217+
@property
218+
def name(self):
219+
return cstr.to_unicode(self.info.name)
220+
221+
@name.setter
222+
def name(self, val):
223+
cstr.fmalloc2(&self.info.name, &self.umsg.name, val)
224+
225+
@property
226+
def denied_groups(self):
227+
return cstr.to_list(self.ptr.deny_groups, ["ALL"])
228+
229+
@property
230+
def denied_users(self):
231+
return cstr.to_list(self.ptr.deny_users, ["ALL"])
232+
233+
@property
234+
def allowed_groups(self):
235+
return cstr.to_list(self.ptr.allow_groups, ["ALL"])
236+
237+
@property
238+
def allowed_users(self):
239+
return cstr.to_list(self.ptr.allow_users, ["ALL"])
240+
241+
@property
242+
def boot_time(self):
243+
return _raw_time(self.info.boot_time)
244+
245+
@property
246+
def reason_time(self):
247+
return _raw_time(self.info.reason_time)
248+
249+
@property
250+
def reason(self):
251+
return cstr.to_unicode(self.info.reason)
252+
253+
@reason.setter
254+
def reason(self, val):
255+
cstr.fmalloc2(&self.info.reason, &self.umsg.reason, val)
256+
257+
@property
258+
def reason_user(self):
259+
return uid_to_name(self.info.reason_uid, err_on_invalid=False)
260+
261+
@property
262+
def slurmd_start_time(self):
263+
return _raw_time(self.info.slurmd_start_time)
264+
265+
@property
266+
def slurm_version(self):
267+
return cstr.to_unicode(self.info.version)
268+
269+
@property
270+
def _node_state(self):
271+
idle_cpus = self.idle_cpus
272+
state = self.info.node_state
273+
274+
if idle_cpus and idle_cpus != self.effective_cpus:
275+
# If we aren't idle but also not allocated, then set state to
276+
# MIXED.
277+
state &= slurm.NODE_STATE_FLAGS
278+
state |= slurm.NODE_STATE_MIXED
279+
280+
return state
281+
282+
@property
283+
def state(self):
284+
cdef char* state = slurm_node_state_string_complete(self._node_state)
285+
state_str = cstr.to_unicode(state)
286+
xfree(state)
287+
return state_str
288+
289+
@state.setter
290+
def state(self, val):
291+
self.umsg.node_state = self.info.node_state = _node_state_from_str(val)

0 commit comments

Comments
 (0)