mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Add ISSU capability and fix workflow bug for nxos_install_os module (#32540)
* Initial issu support * Enhance ISSU support * Additional refactoring to simplify code flow * Remove debugs and bug fixes * Update doc example output * Update provider line in task example * Remove unneeded else clause and comments * Fix ansible-doc errors * Satisfy ansibot requirements * Update header docs * Update nxos_install_os.py
This commit is contained in:
parent
db749de5b8
commit
fedd1779cc
3 changed files with 488 additions and 125 deletions
|
@ -164,19 +164,26 @@ class Cli:
|
|||
responses.append(out)
|
||||
return responses
|
||||
|
||||
def load_config(self, config, return_error=False):
|
||||
def load_config(self, config, return_error=False, opts=None):
|
||||
"""Sends configuration commands to the remote device
|
||||
"""
|
||||
if opts is None:
|
||||
opts = {}
|
||||
|
||||
errors = 'surrogate_then_replace'
|
||||
rc, out, err = self.exec_command('configure')
|
||||
if rc != 0:
|
||||
self._module.fail_json(msg='unable to enter configuration mode', output=to_text(err, errors='surrogate_then_replace'))
|
||||
msg = 'unable to enter configuration mode'
|
||||
self._module.fail_json(msg=msg, output=to_text(err, errors=errors))
|
||||
|
||||
msgs = []
|
||||
for cmd in config:
|
||||
rc, out, err = self.exec_command(cmd)
|
||||
if rc != 0:
|
||||
self._module.fail_json(msg=to_text(err, errors='surrogate_then_replace'))
|
||||
if opts.get('ignore_timeout') and rc == 1:
|
||||
msgs.append(err)
|
||||
return msgs
|
||||
elif rc != 0:
|
||||
self._module.fail_json(msg=to_text(err, errors=errors))
|
||||
elif out:
|
||||
msgs.append(out)
|
||||
|
||||
|
@ -243,9 +250,12 @@ class Nxapi:
|
|||
|
||||
return dict(ins_api=msg)
|
||||
|
||||
def send_request(self, commands, output='text', check_status=True, return_error=False):
|
||||
def send_request(self, commands, output='text', check_status=True,
|
||||
return_error=False, opts=None):
|
||||
# only 10 show commands can be encoded in each request
|
||||
# messages sent to the remote device
|
||||
if opts is None:
|
||||
opts = {}
|
||||
if output != 'config':
|
||||
commands = collections.deque(to_list(commands))
|
||||
stack = list()
|
||||
|
@ -282,7 +292,10 @@ class Nxapi:
|
|||
)
|
||||
self._nxapi_auth = headers.get('set-cookie')
|
||||
|
||||
if headers['status'] != 200:
|
||||
if opts.get('ignore_timeout') and headers['status'] == -1:
|
||||
result.append(headers['msg'])
|
||||
return result
|
||||
elif headers['status'] != 200:
|
||||
self._error(**headers)
|
||||
|
||||
try:
|
||||
|
@ -351,11 +364,12 @@ class Nxapi:
|
|||
|
||||
return responses
|
||||
|
||||
def load_config(self, commands, return_error=False):
|
||||
def load_config(self, commands, return_error=False, opts=None):
|
||||
"""Sends the ordered set of commands to the device
|
||||
"""
|
||||
commands = to_list(commands)
|
||||
msg = self.send_request(commands, output='config', check_status=True, return_error=return_error)
|
||||
msg = self.send_request(commands, output='config', check_status=True,
|
||||
return_error=return_error, opts=opts)
|
||||
if return_error:
|
||||
return msg
|
||||
else:
|
||||
|
@ -410,6 +424,6 @@ def run_commands(module, commands, check_rc=True):
|
|||
return conn.run_commands(to_command(module, commands), check_rc)
|
||||
|
||||
|
||||
def load_config(module, config, return_error=False):
|
||||
def load_config(module, config, return_error=False, opts=None):
|
||||
conn = get_connection(module)
|
||||
return conn.load_config(config, return_error=return_error)
|
||||
return conn.load_config(config, return_error, opts)
|
||||
|
|
|
@ -25,27 +25,29 @@ DOCUMENTATION = '''
|
|||
---
|
||||
module: nxos_install_os
|
||||
extends_documentation_fragment: nxos
|
||||
short_description: Set boot options like boot image and kickstart image.
|
||||
short_description: Set boot options like boot, kickstart image and issu.
|
||||
description:
|
||||
- Install an operating system by setting the boot options like boot
|
||||
image and kickstart image.
|
||||
image and kickstart image and optionally select to install using
|
||||
ISSU (In Server Software Upgrade).
|
||||
notes:
|
||||
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
|
||||
- The module will fail due to timeout issues, but the install will go on
|
||||
anyway. Ansible's block and rescue can be leveraged to handle this kind
|
||||
of failure and check actual module results. See EXAMPLE for more about
|
||||
this. The first task on the rescue block is needed to make sure the
|
||||
device has completed all checks and it started to reboot. The second
|
||||
task is needed to wait for the device to come back up. The last two tasks
|
||||
are used to verify the installation process was successful.
|
||||
- Tested against the following platforms and images
|
||||
- N9k 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(1), 7.0(3)F2(2), 7.0(3)F3(2)
|
||||
- N3k 6.0(2)A8(6), 6.0(2)A8(8), 7.0(3)I6(1), 7.0(3)I7(1)
|
||||
- N7k 7.3(0)D1(1), 8.0(1), 8.2(1)
|
||||
- This module executes longer then the default ansible timeout value and
|
||||
will generate errors unless the module timeout parameter is set to a
|
||||
value of 500 seconds or higher.
|
||||
The example time is sufficent for most upgrades but this can be
|
||||
tuned higher based on specific upgrade time requirements.
|
||||
The module will exit with a failure message if the timer is
|
||||
not set to 500 seconds or higher.
|
||||
- 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.
|
||||
- This module attempts to install the software immediately,
|
||||
which may trigger a reboot.
|
||||
- In check mode, the module tells you if the current boot images are set
|
||||
to the desired images.
|
||||
- In check mode, the module will indicate if an upgrade is needed and
|
||||
whether or not the upgrade is disruptive or non-disruptive(ISSU).
|
||||
author:
|
||||
- Jason Edelman (@jedelman8)
|
||||
- Gabriele Gerbibo (@GGabriele)
|
||||
|
@ -60,34 +62,45 @@ options:
|
|||
- Name of the kickstart image file on flash.
|
||||
required: false
|
||||
default: null
|
||||
issu:
|
||||
version_added: "2.5"
|
||||
description:
|
||||
- Upgrade using In Service Software Upgrade (ISSU).
|
||||
(Only supported on N9k platforms)
|
||||
- Selecting 'required' or 'yes' means that upgrades will only
|
||||
proceed if the switch is capable of ISSU.
|
||||
- Selecting 'desired' means that upgrades will use ISSU if possible
|
||||
but will fall back to disruptive upgrade if needed.
|
||||
- Selecting 'no' means do not use ISSU. Forced disruptive.
|
||||
required: false
|
||||
choices: ['required','desired', 'yes', 'no']
|
||||
default: 'no'
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- block:
|
||||
- name: Install OS
|
||||
nxos_install_os:
|
||||
system_image_file: nxos.7.0.3.I2.2d.bin
|
||||
rescue:
|
||||
- name: Wait for device to perform checks
|
||||
wait_for:
|
||||
port: 22
|
||||
state: stopped
|
||||
timeout: 300
|
||||
delay: 60
|
||||
- name: Wait for device to come back up
|
||||
wait_for:
|
||||
port: 22
|
||||
state: started
|
||||
timeout: 300
|
||||
delay: 60
|
||||
- name: Check installed OS
|
||||
nxos_command:
|
||||
commands:
|
||||
- show version
|
||||
register: output
|
||||
- assert:
|
||||
that:
|
||||
- output['stdout'][0]['kickstart_ver_str'] == '7.0(3)I4(1)'
|
||||
- name: Install OS on N9k
|
||||
check_mode: no
|
||||
nxos_install_os:
|
||||
system_image_file: nxos.7.0.3.I6.1.bin
|
||||
issu: desired
|
||||
provider: "{{ connection | combine({'timeout': 500}) }}"
|
||||
|
||||
- name: Wait for device to come back up with new image
|
||||
wait_for:
|
||||
port: 22
|
||||
state: started
|
||||
timeout: 500
|
||||
delay: 60
|
||||
host: "{{ inventory_hostname }}"
|
||||
|
||||
- name: Check installed OS for newly installed version
|
||||
nxos_command:
|
||||
commands: ['show version | json']
|
||||
provider: "{{ connection }}"
|
||||
register: output
|
||||
- assert:
|
||||
that:
|
||||
- output['stdout'][0]['kickstart_ver_str'] == '7.0(3)I6(1)'
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -96,125 +109,462 @@ install_state:
|
|||
returned: always
|
||||
type: dictionary
|
||||
sample: {
|
||||
"kick": "n5000-uk9-kickstart.7.2.1.N1.1.bin",
|
||||
"sys": "n5000-uk9.7.2.1.N1.1.bin",
|
||||
"status": "This is the log of last installation.\n
|
||||
Continuing with installation process, please wait.\n
|
||||
The login will be disabled until the installation is completed.\n
|
||||
Performing supervisor state verification. \n
|
||||
SUCCESS\n
|
||||
Supervisor non-disruptive upgrade successful.\n
|
||||
Install has been successful.\n",
|
||||
"install_state": [
|
||||
"Compatibility check is done:",
|
||||
"Module bootable Impact Install-type Reason",
|
||||
"------ -------- -------------- ------------ ------",
|
||||
" 1 yes non-disruptive reset ",
|
||||
"Images will be upgraded according to following table:",
|
||||
"Module Image Running-Version(pri:alt) New-Version Upg-Required",
|
||||
"------ ---------- ---------------------------------------- -------------------- ------------",
|
||||
" 1 nxos 7.0(3)I6(1) 7.0(3)I7(1) yes",
|
||||
" 1 bios v4.4.0(07/12/2017) v4.4.0(07/12/2017) no"
|
||||
],
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.nxos import get_config, load_config, run_commands
|
||||
from time import sleep
|
||||
from ansible.module_utils.nxos import load_config, run_commands
|
||||
from ansible.module_utils.nxos import nxos_argument_spec, check_args
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def execute_show_command(command, module):
|
||||
command = {
|
||||
def check_ansible_timer(module):
|
||||
'''Check Ansible Timer Values'''
|
||||
msg = "The 'timeout' provider param value for this module to execute\n"
|
||||
msg = msg + 'properly is too low.\n'
|
||||
msg = msg + 'Upgrades can take a long time so the value needs to be set\n'
|
||||
msg = msg + 'to the recommended value of 500 seconds or higher in the\n'
|
||||
msg = msg + 'ansible playbook for the nxos_install_os module.\n'
|
||||
msg = msg + '\n'
|
||||
msg = msg + 'provider: "{{ connection | combine({\'timeout\': 500}) }}"'
|
||||
data = module.params.get('provider')
|
||||
timer_low = False
|
||||
if data.get('timeout') is None:
|
||||
timer_low = True
|
||||
if data.get('timeout') is not None and data.get('timeout') < 500:
|
||||
timer_low = True
|
||||
if timer_low:
|
||||
module.fail_json(msg=msg.split('\n'))
|
||||
|
||||
|
||||
# Output options are 'text' or 'json'
|
||||
def execute_show_command(module, command, output='text'):
|
||||
cmds = [{
|
||||
'command': command,
|
||||
'output': 'text',
|
||||
}
|
||||
'output': output,
|
||||
}]
|
||||
|
||||
return run_commands(module, [command])
|
||||
return run_commands(module, cmds)
|
||||
|
||||
|
||||
def get_boot_options(module):
|
||||
"""Get current boot variables
|
||||
like system image and kickstart image.
|
||||
Returns:
|
||||
A dictionary, e.g. { 'kick': router_kick.img, 'sys': 'router_sys.img'}
|
||||
"""
|
||||
command = 'show boot'
|
||||
body = execute_show_command(command, module)[0]
|
||||
boot_options_raw_text = body.split('Boot Variables on next reload')[1]
|
||||
def get_platform(module):
|
||||
"""Determine platform type"""
|
||||
data = execute_show_command(module, 'show inventory', 'json')
|
||||
pid = data[0]['TABLE_inv']['ROW_inv'][0]['productid']
|
||||
|
||||
if 'kickstart' in boot_options_raw_text:
|
||||
kick_regex = r'kickstart variable = bootflash:/(\S+)'
|
||||
sys_regex = r'system variable = bootflash:/(\S+)'
|
||||
|
||||
kick = re.search(kick_regex, boot_options_raw_text).group(1)
|
||||
sys = re.search(sys_regex, boot_options_raw_text).group(1)
|
||||
retdict = dict(kick=kick, sys=sys)
|
||||
if re.search(r'N3K', pid):
|
||||
type = 'N3K'
|
||||
elif re.search(r'N5K', pid):
|
||||
type = 'N5K'
|
||||
elif re.search(r'N6K', pid):
|
||||
type = 'N6K'
|
||||
elif re.search(r'N7K', pid):
|
||||
type = 'N7K'
|
||||
elif re.search(r'N9K', pid):
|
||||
type = 'N9K'
|
||||
else:
|
||||
nxos_regex = r'NXOS variable = bootflash:/(\S+)'
|
||||
nxos = re.search(nxos_regex, boot_options_raw_text).group(1)
|
||||
retdict = dict(sys=nxos)
|
||||
type = 'unknown'
|
||||
|
||||
command = 'show install all status'
|
||||
retdict['status'] = execute_show_command(command, module)[0]
|
||||
|
||||
return retdict
|
||||
return type
|
||||
|
||||
|
||||
def already_set(current_boot_options, system_image_file, kickstart_image_file):
|
||||
return current_boot_options.get('sys') == system_image_file \
|
||||
and current_boot_options.get('kick') == kickstart_image_file
|
||||
def kickstart_image_required(module):
|
||||
'''Determine if platform requires a kickstart image'''
|
||||
data = execute_show_command(module, 'show version')[0]
|
||||
kickstart_required = False
|
||||
for x in data.split('\n'):
|
||||
if re.search(r'kickstart image file is:', x):
|
||||
kickstart_required = True
|
||||
|
||||
return kickstart_required
|
||||
|
||||
|
||||
def set_boot_options(module, image_name, kickstart=None):
|
||||
"""Set boot variables
|
||||
like system image and kickstart image.
|
||||
Args:
|
||||
The main system image file name.
|
||||
Keyword Args: many implementors may choose
|
||||
to supply a kickstart parameter to specify a kickstart image.
|
||||
def parse_show_install(data):
|
||||
"""Helper method to parse the output of the 'show install all impact' or
|
||||
'install all' commands.
|
||||
|
||||
Sample Output:
|
||||
|
||||
Installer will perform impact only check. Please wait.
|
||||
|
||||
Verifying image bootflash:/nxos.7.0.3.F2.2.bin for boot variable "nxos".
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Verifying image type.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "bios" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Preparing "nxos" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Performing module support checks.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
Notifying services about system upgrade.
|
||||
[####################] 100% -- SUCCESS
|
||||
|
||||
|
||||
|
||||
Compatibility check is done:
|
||||
Module bootable Impact Install-type Reason
|
||||
------ -------- -------------- ------------ ------
|
||||
8 yes disruptive reset Incompatible image for ISSU
|
||||
21 yes disruptive reset Incompatible image for ISSU
|
||||
|
||||
|
||||
Images will be upgraded according to following table:
|
||||
Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
------ ---------- ---------------------------------------- ------------
|
||||
8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
|
||||
8 bios v01.17 v01.17 no
|
||||
21 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
|
||||
21 bios v01.70 v01.70 no
|
||||
"""
|
||||
if len(data) > 0:
|
||||
data = massage_install_data(data)
|
||||
ud = {'raw': data}
|
||||
ud['list_data'] = data.split('\n')
|
||||
ud['processed'] = []
|
||||
ud['disruptive'] = False
|
||||
ud['upgrade_needed'] = False
|
||||
ud['error'] = False
|
||||
ud['install_in_progress'] = False
|
||||
ud['backend_processing_error'] = False
|
||||
ud['upgrade_succeeded'] = False
|
||||
ud['use_impact_data'] = False
|
||||
for x in ud['list_data']:
|
||||
# Check for errors and exit if found.
|
||||
if re.search(r'Pre-upgrade check failed', x):
|
||||
ud['error'] = True
|
||||
break
|
||||
if re.search(r'[I|i]nvalid command', x):
|
||||
ud['error'] = True
|
||||
break
|
||||
if re.search(r'No install all data found', x):
|
||||
ud['error'] = True
|
||||
break
|
||||
|
||||
# Check for potentially transient conditions
|
||||
if re.search(r'Another install procedure may be in progress', x):
|
||||
ud['install_in_progress'] = True
|
||||
break
|
||||
if re.search(r'Backend processing error', x):
|
||||
ud['backend_processing_error'] = True
|
||||
break
|
||||
|
||||
# Check for messages indicating a successful upgrade.
|
||||
if re.search(r'Finishing the upgrade', x):
|
||||
ud['upgrade_succeeded'] = True
|
||||
break
|
||||
if re.search(r'Install has been successful', x):
|
||||
ud['upgrade_succeeded'] = True
|
||||
break
|
||||
|
||||
# We get these messages when the upgrade is non-disruptive and
|
||||
# we loose connection with the switchover but far enough along that
|
||||
# we can be confident the upgrade succeeded.
|
||||
if re.search(r'timeout trying to send command: install', x):
|
||||
ud['upgrade_succeeded'] = True
|
||||
ud['use_impact_data'] = True
|
||||
break
|
||||
if re.search(r'[C|c]onnection failure: timed out', x):
|
||||
ud['upgrade_succeeded'] = True
|
||||
ud['use_impact_data'] = True
|
||||
break
|
||||
|
||||
# Begin normal parsing.
|
||||
if re.search(r'----|Module|Images will|Compatibility', x):
|
||||
ud['processed'].append(x)
|
||||
continue
|
||||
# Check to see if upgrade will be disruptive or non-disruptive and
|
||||
# build dictionary of individual modules and their status.
|
||||
# Sample Line:
|
||||
#
|
||||
# Module bootable Impact Install-type Reason
|
||||
# ------ -------- ---------- ------------ ------
|
||||
# 8 yes disruptive reset Incompatible image
|
||||
rd = r'(\d+)\s+(\S+)\s+(disruptive|non-disruptive)\s+(\S+)'
|
||||
mo = re.search(rd, x)
|
||||
if mo:
|
||||
ud['processed'].append(x)
|
||||
key = 'm%s' % mo.group(1)
|
||||
field = 'disruptive'
|
||||
if mo.group(3) == 'non-disruptive':
|
||||
ud[key] = {field: False}
|
||||
else:
|
||||
ud[field] = True
|
||||
ud[key] = {field: True}
|
||||
field = 'bootable'
|
||||
if mo.group(2) == 'yes':
|
||||
ud[key].update({field: True})
|
||||
else:
|
||||
ud[key].update({field: False})
|
||||
continue
|
||||
|
||||
# Check to see if switch needs an upgrade and build a dictionary
|
||||
# of individual modules and their individual upgrade status.
|
||||
# Sample Line:
|
||||
#
|
||||
# Module Image Running-Version(pri:alt) New-Version Upg-Required
|
||||
# ------ ----- ---------------------------------------- ------------
|
||||
# 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
|
||||
mo = re.search(r'(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(yes|no)', x)
|
||||
if mo:
|
||||
ud['processed'].append(x)
|
||||
key = 'm%s_%s' % (mo.group(1), mo.group(2))
|
||||
field = 'upgrade_needed'
|
||||
if mo.group(5) == 'yes':
|
||||
ud[field] = True
|
||||
ud[key] = {field: True}
|
||||
else:
|
||||
ud[key] = {field: False}
|
||||
continue
|
||||
|
||||
return ud
|
||||
|
||||
|
||||
def massage_install_data(data):
|
||||
# Transport cli returns a list containing one result item.
|
||||
# Transport nxapi returns a list containing two items. The second item
|
||||
# contains the data we are interested in.
|
||||
default_error_msg = 'No install all data found'
|
||||
if len(data) == 1:
|
||||
result_data = data[0]
|
||||
elif len(data) == 2:
|
||||
result_data = data[1]
|
||||
else:
|
||||
result_data = default_error_msg
|
||||
|
||||
# Further processing may be needed for result_data
|
||||
if len(data) == 2 and isinstance(data[1], dict):
|
||||
if 'clierror' in data[1].keys():
|
||||
result_data = data[1]['clierror']
|
||||
elif 'code' in data[1].keys() and data[1]['code'] == '500':
|
||||
# We encountered a backend processing error for nxapi
|
||||
result_data = data[1]['msg']
|
||||
else:
|
||||
result_data = default_error_msg
|
||||
return result_data
|
||||
|
||||
|
||||
def build_install_cmd_set(issu, image, kick, type):
|
||||
commands = ['terminal dont-ask']
|
||||
if kickstart is None:
|
||||
commands.append('install all nxos %s' % image_name)
|
||||
if re.search(r'required|desired|yes', issu):
|
||||
issu_cmd = 'non-disruptive'
|
||||
else:
|
||||
issu_cmd = ''
|
||||
if type == 'impact':
|
||||
rootcmd = 'show install all impact'
|
||||
else:
|
||||
rootcmd = 'install all'
|
||||
if kick is None:
|
||||
commands.append(
|
||||
'%s nxos %s %s' % (rootcmd, image, issu_cmd))
|
||||
else:
|
||||
commands.append(
|
||||
'install all system %s kickstart %s' % (image_name, kickstart))
|
||||
load_config(module, commands)
|
||||
'%s system %s kickstart %s' % (rootcmd, image, kick))
|
||||
return commands
|
||||
|
||||
|
||||
def parse_show_version(data):
|
||||
version_data = {'raw': data[0].split('\n')}
|
||||
version_data['version'] = ''
|
||||
version_data['error'] = False
|
||||
for x in version_data['raw']:
|
||||
mo = re.search(r'(kickstart|system|NXOS):\s+version\s+(\S+)', x)
|
||||
if mo:
|
||||
version_data['version'] = mo.group(2)
|
||||
continue
|
||||
|
||||
if version_data['version'] == '':
|
||||
version_data['error'] = True
|
||||
|
||||
return version_data
|
||||
|
||||
|
||||
def check_mode_legacy(module, issu, image, kick=None):
|
||||
"""Some platforms/images/transports don't support the 'install all impact'
|
||||
command so we need to use a different method."""
|
||||
current = execute_show_command(module, 'show version', 'json')[0]
|
||||
# Call parse_show_data on empty string to create the default upgrade
|
||||
# data stucture dictionary
|
||||
data = parse_show_install('')
|
||||
upgrade_msg = 'No upgrade required'
|
||||
|
||||
# Process System Image
|
||||
data['error'] = False
|
||||
tsver = 'show version image bootflash:%s' % image
|
||||
target_image = parse_show_version(execute_show_command(module, tsver))
|
||||
if target_image['error']:
|
||||
data['error'] = True
|
||||
data['raw'] = target_image['raw']
|
||||
if current['kickstart_ver_str'] != target_image['version'] and not data['error']:
|
||||
data['upgrade_needed'] = True
|
||||
data['disruptive'] = True
|
||||
upgrade_msg = 'Switch upgraded: system: %s' % tsver
|
||||
|
||||
# Process Kickstart Image
|
||||
if kick is not None and not data['error']:
|
||||
tkver = 'show version image bootflash:%s' % kick
|
||||
target_kick = parse_show_version(execute_show_command(module, tkver))
|
||||
if target_kick['error']:
|
||||
data['error'] = True
|
||||
data['raw'] = target_kick['raw']
|
||||
if current['kickstart_ver_str'] != target_kick['version'] and not data['error']:
|
||||
data['upgrade_needed'] = True
|
||||
data['disruptive'] = True
|
||||
upgrade_msg = upgrade_msg + ' kickstart: %s' % tkver
|
||||
|
||||
data['processed'] = upgrade_msg
|
||||
return data
|
||||
|
||||
|
||||
def check_mode_nextgen(module, issu, image, kick=None):
|
||||
"""Use the 'install all impact' command for check_mode"""
|
||||
opts = {'ignore_timeout': True}
|
||||
commands = build_install_cmd_set(issu, image, kick, 'impact')
|
||||
data = parse_show_install(load_config(module, commands, True, opts))
|
||||
# If an error is encountered when issu is 'desired' then try again
|
||||
# but set issu to 'no'
|
||||
if data['error'] and issu == 'desired':
|
||||
issu = 'no'
|
||||
commands = build_install_cmd_set(issu, image, kick, 'impact')
|
||||
# The system may be busy from the previous call to check_mode so loop
|
||||
# until it's done.
|
||||
data = check_install_in_progress(module, commands, opts)
|
||||
if re.search(r'No install all data found', data['raw']):
|
||||
data['error'] = True
|
||||
return data
|
||||
|
||||
|
||||
def check_install_in_progress(module, commands, opts):
|
||||
for attempt in range(20):
|
||||
data = parse_show_install(load_config(module, commands, True, opts))
|
||||
if data['install_in_progress']:
|
||||
sleep(1)
|
||||
continue
|
||||
break
|
||||
return data
|
||||
|
||||
|
||||
def check_mode(module, issu, image, kick=None):
|
||||
"""Check switch upgrade impact using 'show install all impact' command"""
|
||||
data = check_mode_nextgen(module, issu, image, kick)
|
||||
if data['backend_processing_error']:
|
||||
# We encountered an unrecoverable error in the attempt to get upgrade
|
||||
# impact data from the 'show install all impact' command.
|
||||
# Fallback to legacy method.
|
||||
data = check_mode_legacy(module, issu, image, kick)
|
||||
return data
|
||||
|
||||
|
||||
def do_install_all(module, issu, image, kick=None):
|
||||
"""Perform the switch upgrade using the 'install all' command"""
|
||||
impact_data = check_mode(module, issu, image, kick)
|
||||
if module.check_mode:
|
||||
# Check mode set in the playbook so just return the impact data.
|
||||
msg = '*** SWITCH WAS NOT UPGRADED: IMPACT DATA ONLY ***'
|
||||
impact_data['processed'].append(msg)
|
||||
return impact_data
|
||||
if impact_data['error']:
|
||||
# Check mode discovered an error so return with this info.
|
||||
return impact_data
|
||||
elif not impact_data['upgrade_needed']:
|
||||
# The switch is already upgraded. Nothing more to do.
|
||||
return impact_data
|
||||
else:
|
||||
# If we get here, check_mode returned no errors and the switch
|
||||
# needs to be upgraded.
|
||||
if impact_data['disruptive']:
|
||||
# Check mode indicated that ISSU is not possible so issue the
|
||||
# upgrade command without the non-disruptive flag.
|
||||
issu = 'no'
|
||||
commands = build_install_cmd_set(issu, image, kick, 'install')
|
||||
opts = {'ignore_timeout': True}
|
||||
# The system may be busy from the call to check_mode so loop until
|
||||
# it's done.
|
||||
upgrade = check_install_in_progress(module, commands, opts)
|
||||
|
||||
# Special case: If we encounter a backend processing error at this
|
||||
# stage it means the command was sent and the upgrade was started but
|
||||
# we will need to use the impact data instead of the current install
|
||||
# data.
|
||||
if upgrade['backend_processing_error']:
|
||||
upgrade['upgrade_succeeded'] = True
|
||||
upgrade['use_impact_data'] = True
|
||||
|
||||
if upgrade['use_impact_data']:
|
||||
if upgrade['upgrade_succeeded']:
|
||||
upgrade = impact_data
|
||||
upgrade['upgrade_succeeded'] = True
|
||||
else:
|
||||
upgrade = impact_data
|
||||
upgrade['upgrade_succeeded'] = False
|
||||
|
||||
if not upgrade['upgrade_succeeded']:
|
||||
upgrade['error'] = True
|
||||
return upgrade
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
system_image_file=dict(required=True),
|
||||
kickstart_image_file=dict(required=False),
|
||||
issu=dict(choices=['required', 'desired', 'no', 'yes'], default='no'),
|
||||
)
|
||||
|
||||
argument_spec.update(nxos_argument_spec)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
supports_check_mode=True)
|
||||
|
||||
warnings = list()
|
||||
check_args(module, warnings)
|
||||
|
||||
system_image_file = module.params['system_image_file']
|
||||
kickstart_image_file = module.params['kickstart_image_file']
|
||||
# This module will error out if the Ansible task timeout value is not
|
||||
# tuned high enough.
|
||||
check_ansible_timer(module)
|
||||
|
||||
if kickstart_image_file == 'null':
|
||||
kickstart_image_file = None
|
||||
# Get system_image_file(sif), kickstart_image_file(kif) and
|
||||
# issu settings from module params.
|
||||
sif = module.params['system_image_file']
|
||||
kif = module.params['kickstart_image_file']
|
||||
issu = module.params['issu']
|
||||
|
||||
current_boot_options = get_boot_options(module)
|
||||
changed = False
|
||||
if not already_set(current_boot_options,
|
||||
system_image_file,
|
||||
kickstart_image_file):
|
||||
changed = True
|
||||
if kif == 'null' or kif == '':
|
||||
kif = None
|
||||
|
||||
install_state = current_boot_options
|
||||
if not module.check_mode and changed is True:
|
||||
set_boot_options(module,
|
||||
system_image_file,
|
||||
kickstart=kickstart_image_file)
|
||||
if kickstart_image_required(module) and kif is None:
|
||||
msg = 'This platform requires a kickstart_image_file'
|
||||
module.fail_json(msg=msg)
|
||||
|
||||
if not already_set(install_state,
|
||||
system_image_file,
|
||||
kickstart_image_file):
|
||||
module.fail_json(msg='Install not successful',
|
||||
install_state=install_state)
|
||||
install_result = do_install_all(module, issu, sif, kick=kif)
|
||||
if install_result['error']:
|
||||
msg = "Failed to upgrade device using image "
|
||||
if kif:
|
||||
msg = msg + "files: kickstart: %s, system: %s" % (kif, sif)
|
||||
else:
|
||||
msg = msg + "file: system: %s" % sif
|
||||
module.fail_json(msg=msg, raw_data=install_result['list_data'])
|
||||
|
||||
module.exit_json(changed=changed, install_state=install_state, warnings=warnings)
|
||||
state = install_result['processed']
|
||||
changed = install_result['upgrade_needed']
|
||||
module.exit_json(changed=changed, install_state=state, warnings=warnings)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -205,7 +205,6 @@ lib/ansible/modules/network/nxos/nxos_gir_profile_management.py
|
|||
lib/ansible/modules/network/nxos/nxos_igmp.py
|
||||
lib/ansible/modules/network/nxos/nxos_igmp_interface.py
|
||||
lib/ansible/modules/network/nxos/nxos_igmp_snooping.py
|
||||
lib/ansible/modules/network/nxos/nxos_install_os.py
|
||||
lib/ansible/modules/network/nxos/nxos_ntp_auth.py
|
||||
lib/ansible/modules/network/nxos/nxos_ntp_options.py
|
||||
lib/ansible/modules/network/nxos/nxos_nxapi.py
|
||||
|
|
Loading…
Reference in a new issue