Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/395.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added an ``install_mode`` property to ``BaseDevice`` (abstract) and ``IOSDevice``; the IOS implementation returns ``True`` when the device boots from ``packages.conf``.
Comment thread
jtdub marked this conversation as resolved.
1 change: 1 addition & 0 deletions changes/395.deprecated
Original file line number Diff line number Diff line change
@@ -0,0 +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.
14 changes: 14 additions & 0 deletions pyntc/devices/base_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,20 @@ def verify_file(self, checksum, filename, hashing_algorithm="md5", **kwargs):
"""
raise NotImplementedError

@property
def install_mode(self):
"""Indicate whether the device is operating in install mode.

Drivers override this to derive the value from the device's current boot
configuration. Used by ``install_os`` to choose between install-mode and
legacy upgrade procedures.

Returns:
(bool): True when the device boots from an install-mode bundle
(e.g., ``packages.conf`` on IOS-XE), False otherwise.
"""
raise NotImplementedError

def install_os(self, image_name, reboot=True, **vendor_specifics):
"""Install the OS from specified image_name.

Expand Down
38 changes: 33 additions & 5 deletions pyntc/devices/ios_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import re
import time
import warnings

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

@property
def install_mode(self):
"""Return whether the device is currently booted in install mode.

Returns:
(bool): True when the current boot image equals
:data:`INSTALL_MODE_FILE_NAME` (i.e., ``packages.conf``),
False otherwise.
"""
return self.boot_options.get("sys") == INSTALL_MODE_FILE_NAME

def checkpoint(self, checkpoint_file):
"""Create checkpoint file.

Expand Down Expand Up @@ -877,13 +889,28 @@ def file_copy_remote_exists(self, src, dest=None, file_system=None):
log.debug("Host %s: File %s does not already exist on remote.", self.host, src)
return False

def install_os(self, image_name, reboot=True, install_mode=False, read_timeout=2000, **vendor_specifics):
def _resolve_install_mode(self, install_mode):
"""Return the effective install_mode flag, warning if the caller passed it explicitly."""
if install_mode is None:
return self.install_mode
warnings.warn(
"The install_mode argument to install_os is deprecated; install mode is now "
"derived from the device's boot_options via the install_mode property.",
DeprecationWarning,
)
Comment thread
jtdub marked this conversation as resolved.
return install_mode

def install_os(self, image_name, reboot=True, install_mode=None, read_timeout=2000, **vendor_specifics):
"""Installs the prescribed Network OS, which must be present before issuing this command.

Args:
image_name (str): Name of the IOS image to boot into
reboot (bool): Whether to reboot the device after setting the boot options. Defaults to true.
install_mode (bool, optional): Uses newer install method on devices. Defaults to False.
install_mode (bool, optional): **Deprecated.** Whether to use the newer install-mode
upgrade procedure. When omitted (the default), the value is derived from
:attr:`install_mode`, which reads the device's current boot configuration.
Passing the argument explicitly still works but emits a ``DeprecationWarning``
and will be removed in a future release.
read_timeout (int, optional): Netmiko timeout when waiting for device prompt. Default 2000.
vendor_specifics (dict, optional): Vendor specific arguments to pass to the install command.

Expand All @@ -893,14 +920,15 @@ def install_os(self, image_name, reboot=True, install_mode=False, read_timeout=2
Returns:
(bool): False if no install is needed, true if the install completes successfully
"""
use_install_mode = self._resolve_install_mode(install_mode)
timeout = vendor_specifics.get("timeout", 3600)
if not self._image_booted(image_name):
if install_mode and not reboot:
if use_install_mode and not reboot:
raise ValueError(
"IOS devices automatically reboot after installation when using install mode but the reboot argument was set to false."
)

if install_mode:
if use_install_mode:
# Change boot statement to be boot system <flash>:packages.conf
self.set_boot_options(INSTALL_MODE_FILE_NAME, **vendor_specifics)

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

# Set FastCLI back to originally set when using install mode
if install_mode:
if use_install_mode:
image_name = INSTALL_MODE_FILE_NAME
# Verify the OS level
if not self._image_booted(image_name):
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/test_devices/test_base_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Tests for the abstract :class:`pyntc.devices.base_device.BaseDevice` contract."""

import pytest

from pyntc.devices.base_device import BaseDevice


@pytest.fixture
def base_device():
return BaseDevice(host="host", username="user", password="pass")


def test_install_mode_raises_not_implemented(base_device):
with pytest.raises(NotImplementedError):
_ = base_device.install_mode
Loading
Loading