Skip to content

Commit 508cf9b

Browse files
authored
NAPPS-968 | Adds device.install_mode to base driver and ios driver (#395)
* adds device.install_mode to base driver and ios driver * pylint * Updates per review
1 parent 8c636a2 commit 508cf9b

6 files changed

Lines changed: 235 additions & 16 deletions

File tree

changes/395.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added an ``install_mode`` property to ``BaseDevice`` (abstract) and ``IOSDevice``; the IOS implementation returns ``True`` when the device boots from ``packages.conf``.

changes/395.deprecated

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecated the ``install_mode`` argument to ``IOSDevice.install_os``; install mode is now derived from the device's ``boot_options`` via the new ``install_mode`` property and will be removed in a future release.

pyntc/devices/base_device.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,20 @@ def verify_file(self, checksum, filename, hashing_algorithm="md5", **kwargs):
450450
"""
451451
raise NotImplementedError
452452

453+
@property
454+
def install_mode(self):
455+
"""Indicate whether the device is operating in install mode.
456+
457+
Drivers override this to derive the value from the device's current boot
458+
configuration. Used by ``install_os`` to choose between install-mode and
459+
legacy upgrade procedures.
460+
461+
Returns:
462+
(bool): True when the device boots from an install-mode bundle
463+
(e.g., ``packages.conf`` on IOS-XE), False otherwise.
464+
"""
465+
raise NotImplementedError
466+
453467
def install_os(self, image_name, reboot=True, **vendor_specifics):
454468
"""Install the OS from specified image_name.
455469

pyntc/devices/ios_device.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import re
55
import time
6+
import warnings
67

78
from netmiko import ConnectHandler, FileTransfer
89
from netmiko.exceptions import ReadTimeout
@@ -319,6 +320,17 @@ def boot_options(self):
319320
log.debug("Host %s: the boot options are {dict(sys=boot_image)}", self.host)
320321
return {"sys": boot_image}
321322

323+
@property
324+
def install_mode(self):
325+
"""Return whether the device is currently booted in install mode.
326+
327+
Returns:
328+
(bool): True when the current boot image equals
329+
:data:`INSTALL_MODE_FILE_NAME` (i.e., ``packages.conf``),
330+
False otherwise.
331+
"""
332+
return self.boot_options.get("sys") == INSTALL_MODE_FILE_NAME
333+
322334
def checkpoint(self, checkpoint_file):
323335
"""Create checkpoint file.
324336
@@ -877,13 +889,28 @@ def file_copy_remote_exists(self, src, dest=None, file_system=None):
877889
log.debug("Host %s: File %s does not already exist on remote.", self.host, src)
878890
return False
879891

880-
def install_os(self, image_name, reboot=True, install_mode=False, read_timeout=2000, **vendor_specifics):
892+
def _resolve_install_mode(self, install_mode):
893+
"""Return the effective install_mode flag, warning if the caller passed it explicitly."""
894+
if install_mode is None:
895+
return self.install_mode
896+
warnings.warn(
897+
"The install_mode argument to install_os is deprecated; install mode is now "
898+
"derived from the device's boot_options via the install_mode property.",
899+
DeprecationWarning,
900+
)
901+
return install_mode
902+
903+
def install_os(self, image_name, reboot=True, install_mode=None, read_timeout=2000, **vendor_specifics):
881904
"""Installs the prescribed Network OS, which must be present before issuing this command.
882905
883906
Args:
884907
image_name (str): Name of the IOS image to boot into
885908
reboot (bool): Whether to reboot the device after setting the boot options. Defaults to true.
886-
install_mode (bool, optional): Uses newer install method on devices. Defaults to False.
909+
install_mode (bool, optional): **Deprecated.** Whether to use the newer install-mode
910+
upgrade procedure. When omitted (the default), the value is derived from
911+
:attr:`install_mode`, which reads the device's current boot configuration.
912+
Passing the argument explicitly still works but emits a ``DeprecationWarning``
913+
and will be removed in a future release.
887914
read_timeout (int, optional): Netmiko timeout when waiting for device prompt. Default 2000.
888915
vendor_specifics (dict, optional): Vendor specific arguments to pass to the install command.
889916
@@ -893,14 +920,15 @@ def install_os(self, image_name, reboot=True, install_mode=False, read_timeout=2
893920
Returns:
894921
(bool): False if no install is needed, true if the install completes successfully
895922
"""
923+
use_install_mode = self._resolve_install_mode(install_mode)
896924
timeout = vendor_specifics.get("timeout", 3600)
897925
if not self._image_booted(image_name):
898-
if install_mode and not reboot:
926+
if use_install_mode and not reboot:
899927
raise ValueError(
900928
"IOS devices automatically reboot after installation when using install mode but the reboot argument was set to false."
901929
)
902930

903-
if install_mode:
931+
if use_install_mode:
904932
# Change boot statement to be boot system <flash>:packages.conf
905933
self.set_boot_options(INSTALL_MODE_FILE_NAME, **vendor_specifics)
906934

@@ -942,7 +970,7 @@ def install_os(self, image_name, reboot=True, install_mode=False, read_timeout=2
942970
self._wait_for_device_reboot(timeout=timeout)
943971

944972
# Set FastCLI back to originally set when using install mode
945-
if install_mode:
973+
if use_install_mode:
946974
image_name = INSTALL_MODE_FILE_NAME
947975
# Verify the OS level
948976
if not self._image_booted(image_name):
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Tests for the abstract :class:`pyntc.devices.base_device.BaseDevice` contract."""
2+
3+
import pytest
4+
5+
from pyntc.devices.base_device import BaseDevice
6+
7+
8+
@pytest.fixture
9+
def base_device():
10+
return BaseDevice(host="host", username="user", password="pass")
11+
12+
13+
def test_install_mode_raises_not_implemented(base_device):
14+
with pytest.raises(NotImplementedError):
15+
_ = base_device.install_mode

0 commit comments

Comments
 (0)