@@ -53,6 +53,7 @@ cdef class Reservations(MultiClusterMap):
5353
5454 def __dealloc__ (self ):
5555 slurm_free_reservation_info_msg(self .info)
56+ self .info = NULL
5657
5758 def __cinit__ (self ):
5859 self .info = NULL
@@ -81,15 +82,19 @@ cdef class Reservations(MultiClusterMap):
8182
8283 verify_rpc(slurm_load_reservations(0 , & reservations.info))
8384
84- # zero-out a dummy reserve_info_t
85+ # prepare a dummy reserve_info_t struct.
8586 memset(& reservations.tmp_info, 0 , sizeof(reserve_info_t))
8687
8788 # Put each pointer into its own instance.
8889 for cnt in range (reservations.info.record_count):
8990 reservation = Reservation.from_ptr(& reservations.info.reservation_array[cnt])
9091
91- # Prevent double free if xmalloc fails mid-loop and a MemoryError
92- # is raised by replacing it with a zeroed-out reserve_info_t.
92+ # If we already parsed at least one Reservation, and if for some
93+ # reason a MemoryError is raised after parsing subsequent
94+ # reservations, invalid behaviour will be shown by Valgrind, since
95+ # the Memory for the already parsed Reservation will be freed
96+ # twice. So for all sucessfully parsed Reservations, replace it
97+ # with a dummy struct that will be skipped in case of error.
9398 reservations.info.reservation_array[cnt] = reservations.tmp_info
9499
95100 cluster = reservation.cluster
@@ -112,8 +117,6 @@ cdef class Reservation:
112117 def __init__ (self , name = None , **kwargs ):
113118 self ._alloc_impl()
114119 self .name = name
115- self .start_time = datetime.now()
116- self .duration = " 365-00:00:00"
117120 self .cluster = LOCAL_CLUSTER
118121 for k, v in kwargs.items():
119122 setattr (self , k, v)
@@ -133,14 +136,17 @@ cdef class Reservation:
133136 self .umsg = < resv_desc_msg_t* > try_xmalloc(sizeof(resv_desc_msg_t))
134137 if not self .umsg:
135138 raise MemoryError (" xmalloc failed for resv_desc_msg_t" )
136- print (" we here" )
137139 slurm_init_resv_desc_msg(self .umsg)
138140
139- def _dealloc_impl (self ):
141+ def _dealloc_umsg (self ):
140142 slurm_free_resv_desc_msg(self .umsg)
141143 self .umsg = NULL
144+
145+ def _dealloc_impl (self ):
146+ self ._dealloc_umsg()
142147 slurm_free_reserve_info_members(self .info)
143148 xfree(self .info)
149+ self .info = NULL
144150
145151 def __dealloc__ (self ):
146152 self ._dealloc_impl()
@@ -161,8 +167,6 @@ cdef class Reservation:
161167 cdef Reservation from_ptr(reserve_info_t * in_ptr):
162168 cdef Reservation wrap = Reservation.__new__ (Reservation)
163169 wrap._alloc_info()
164- wrap.passwd = {}
165- wrap.groups = {}
166170 wrap.cluster = LOCAL_CLUSTER
167171 memcpy(wrap.info, in_ptr, sizeof(reserve_info_t))
168172 return wrap
@@ -175,15 +179,16 @@ cdef class Reservation:
175179 return self .name
176180
177181 def to_dict (self ):
178- """ Node information formatted as a dictionary.
182+ """ Reservation information formatted as a dictionary.
179183
180184 Returns:
181- (dict): Node information as dict
185+ (dict): Reservation information as dict
182186
183187 Examples:
184188 >>> import pyslurm
185- >>> mynode = pyslurm.Node.load("mynode")
186- >>> mynode_dict = mynode.to_dict()
189+ >>> resv = pyslurm.Reservation.load("maintenance")
190+ >>> resv_dict = resv.to_dict()
191+ >>> print(resv_dict)
187192 """
188193 return instance_to_dict(self )
189194
@@ -215,6 +220,10 @@ cdef class Reservation:
215220 def create (self ):
216221 """ Create a Reservation.
217222
223+ If you did not specify atleast a `start_time` and `duration` or
224+ `end_time`, then by default the Reservation will start effective
225+ immediately, with a duration of one year.
226+
218227 Returns:
219228 (pyslurm.Reservation): This function returns the current
220229 Reservation instance object itself.
@@ -224,24 +233,29 @@ cdef class Reservation:
224233
225234 Examples:
226235 >>> import pyslurm
227- >>> reservation = pyslurm.Reservation("debug").create( )
236+ >>> resv = pyslurm.Reservation("debug")
228237 """
229238 cdef char * new_name = NULL
230239
240+ if not self .start_time or not (self .duration and self .end_time):
241+ raise RPCError(msg = " You must atleast specify a start_time, "
242+ " combined with an end_time or a duration." )
243+
231244 self .name = self ._error_or_name()
232245 new_name = slurm_create_reservation(self .umsg)
233246 free(new_name)
234247 verify_rpc(slurm_errno())
235248 return self
236249
237- def modify (self , Reservation changes ):
250+ def modify (self , Reservation changes = None ):
238251 """ Modify a Reservation.
239252
240253 Args:
241- changes (pyslurm.Reservation):
254+ changes (pyslurm.Reservation, optional=None ):
242255 Another Reservation object that contains all the changes to
243- apply. Check the `Other Parameters` of the Reservation class to
244- see which properties can be modified.
256+ apply. This is optional - you can also directly modify a
257+ Reservation object and just call `modify()`, and the changes
258+ will be sent to `slurmctld`.
245259
246260 Raises:
247261 (pyslurm.RPCError): When updating the Reservation was not
@@ -250,18 +264,27 @@ cdef class Reservation:
250264 Examples:
251265 >>> import pyslurm
252266 >>>
253- >>> mynode = pyslurm.Node.load("localhost")
254- >>> # Prepare the changes
255- >>> changes = pyslurm.Node(state="DRAIN", reason="DRAIN Reason")
256- >>> # Modify it
257- >>> mynode.modify(changes)
267+ >>> resv = pyslurm.Reservation.load("maintenance")
268+ >>> # Add 60 Minutes to the reservation
269+ >>> resv.duration += 60
270+ >>>
271+ >>> # You can also add a slurm timestring.
272+ >>> # For example, extend the duration by another day:
273+ >>> resv.duration += pyslurm.utils.timestr_to_mins("1-00:00:00")
274+ >>>
275+ >>> # Now send the changes to the Controller:
276+ >>> resv.modify()
258277 """
259- if not changes.umsg:
278+ cdef Reservation updates = changes if changes is not None else self
279+ if not updates.umsg:
260280 return
261281
262282 self ._error_or_name()
263- cstr.fmalloc(& changes.umsg.name, self .info.name)
264- verify_rpc(slurm_update_reservation(changes.umsg))
283+ cstr.fmalloc(& updates.umsg.name, self .info.name)
284+ verify_rpc(slurm_update_reservation(updates.umsg))
285+
286+ # Make sure we clean the object from any previous changes.
287+ updates._dealloc_umsg()
265288
266289 def delete (self ):
267290 """ Delete a Reservation.
@@ -303,15 +326,15 @@ cdef class Reservation:
303326 cstr.fmalloc2(& self .info.comment, & self .umsg.comment, val)
304327
305328 @property
306- def core_count (self ):
329+ def cpus (self ):
307330 return u32_parse(self .info.core_cnt, zero_is_noval = False )
308331
309- @core_count .setter
310- def core_count (self , val ):
332+ @cpus .setter
333+ def cpus (self , val ):
311334 self .info.core_cnt = self .umsg.core_cnt = int (val)
312335
313336 @property
314- def cores_by_node (self ):
337+ def cpus_by_node (self ):
315338 out = {}
316339 for i in range (self .info.core_spec_cnt):
317340 node = cstr.to_unicode(self .info.core_spec[i].node_name)
@@ -410,7 +433,7 @@ cdef class Reservation:
410433 def duration (self ):
411434 cdef time_t duration = 0
412435
413- if self .info.end_time >= self .info.start_time:
436+ if self .start_time and self . info.end_time >= self .info.start_time:
414437 duration = < time_t> ctime.difftime(self .info.end_time,
415438 self .info.start_time)
416439
@@ -419,7 +442,8 @@ cdef class Reservation:
419442 @duration.setter
420443 def duration (self , val ):
421444 val = timestr_to_mins(val)
422- self .umsg.duration = val
445+ if not self .start_time:
446+ self .start_time = datetime.now()
423447 self .end_time = self .start_time + (val * 60 )
424448
425449 @property
@@ -433,6 +457,11 @@ cdef class Reservation:
433457 def tres (self ):
434458 return cstr.to_dict(self .info.tres_str)
435459
460+ @tres.setter
461+ def tres (self , val ):
462+ cstr.fmalloc2(& self .info.tres_str, & self .umsg.tres_str,
463+ cstr.dict_to_str(val))
464+
436465 @property
437466 def users (self ):
438467 return cstr.to_list(self .info.users)
0 commit comments