@@ -46,7 +46,7 @@ class IOSDevice(BaseDevice):
4646 vendor = "cisco"
4747 active_redundancy_states = {None , "active" }
4848
49- def __init__ (self , host , username , password , secret = "" , port = 22 , confirm_active = True , ** kwargs ):
49+ def __init__ (self , host , username , password , secret = "" , port = 22 , confirm_active = True , fast_cli = True , ** kwargs ):
5050 """
5151 PyNTC Device implementation for Cisco IOS.
5252
@@ -57,6 +57,7 @@ def __init__(self, host, username, password, secret="", port=22, confirm_active=
5757 secret (str): The password to escalate privilege on the device.
5858 port (int): The port to use to establish the connection.
5959 confirm_active (bool): Determines if device's high availability state should be validated before leaving connection open.
60+ fast_cli (bool): Fast CLI mode for Netmiko, it is recommended to use False when opening the client on code upgrades
6061 """
6162 super ().__init__ (host , username , password , device_type = "cisco_ios_ssh" )
6263
@@ -65,6 +66,7 @@ def __init__(self, host, username, password, secret="", port=22, confirm_active=
6566 self .port = int (port )
6667 self .global_delay_factor = kwargs .get ("global_delay_factor" , 1 )
6768 self .delay_factor = kwargs .get ("delay_factor" , 1 )
69+ self ._fast_cli = fast_cli
6870 self ._connected = False
6971 self .open (confirm_active = confirm_active )
7072
@@ -133,11 +135,21 @@ def _get_file_system(self):
133135 raise FileSystemNotFoundError (hostname = self .hostname , command = "dir" )
134136
135137 # Get the version of the image that is booted into on the device
136- def _image_booted (self , image_name , ** vendor_specifics ):
138+ def _image_booted (self , image_name , image_pattern = r".*\.(\d+\.\d+\.\w+)\.SPA.+" , ** vendor_specifics ):
137139 version_data = self .show ("show version" )
138140 if re .search (image_name , version_data ):
139141 return True
140142
143+ # Test for version number in the text, used on install mode devices that use packages.conf
144+ try :
145+ version_number = re .search (image_pattern , image_name ).group (1 )
146+ if version_number and version_number in version_data :
147+ return True
148+ # Continue on if regex is unable to find the result, which raises an attribute error
149+ except AttributeError :
150+ pass
151+
152+ # Unable to find the version number in output, the image is not booted.
141153 return False
142154
143155 def _interfaces_detailed_list (self ):
@@ -277,8 +289,8 @@ def config(self, command, **netmiko_args):
277289 **netmiko_args: Any argument supported by ``netmiko.ConnectHandler.send_config_set``.
278290
279291 Returns:
280- str: When ``command`` is a str, the config session input and ouput from sending ``command``.
281- list: When ``command`` is a list, the config session input and ouput from sending ``command``.
292+ str: When ``command`` is a str, the config session input and output from sending ``command``.
293+ list: When ``command`` is a list, the config session input and output from sending ``command``.
282294
283295 Raises:
284296 TypeError: When sending an argument in ``**netmiko_args`` that is not supported.
@@ -350,7 +362,7 @@ def config_list(self, commands, **netmiko_args):
350362 **netmiko_args: Any argument supported by ``netmiko.ConnectHandler.send_config_set``.
351363
352364 Returns:
353- list: Each command's input and ouput from sending the command in ``commands``.
365+ list: Each command's input and output from sending the command in ``commands``.
354366
355367 Raises:
356368 TypeError: When sending an argument in ``**netmiko_args`` that is not supported.
@@ -509,6 +521,15 @@ def config_register(self):
509521
510522 return self ._config_register
511523
524+ @property
525+ def fast_cli (self ):
526+ return self ._fast_cli
527+
528+ @fast_cli .setter
529+ def fast_cli (self , value ):
530+ self ._fast_cli = value
531+ self .native .fast_cli = value
532+
512533 def file_copy (self , src , dest = None , file_system = None ):
513534 self .enable ()
514535 if file_system is None :
@@ -551,7 +572,7 @@ def file_copy_remote_exists(self, src, dest=None, file_system=None):
551572 return True
552573 return False
553574
554- def install_os (self , image_name , install_mode = False , install_mode_delay_factor = 10 , ** vendor_specifics ):
575+ def install_os (self , image_name , install_mode = False , install_mode_delay_factor = 20 , ** vendor_specifics ):
555576 """Installs the prescribed Network OS, which must be present before issuing this command.
556577
557578 Args:
@@ -570,6 +591,12 @@ def install_os(self, image_name, install_mode=False, install_mode_delay_factor=1
570591 # Change boot statement to be boot system <flash>:packages.conf
571592 self .set_boot_options (INSTALL_MODE_FILE_NAME , ** vendor_specifics )
572593
594+ # Get the current fast_cli to set it back later to whatever it is
595+ current_fast_cli = self .fast_cli
596+
597+ # Set fast_cli to False to handle install mode, 10+ minute installation
598+ self .fast_cli = False
599+
573600 # Check for OS Version specific upgrade path
574601 # https://www.cisco.com/c/en/us/td/docs/switches/lan/catalyst9300/software/release/17-2/release_notes/ol-17-2-9300.html
575602 os_version = self .os_version
@@ -589,13 +616,18 @@ def install_os(self, image_name, install_mode=False, install_mode_delay_factor=1
589616 self .show (command , delay_factor = install_mode_delay_factor )
590617 except IOError :
591618 pass
619+
592620 else :
593621 self .set_boot_options (image_name , ** vendor_specifics )
594622 self .reboot ()
595623
596624 # Wait for the reboot to finish
597625 self ._wait_for_device_reboot (timeout = timeout )
598626
627+ # Set FastCLI back to originally set when using install mode
628+ if install_mode :
629+ self .fast_cli = current_fast_cli
630+
599631 # Verify the OS level
600632 if not self ._image_booted (image_name ):
601633 raise OSInstallError (hostname = self .hostname , desired_boot = image_name )
@@ -624,13 +656,13 @@ def open(self, confirm_active=True):
624656 Open a connection to the network device.
625657
626658 This method will close the connection if ``confirm_active`` is True and the device is not active.
627- Devices that do not have high availibility are considred active.
659+ Devices that do not have high availability are considered active.
628660
629661 Args:
630662 confirm_active (bool): Determines if device's high availability state should be validated before leaving connection open.
631663
632664 Raises:
633- DeviceNotActiveError: When ``confirm_active`` is True, and the device high availabilit state is not active.
665+ DeviceNotActiveError: When ``confirm_active`` is True, and the device high availability state is not active.
634666
635667 Example:
636668 >>> device = IOSDevice(**connection_args)
@@ -662,6 +694,7 @@ def open(self, confirm_active=True):
662694 global_delay_factor = self .global_delay_factor ,
663695 secret = self .secret ,
664696 verbose = False ,
697+ fast_cli = self .fast_cli ,
665698 )
666699 self ._connected = True
667700
0 commit comments