@@ -228,18 +228,53 @@ def _uptime_to_string(self, uptime_full_string):
228228 days , hours , minutes , seconds = self ._uptime_components (uptime_full_string )
229229 return f"{ days :02d} :{ hours :02d} :{ minutes :02d} :{ seconds :02d} "
230230
231- def _wait_for_device_reboot (self , timeout = 3600 ):
231+ def _wait_for_device_reboot (self , original_uptime , timeout = 7200 ):
232+ """Block until the device reboots and accepts a fresh connection.
233+
234+ Drops the existing NETCONF session and polls for the device to come back.
235+ The reboot is considered complete when a new connection succeeds and the
236+ device reports an uptime lower than ``original_uptime`` (i.e., it has
237+ booted since the reboot was issued).
238+
239+ The pre-reboot session must be discarded first: once the device restarts,
240+ PyEZ still reports it as connected even though the transport is dead, so it
241+ is closed here to force each probe to establish a fresh connection.
242+
243+ Args:
244+ original_uptime (int): Device uptime in seconds captured before the reboot.
245+ timeout (int, optional): Max seconds to wait for the device to return. Defaults to 2 hours.
246+ """
232247 start = time .time ()
233- disconnected = False
248+
249+ # Drop the pre-reboot NETCONF session so subsequent probes can't read from
250+ # a stale connection PyEZ still reports as connected.
251+ try :
252+ self .close ()
253+ except Exception as close_exc : # pylint: disable=broad-exception-caught
254+ log .debug ("Host %s: Pre-reboot disconnect raised %s (ignored)." , self .host , close_exc )
255+
234256 while time .time () - start < timeout :
235- if disconnected :
236- try :
237- self .open ()
257+ try :
258+ self .open ()
259+ self ._uptime = None
260+ current_uptime = self .uptime
261+ if current_uptime is not None and current_uptime < original_uptime :
262+ log .info (
263+ "Host %s: Device rebooted (uptime %ss < pre-reboot %ss)." ,
264+ self .host ,
265+ current_uptime ,
266+ original_uptime ,
267+ )
238268 return
239- except : # noqa E722 # nosec # pylint: disable=bare-except
240- pass
241- elif not self .connected :
242- disconnected = True
269+ log .debug (
270+ "Host %s: Reachable but uptime %ss >= pre-reboot %ss; still waiting." ,
271+ self .host ,
272+ current_uptime ,
273+ original_uptime ,
274+ )
275+ except Exception as exc : # pylint: disable=broad-exception-caught
276+ log .debug ("Host %s: Reboot probe failed (%s); will retry." , self .host , exc )
277+ self .native .connected = False
243278 time .sleep (10 )
244279
245280 raise RebootTimeoutError (hostname = self .hostname , wait_time = timeout )
@@ -318,12 +353,14 @@ def uptime(self):
318353 Returns:
319354 (int): Device uptime in seconds.
320355 """
321- try :
322- native_uptime_string = self .native .facts ["RE0" ]["up_time" ]
323- except (AttributeError , TypeError ):
324- native_uptime_string = None
325-
326356 if self ._uptime is None :
357+ try :
358+ # Bust PyEZ's cached facts so a cold cache always reflects the live device.
359+ self .native .facts_refresh (keys = "RE0" )
360+ native_uptime_string = self .native .facts ["RE0" ]["up_time" ]
361+ except (AttributeError , TypeError , KeyError ):
362+ native_uptime_string = None
363+
327364 if native_uptime_string is not None :
328365 self ._uptime = self ._uptime_to_seconds (native_uptime_string )
329366
@@ -337,13 +374,16 @@ def uptime_string(self):
337374 Returns:
338375 (str): Device uptime.
339376 """
340- try :
341- native_uptime_string = self .native .facts ["RE0" ]["up_time" ]
342- except (AttributeError , TypeError ):
343- native_uptime_string = None
344-
345377 if self ._uptime_string is None :
346- self ._uptime_string = self ._uptime_to_string (native_uptime_string )
378+ try :
379+ # Bust PyEZ's cached facts so a cold cache always reflects the live device.
380+ self .native .facts_refresh (keys = "RE0" )
381+ native_uptime_string = self .native .facts ["RE0" ]["up_time" ]
382+ except (AttributeError , TypeError , KeyError ):
383+ native_uptime_string = None
384+
385+ if native_uptime_string is not None :
386+ self ._uptime_string = self ._uptime_to_string (native_uptime_string )
347387
348388 return self ._uptime_string
349389
@@ -505,13 +545,13 @@ def open(self):
505545 if not self .connected :
506546 self .native .open ()
507547
508- def reboot (self , wait_for_reload = False , timeout = 3600 , confirm = None ):
548+ def reboot (self , wait_for_reload = False , timeout = 7200 , confirm = None ):
509549 """
510550 Reload the controller or controller pair.
511551
512552 Args:
513553 wait_for_reload (bool): Whether the reboot method should wait for the device to come back up before returning. Defaults to False.
514- timeout (int, optional): Time in seconds to wait for the device to return after reboot. Defaults to 1 hour .
554+ timeout (int, optional): Time in seconds to wait for the device to return after reboot. Defaults to 2 hours .
515555 confirm (None): Not used. Deprecated since v0.17.0.
516556
517557 Example:
@@ -522,9 +562,16 @@ def reboot(self, wait_for_reload=False, timeout=3600, confirm=None):
522562 if confirm is not None :
523563 warnings .warn ("Passing 'confirm' to reboot method is deprecated." , DeprecationWarning )
524564
565+ self ._uptime = None
566+ original_uptime = self .uptime
567+ if original_uptime is None :
568+ raise CommandError (
569+ command = "reboot" ,
570+ message = "Could not determine pre-reboot uptime; refusing to wait for reload." ,
571+ )
525572 self .sw .reboot (in_min = 0 )
526573 if wait_for_reload :
527- self ._wait_for_device_reboot (timeout = timeout )
574+ self ._wait_for_device_reboot (original_uptime , timeout = timeout )
528575
529576 def rollback (self , filename ):
530577 """Rollback to a specific configuration file.
0 commit comments