Skip to content

NAPPS-1175 | Adds IOS-XR OS Upgrades Support#398

Open
jtdub wants to merge 4 commits into
developfrom
u/jtdub-napps-1175-xr-driver
Open

NAPPS-1175 | Adds IOS-XR OS Upgrades Support#398
jtdub wants to merge 4 commits into
developfrom
u/jtdub-napps-1175-xr-driver

Conversation

@jtdub

@jtdub jtdub commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Closes: NAPPS-1175

What's Changed:

  • Adds IOS-XR Upgrade Support. (Golden ISO support only, currently)

Caveats

  • Does not currently validate RP/RSP redundancy: (show redundancy, show platform vm)
  • Does not currently upgrade FPGA's: (upgrade hw-module location all fpd all, show hw-module location <loc> fpd)
  • Does not currently reload redundant RP/RSP's: (hw-module location 0/<standby>/CPU0 reload, redundancy switchover)

Integration Test

(pyntc-py3.12) jameswilliams@Jamess-NTC-M4-MacBook-Pro pyntc % python test-iosxr.py 

======================================================================
Connecting to 10.1.100.75 (cisco_iosxr_ssh)
======================================================================
INFO paramiko.transport: Connected (version 2.0, client Cisco-2.0)
INFO paramiko.transport: Authentication (password) successful!
INFO pyntc: Logging initialized for host 10.1.100.75.
Connected: True

======================================================================
READ-ONLY DIAGNOSTICS
======================================================================

-- os_version --
INFO pyntc: Host 10.1.100.75: Command show version was executed successfully.
7.11.1

-- uptime (seconds) / uptime_string --
INFO pyntc: Host 10.1.100.75: Command show version was executed successfully.
INFO pyntc: Host 10.1.100.75: Command show version was executed successfully.
1020 / 00:00:17:00

-- boot_options (parsed from 'show install active') --
INFO pyntc: Host 10.1.100.75: Command show install active was executed successfully.
{'sys': 'ncs5k-xr-7.11.1', 'version': '7.11.1'}

-- _image_booted('ncs5k-golden-x-7.11.2-NTC7112.iso') --
INFO pyntc: Host 10.1.100.75: Command show install active was executed successfully.
INFO pyntc: Host 10.1.100.75: Image ncs5k-golden-x-7.11.2-NTC7112.iso not booted (running 7.11.1).
False

-- raw 'dir harddisk:' (to confirm free-space parsing) --
INFO pyntc: Host 10.1.100.75: Command dir harddisk: was executed successfully.

Tue Jun 16 17:02:01.544 UTC

Directory of harddisk:
    13 drwxr-xr-x. 2       4096 Jun 15 20:22 .tmp
    27 -rw-r--r--. 1       4977 Jun 16 13:14 ncs5k-m2m-1.0.0.0-r7112.x86_64.rpm
    14 drwxr-xr-x. 2       4096 Jun 15 19:55 showtech
389377 drwxr-xr-x. 2       4096 Jun 15 19:55 nvram
    22 -rw-r--r--. 1    3847870 Jun 16 13:14 ncs5k-isis-1.0.0.0-r7112.x86_64.rpm
129815 drwxr-xr-x. 2       4096 Jun 16 15:36 nvgen_traces
    18 drwxr-xr-x. 2       4096 Jun 15 19:57 dumper
    28 -rw-r--r--. 1   21200307 Jun 16 13:14 ncs5k-mgbl-1.0.0.0-r7112.x86_64.rpm
    19 drwxr-xr-x. 3       4096 Jun 15 19:57 pam
    24 -rw-r--r--. 1    2295521 Jun 16 13:14 ncs5k-mpls-1.0.0.0-r7112.x86_64.rpm
129793 drwxr-xr-x. 2       4096 Jun 15 19:54 tftpboot
    23 -rw-r--r--. 1    4125707 Jun 16 13:14 ncs5k-ospf-1.0.0.0-r7112.x86_64.rpm
129799 drwxr-xr-x. 5       4096 Jun 16 15:01 cisco_support
129794 drwxr-xr-x. 2       4096 Jun 15 19:55 shutdown
    25 -rw-r--r--. 1    8916083 Jun 16 13:14 ncs5k-mpls-te-rsvp-1.0.0.0-r7112.x86_64.rpm
    11 drwx------. 2       4096 Jun 15 19:54 lost+found
    15 drwxr-xr-x. 3       4096 Jun 15 19:55 ztp
    12 -rw-r--r--. 1    2719190 Jun 16 16:46 nvgen_bkup.log
259585 drw-rw-r--. 2       4096 Jun 16 00:59 rdsfs_log
389378 drwxr-xr-x. 2       4096 Jun 15 20:22 .agt_cache
    26 -rw-r--r--. 1   11998047 Jun 16 13:14 ncs5k-mcast-1.0.0.0-r7112.x86_64.rpm
129795 drwxr-xr-x. 2       4096 Jun 16 16:28 .tmp_staging
129797 drwx------. 3       4096 Jun 16 16:45 ima
389379 drwxr-xr-x. 3       4096 Jun 15 19:55 mirror
259589 drwxr-xr-x. 2       4096 Jun 16 12:36 install_tmp_staging_area
129796 drwxr-xr-x. 2       4096 Jun 15 19:55 ipodwdm_log
    17 drwxr-xr-x. 2       4096 Jun 15 20:15 .mgr_cache
    21 -rw-r--r--. 1 1432756224 Jun 16 13:14 ncs5k-mini-x-7.11.2.iso

9948012 kbytes total (7942968 kbytes free)

-- free space on harddisk: (bytes) --
INFO pyntc: Host 10.1.100.75: Command dir harddisk: was executed successfully.
8133599232

-- check_file_exists('ncs5k-golden-x-7.11.2-NTC7112.iso') --
False

-- raw 'show install active' --
INFO pyntc: Host 10.1.100.75: Command show install active was executed successfully.

Tue Jun 16 17:02:03.243 UTC
Label : 7.11.1-NTC711

Node 0/RP0/CPU0 [RP]
  Boot Partition: xr_lv36
  Active Packages: 8
        ncs5k-xr-7.11.1 version=7.11.1 [Boot image]
        ncs5k-mgbl-1.0.0.0-r7111
        ncs5k-ospf-1.0.0.0-r7111
        ncs5k-mpls-1.0.0.0-r7111
        ncs5k-m2m-1.0.0.0-r7111
        ncs5k-isis-1.0.0.0-r7111
        ncs5k-mcast-1.0.0.0-r7111
        ncs5k-mpls-te-rsvp-1.0.0.0-r7111


======================================================================
COPYING http://10.1.100.220/IOS-XR/ncs5k-golden-x-7.11.2-NTC7112.iso -> harddisk:/ncs5k-golden-x-7.11.2-NTC7112.iso
======================================================================
INFO pyntc: Host 10.1.100.75: Command dir harddisk: was executed successfully.
INFO pyntc: Host 10.1.100.75: File ncs5k-golden-x-7.11.2-NTC7112.iso transfer reported success.
INFO pyntc: Host 10.1.100.75: File ncs5k-golden-x-7.11.2-NTC7112.iso copied to harddisk: and verified present.
  present on harddisk:? True

======================================================================
UPGRADE REQUESTED
======================================================================
This will install ncs5k-golden-x-7.11.2-NTC7112.iso and RELOAD 10.1.100.75.
Type 'yes' to proceed: yes

======================================================================
RUNNING install_os (add golden ISO -> poll -> activate -> poll -> reload -> commit -> verify)
======================================================================
INFO pyntc: Host 10.1.100.75: Command show install active was executed successfully.
INFO pyntc: Host 10.1.100.75: Image ncs5k-golden-x-7.11.2-NTC7112.iso not booted (running 7.11.1).
INFO pyntc: Host 10.1.100.75: install add started operation 38.
INFO pyntc: Host 10.1.100.75: install operation 38 completed successfully.
INFO pyntc: Host 10.1.100.75: issuing activation: install activate id 38 noprompt
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: polled activation status.
INFO pyntc: Host 10.1.100.75: activation completed; reload imminent.
INFO paramiko.transport: Connected (version 2.0, client Cisco-2.0)
INFO paramiko.transport: Authentication (password) successful!
INFO pyntc: Host 10.1.100.75: Command show version was executed successfully.
INFO pyntc: Host 10.1.100.75: device still reachable; waiting for the reload to drop the session...
INFO pyntc: Host 10.1.100.75: device disconnected; reload in progress (TCP connection to device failed.

Common causes of this problem are:
1. Incorrect hostname or IP address.
2. Wrong TCP port.
3. Intermediate firewall blocking access.

Device settings: cisco_xr 10.1.100.75:22

).
INFO pyntc: Host 10.1.100.75: device still down (TCP connection to device failed.

Common causes of this problem are:
1. Incorrect hostname or IP address.
2. Wrong TCP port.
3. Intermediate firewall blocking access.

Device settings: cisco_xr 10.1.100.75:22

); polling again in 60s.
INFO pyntc: Host 10.1.100.75: device still down (TCP connection to device failed.

Common causes of this problem are:
1. Incorrect hostname or IP address.
2. Wrong TCP port.
3. Intermediate firewall blocking access.

Device settings: cisco_xr 10.1.100.75:22

); polling again in 60s.
INFO pyntc: Host 10.1.100.75: device still down (TCP connection to device failed.

Common causes of this problem are:
1. Incorrect hostname or IP address.
2. Wrong TCP port.
3. Intermediate firewall blocking access.

Device settings: cisco_xr 10.1.100.75:22

); polling again in 60s.
INFO pyntc: Host 10.1.100.75: device still down (TCP connection to device failed.

Common causes of this problem are:
1. Incorrect hostname or IP address.
2. Wrong TCP port.
3. Intermediate firewall blocking access.

Device settings: cisco_xr 10.1.100.75:22

); polling again in 60s.
INFO paramiko.transport: Connected (version 2.0, client Cisco-2.0)
INFO paramiko.transport: Authentication (password) successful!
INFO pyntc: Host 10.1.100.75: Command show version was executed successfully.
INFO pyntc: Host 10.1.100.75: device is back up after reload.
INFO pyntc: Host 10.1.100.75: install commit issued.
INFO pyntc: Host 10.1.100.75: Command show install active was executed successfully.
INFO pyntc: Host 10.1.100.75: Command show version was executed successfully.
INFO pyntc: Host 10.1.100.75: Image ncs5k-golden-x-7.11.2-NTC7112.iso booted successfully.
INFO pyntc: Host 10.1.100.75: OS image ncs5k-golden-x-7.11.2-NTC7112.iso installed successfully.

install_os returned: True
INFO pyntc: Host 10.1.100.75: Command show install active was executed successfully.
boot_options after install: {'sys': None, 'version': None}

Connection closed.

@jtdub jtdub marked this pull request as draft June 16, 2026 14:42
@jtdub jtdub marked this pull request as ready for review June 16, 2026 18:57
@jtdub jtdub requested a review from Copilot June 16, 2026 19:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Cisco IOS-XR (eXR / 64-bit) device driver to PyNTC with support for staging golden ISOs, running the async install addinstall activate → reload → install commit workflow, and documenting/adding unit coverage around that behavior.

Changes:

  • Introduces IOSXRDevice (cisco_iosxr_ssh) with OS upgrade orchestration and URL-based remote_file_copy.
  • Registers the new driver in the device factory and documentation (MkDocs + user library overview).
  • Adds unit tests for IOS-XR behavior and updates existing device-creation test to include IOS-XR.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/test_infra.py Adds IOS-XR device creation coverage via factory patching.
tests/unit/test_devices/test_iosxr_device.py New unit tests covering IOS-XR facts, copy flow, and upgrade workflow primitives.
pyntc/devices/iosxr_device.py New IOS-XR SSH driver implementing golden-ISO upgrades and remote file copy.
pyntc/devices/init.py Registers cisco_iosxr_ssh and exports IOSXRDevice.
mkdocs.yml Adds IOS-XR module to generated code reference nav.
docs/user/lib_overview.md Documents IOS-XR support and golden-ISO requirement/workflow.
changes/398.added Towncrier entry for IOS-XR support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +416 to +420
r"Host name or IP address": "",
r"Source username|Username": src.username or "",
r"Password": src.token or "",
r"yes/no|\[confirm\]|Are you sure": "",
}
Comment on lines +376 to +377
log.debug("Host %s: File %s not found in 'dir' output on %s.", self.host, filename, file_system)
return False
from pyntc.errors import CommandError, CommandListError, FileTransferError, OSInstallError, RebootTimeoutError
from pyntc.utils.models import FileCopyModel

DEFAULT_FILE_SYSTEM = "harddisk:"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should likely update this to match the ios_device method _get_file_system

command = f"install add source {source} {image_name}"
response = self.native.send_command(command, read_timeout=120)

match = RE_INSTALL_OP.search(response)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the constant from L38 to here; single use reference

both values are ``None`` when the output cannot be parsed.
"""
show_install_active = self.show("show install active")
match = RE_XR_BOOT_IMAGE.search(show_install_active)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the constant from line 40; single use reference

# Parse the active boot package and its version from "show install active", e.g. "ncs5k-xr-7.11.2".
RE_XR_BOOT_IMAGE = re.compile(r"(?P<sys>\S*xr-(?P<version>\d+\.\d+\.\d+\w*))")
# Parse the running version from "show version", e.g. "Version 7.11.2".
RE_XR_VERSION = re.compile(r"Version\s+(\d+\.\d+\.\d+\w*)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be moved into a parse_version function?

Comment on lines +43 to +52
# Success / error markers emitted by the IOS-XR "copy" command (eXR uses
# "Successfully copied ... Bytes" / "Copy operation success").
RE_COPY_SUCCESS = re.compile(
r"Successfully copied|Copy operation success|bytes copied|copied in|\[OK\]|Download Complete|transfer successful",
re.IGNORECASE,
)
RE_COPY_ERROR = re.compile(
r"%Error|Error opening|Invalid input|Failed|Aborted|denied|No such file|Connection refused|timed out|could not",
re.IGNORECASE,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likely should be considered as a function

Comment on lines +78 to +80
self.read_timeout_override = kwargs.get("read_timeout_override")
self._connect_attempts = int(kwargs.get("ssh_connect_attempts", DEFAULT_SSH_CONNECT_ATTEMPTS))
self._connect_retry_delay = int(kwargs.get("ssh_connect_retry_delay", DEFAULT_SSH_CONNECT_RETRY_DELAY))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document the kwargs in the docstring


response = self.native.send_command(**command_args)

if "% " in response or "Error:" in response:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change so that we can match on % at the start of line

command_args["expect_string"] = expect_string
command_args.update(kwargs)

response = self.native.send_command(**command_args)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for self.native.send_command and probably update to use self._send_command method we defined.

log.error("Host %s: Unable to parse install operation id from response: %s", self.host, response)
raise OSInstallError(hostname=self.host, desired_boot=image_name)

op_id = int(match.group(1))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe update to operation_id for consistency against docstring

log.info("Host %s: install add started operation %s.", self.host, op_id)
return op_id

def _wait_for_install_op(self, op_id, timeout=3600, interval=30):

@mattmiller87 mattmiller87 Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update op to operation_id

all op_id references should be updated to operation_id as well.

def _install_activate(self, op_id, poll_interval=60, timeout=3600):
"""Activate a staged install operation and track it to completion.

The activation is issued with ``noprompt`` (so eXR does not wait on the interactive

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reference send_command_timing which isn't necessarily asyncrounous, https://pynet.twb-tech.com/blog/netmiko-send-command-timing.html

log.error("Host %s: install commit did not complete after %s attempts.", self.host, retries)
raise OSInstallError(hostname=self.host, desired_boot="install commit")

def _wait_for_device_reboot(self, timeout=3600, interval=60):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should reference the logic in the nxos_device.py code

Pulls the file specified by ``src`` from a remote server (FTP/TFTP/SCP/HTTP/HTTPS)
using the IOS-XR ``copy`` command and saves it to ``file_system`` (default
``harddisk:``). The transfer is verified by confirming the file exists after
copy. **Checksum verification is not performed** on IOS-XR in this release; the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add checksum verification as a step in this process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants