Motivation
A reoccuring task of a network administrator is the upgrade of all kinds of devices in the network. A boring task that takes its time. A worthy task for automation. In this artice I want to present a collection of playbooks that will do the upgrade.
My intention was to make the playbooks modular based on the switch models. For every switch or router model Cisco offers different upgrade paths. So I have an umbrella playbook that determins if an upgrade is nescessary and than includes the correct task according to the device model.
My work was inspired by the work of Gerald Dykeman: Upgrading Cisco IOS Devices with Ansible
Inventory
In my inventory I define the devices and group them by the vendor and the device model. The vendor group defines the access to the device and in the model group I define the IOS version, I would like to have on my devices:
all:
hosts:
switch01:
ansible_host: 192.0.2.1
switch02:
ansbile_host: 192.0.2.2
children:
cisco:
hosts:
switch01:
switch02:
vars:
ansible_connection: network_cli
ansible_network_os: ios
c2960l:
hosts:
switch01:
vars:
compliant_ios_version: 15.2(7)E1
ios_hash: b8bebb153c0fe95104f18785be2759e9
ios_file: c2960l-universalk9-mz.152-7.E1.bin
c9200l:
hosts:
switch02:
vars:
compliant_ios_version: 16.12.02
ios_hash: 9ba9f898d529f5132b78bca636e95576
ios_file: cat9k_lite_iosxe.16.12.02.SPA.bin
Of course, you also can define the devices, groups, and variables in a suitable backend assset management tool like netbox. In a productive enviromment with lots of devices you will use such an inventory backend anyway.
Meta-Playbook
The umbrella playbook checks if the upgrade is nescessary and delegates the work to the playbooks that take care of the specific switch models. The need for an upgrade resuls from a comparison of the running IOS version and the desired version defined in the inventory.
- hosts: cisco
# Define the device model by parsing the ios fact
vars:
model: "{{ ansible_net_model | regex_replace('^WS-C(\\d+.).*$', '\\1') }}"
tasks:
- name: Include model series specific upgrade
block:
- name: do include
include: "ios-upgrade-{{ model }}.yaml"
# only if the running IOS version is not the desired version
when: (ansible_net_version != compliant_ios_version)
If the running verison differs from the desired version defined in the inventory, the playbook passed the control to an other playbook specific for the device model.
Playbook for 2960L
The low-cost (ha, ha! yes, I know) access models from Cisco are the 2960L. The playbook for the upgrade of this model is:
- name: Copy IOS to device
ios_command:
commands:
- command: 'copy tftp://192.0.2.16/{{ ios_file }} flash:'
prompt: 'Destination filename \[{{ ios_file }}\]?'
answer: "\r"
vars:
ansible_command_timeout: 600
- name: Check md5sum of IOS on device
ios_command:
commands:
- command: 'verify /md5 flash:{{ ios_file }} {{ ios_hash }}'
register: md5_output
- name: Continue if hash matches
block:
- name: Set new boot variable
ios_config:
lines: boot system flash:{{ ios_file }}
save_when: always
- name: Reboot device
ios_command:
commands:
- command: reload
prompt: '\[confirm\]'
answer: "\r"
- name: Wait for switch to reboot
wait_for:
host: "{{ ansible_host }}"
port: 22
delay: 60
timeout: 600
delegate_to: localhost
- name: Gather facts of the switch again
ios_facts:
- name: Assert that the running IOS version is correct
assert:
that:
- compliant_ios_version == ansible_net_version
msg: "IOS version does not match compliant version. Upgrade unsuccessful."
when: '"Verified" in md5_output.stdout[0]'
Playbook for 9200L
Enterprise grade access switches are the various 9200 models. The upgrade of its operating system is different:
- name: remove old OS files
ios_command:
commands:
- command: install remove inactive
prompt: Do you want to remove the above files
answer: 'y'
vars:
ansible_command_timeout: 300
- name: Copy IOS to device
ios_command:
commands:
- command: 'copy tftp://192.0.2.16/srv/tftp/{{ ios_file }} flash: vrf Mgmt-vrf'
prompt: 'Destination filename \[{{ ios_file }}\]?'
answer: "\r"
vars:
ansible_command_timeout: 1800
- name: Check md5sum of IOS on device
ios_command:
commands:
- command: 'verify /md5 flash:{{ ios_file }} {{ ios_hash }}'
register: md5_output
- name: Continue if hash matches
block:
- name: Set new boot variable
ios_config:
lines: boot system flash:packages.conf
save_when: always
- name: Write config
ios_command:
commands:
- command: wr mem
- name: Install new IOS
ios_command:
commands:
- command: 'install add file flash:{{ ios_file }} activate commit prompt-level none'
vars:
ansible_command_timeout: 1800
- name: Wait for switch to reboot
wait_for:
host: "{{ ansible_host }}"
port: 22
delay: 60
timeout: 600
delegate_to: localhost
- name: Gather facts of the switch again
ios_facts:
- name: Assert that the running IOS version is correct
assert:
that:
- compliant_ios_version == ansible_net_version
msg: "IOS version does not match compliant version. Upgrade unsuccessful."
when: '"Verified" in md5_output.stdout[0]'
Please note the huge timeouts for the copy and the install operation. Feedback, improvements and other comments to ms@sys4.de.