Skip to content

Commit

Permalink
NTC_INSTALL_OS: Add support for reboot (#229)
Browse files Browse the repository at this point in the history
* Add reboot parameter to support new pyntc `install_os` method.

* Add logic for updated pyntc version

* Update to take into account latest from pyntc

* Add deprecation for older pyntc versions.
  • Loading branch information
jmcgill298 authored Nov 28, 2018
1 parent 42868c2 commit cd317e3
Showing 1 changed file with 160 additions and 51 deletions.
211 changes: 160 additions & 51 deletions library/ntc_install_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,24 @@
like boot image and kickstart image.
description:
- Set boot options like boot image and kickstart image.
- Reboot option for device to perform install.
- Supported platforms include Cisco Nexus switches with NX-API,
Cisco IOS switches or routers, Arista switches with eAPI.
Cisco IOS switches or routers, Arista switches with eAPI,
Cisco ASA firewalls, and F5 LTMs with iControl API.
notes:
- Do not include full file paths, just the name
of the file(s) stored on the top level flash directory.
- You must know if your platform supports taking a kickstart image as a parameter.
If supplied but not supported, errors may occur.
- It may be useful to use this module in conjuction with ntc_file_copy and ntc_reboot.
- It may be useful to use this module in conjunction with ntc_file_copy and ntc_reboot.
- With F5, volume parameter is required.
- With NXOS devices, this module attempts to install the software immediately,
wich may trigger a reboot.
which may trigger a reboot.
- With NXOS devices, install process may take up to 10 minutes,
especially if the device reboots.
- Tested on Nexus 3000, 5000, 9000.
- In check mode, the module tells you if the current boot images are set to the desired images.
- In check mode, the module tells you if the image currently
booted matches C(system_image_file).
author: Jason Edelman (@jedelman8)
version_added: 1.9.2
requirements:
Expand All @@ -64,7 +67,7 @@
required: false
host:
description:
- Hostame or IP address of switch.
- Hostname or IP address of switch.
required: false
username:
description:
Expand Down Expand Up @@ -111,6 +114,11 @@
home directory for a file called .ntc.conf.
required: false
default: null
reboot:
description:
- Determines whether or not the device should be rebooted to complete OS installation.
required: false
default: false
'''

EXAMPLES = '''
Expand All @@ -122,22 +130,29 @@
platform: "cisco_ios_ssh"
connection: ssh
- ntc_install_os:
- name: "INSTALL OS ON NEXUS 9K"
ntc_install_os:
ntc_host: n9k1
system_image_file: n9000-dk9.6.1.2.I3.1.bin
reboot: yes
- ntc_install_os:
- name: "INSTALL OS ON NEXUS 3K WITH KICKSTART"
ntc_install_os:
ntc_host: n3k1
system_image_file: n3000-uk9.6.0.2.U6.5.bin
kickstart_image_file: n3000-uk9-kickstart.6.0.2.U6.5.bin
reboot: yes
- ntc_install_os:
- name: "CONFIGURE BOOT OPTIONS ON CISCO 2800"
ntc_install_os:
ntc_host: c2801
system_image_file: c2800nm-adventerprisek9_ivs_li-mz.151-3.T4.bin
- ntc_install_os:
- name: "INSTALL OS ON CISCO 2800"
ntc_install_os:
provider: "{{ ios_provider }}"
system_image_file: c2800nm-adventerprisek9_ivs_li-mz.151-3.T4.bin
reboot: yes
'''

RETURN = '''
Expand Down Expand Up @@ -166,6 +181,22 @@
HAS_PYNTC = True
except ImportError:
HAS_PYNTC = False

try:
# TODO: Ensure pyntc adds __version__
from pyntc import __version__ as pyntc_version # noqa F401
from pyntc.errors import (
CommandError,
CommandListError,
FileSystemNotFoundError,
NotEnoughFreeSpaceError,
NTCFileNotFoundError,
OSInstallError,
RebootTimeoutError,
)
HAS_PYNTC_VERSION = True
except ImportError:
HAS_PYNTC_VERSION = False
# fmt: on

PLATFORM_NXAPI = "cisco_nxos_nxapi"
Expand All @@ -175,6 +206,7 @@
PLATFORM_ASA = "cisco_asa_ssh"


# TODO: Remove when deprecating older pyntc
def already_set(boot_options, system_image_file, kickstart_image_file, **kwargs):
volume = kwargs.get("volume")
device = kwargs.get("device")
Expand Down Expand Up @@ -206,6 +238,7 @@ def main():
system_image_file=dict(required=True),
kickstart_image_file=dict(required=False),
volume=dict(required=False, type="str"),
reboot=dict(required=False, type="bool", default=False),
),
mutually_exclusive=[
["host", "ntc_host"],
Expand All @@ -223,6 +256,12 @@ def main():

if not HAS_PYNTC:
module.fail_json(msg="pyntc Python library not found.")
# TODO: Change to fail_json when deprecating older pyntc
if not HAS_PYNTC_VERSION:
module.warn("Support for pyntc version < 0.0.9 is being deprecated; please upgrade pyntc")

# TODO: Remove warning when deprecating reboot option on non-F5 devices
module.warn("Support for installing the OS without rebooting may be deprecated in the future")

provider = module.params["provider"] or {}

Expand All @@ -233,6 +272,7 @@ def main():

# allow local params to override provider
for param, pvalue in provider.items():
# TODO: Figure out exactly the purpose of this and correct truthiness or noneness
if module.params.get(param) != False:
module.params[param] = module.params.get(param) or pvalue

Expand All @@ -247,6 +287,15 @@ def main():
transport = module.params["transport"]
port = module.params["port"]
secret = module.params["secret"]
reboot = module.params["reboot"]

# TODO: Remove checks if we require reboot for non-F5 devices
if platform == "cisco_nxos_nxapi" and not reboot:
module.fail_json(msg='NXOS requires setting the "reboot" parameter to True')
if platform != "cisco_nxos_nxapi" and reboot and not HAS_PYNTC_VERSION:
module.fail_json(
msg='Using the "reboot" parameter for non-NXOS devices' "requires pyntc version > 0.0.8"
)

argument_check = {
"host": host,
Expand Down Expand Up @@ -281,55 +330,115 @@ def main():

device.open()
pre_install_boot_options = device.get_boot_options()
changed = False

if not already_set(
boot_options=pre_install_boot_options,
system_image_file=system_image_file,
kickstart_image_file=kickstart_image_file,
volume=volume,
device=device,
):
changed = True

if not module.check_mode and changed == True:
if device.device_type == "nxos":
timeout = 600
device.set_timeout(timeout)
try:
start_time = time.time()
device.set_boot_options(system_image_file, kickstart=kickstart_image_file)
except:
pass
elapsed_time = time.time() - start_time

device.set_timeout(30)
if not module.check_mode:
# TODO: Remove conditional when deprecating older pyntc
if HAS_PYNTC_VERSION:
try:
install_state = device.get_boot_options()
except:
install_state = {}

while elapsed_time < timeout and not install_state:
# TODO: Remove conditional if we require reboot for non-F5 devices
if reboot or device.device_type == "f5_tmos_icontrol":
changed = device.install_os(
image_name=system_image_file, kickstart=kickstart_image_file, volume=volume
)
else:
# TODO: Remove support if we require reboot for non-F5 devices
changed = device.set_boot_options(system_image_file)
except (
CommandError,
CommandListError,
FileSystemNotFoundError,
NotEnoughFreeSpaceError,
NTCFileNotFoundError,
OSInstallError,
RebootTimeoutError,
) as e:
module.fail_json(msg=e.message)
except Exception as e:
module.fail_json(msg=str(e))

if (
reboot
and device.device_type == "f5_tmos_icontrol"
and pre_install_boot_options["active_volume"] != volume
):
try:
changed = True
device.reboot(confirm=True, volume=volume)
except RuntimeError:
module.fail_json(
msg="Attempted reboot but did not boot to desired volume",
original_volume=pre_install_boot_options["active_volume"],
expected_volume=volume,
)

install_state = device.get_boot_options()

# TODO: Remove contents of else when deprecating older pyntc
else:
changed = False
install_state = pre_install_boot_options

if not already_set(
boot_options=pre_install_boot_options,
system_image_file=system_image_file,
kickstart_image_file=kickstart_image_file,
volume=volume,
device=device,
):
changed = True

if device.device_type == "nxos":
timeout = 600
device.set_timeout(timeout)
try:
start_time = time.time()
device.set_boot_options(system_image_file, kickstart=kickstart_image_file)
except:
pass
elapsed_time = time.time() - start_time

device.set_timeout(30)
try:
install_state = device.get_boot_options()
except:
install_state = {}

while elapsed_time < timeout and not install_state:
try:
install_state = device.get_boot_options()
except:
time.sleep(10)
elapsed_time += 10
else:
device.set_boot_options(
system_image_file, kickstart=kickstart_image_file, volume=volume
)
install_state = device.get_boot_options()
except:
time.sleep(10)
elapsed_time += 10

if not already_set(
boot_options=pre_install_boot_options,
system_image_file=system_image_file,
kickstart_image_file=kickstart_image_file,
volume=volume,
device=device,
):
module.fail_json(msg="Install not successful", install_state=install_state)

else:
if HAS_PYNTC_VERSION:
changed = device._image_booted(
image_name=system_image_file, kickstart=kickstart_image_file, volume=volume
)
# TODO: Remove contents of else when deprecating older pyntc
else:
device.set_boot_options(
system_image_file, kickstart=kickstart_image_file, volume=volume
changed = already_set(
boot_options=pre_install_boot_options,
system_image_file=system_image_file,
kickstart_image_file=kickstart_image_file,
volume=volume,
device=device,
)
install_state = device.get_boot_options()

if not already_set(
boot_options=install_state,
system_image_file=system_image_file,
kickstart_image_file=kickstart_image_file,
volume=volume,
device=device,
):
module.fail_json(msg="Install not successful", install_state=install_state)
else:
install_state = pre_install_boot_options

device.close()
Expand Down

0 comments on commit cd317e3

Please sign in to comment.