Some usage tips and tricks from real world FreeZTP deployments.
- Use-case: Provisioning Without Vlan1
- Use-case: Upgrade IOS-XE 3.7.x to 16.3.6
- Use-case: Automated IOS-XE Stack Renumbering
- Tip: Advanced Jinja Syntax
- Use Case: Multi-Platform IOS / IOS-XE Upgrade
- Use Case: Automatic Stack Reordering - Alternate Method
Author: derek-shnosh, Rev: 1, Date: 2018.1008, FreeZTP v1.1.0
The use of Vlan1 is not required for provisioning. The client switch running the smart-install process will still bring up all interfaces as dynamic desirable (or other default behavior) on Vlan1; however, disabling CDP and enabling BPDU filter will circumvent any undesirable spanning-tree behavior that would otherwise interfere with the link coming up between the master and client switches.
NOTE: A client switch should only be connected to the master provisioning switch during provisioning; i.e. a client switch should never be connected to the provisioning environment and production infrastructure during the provisioning process, spanning-tree loops can occur.
-
Configure the master provisioning switch interfaces as follows; Replace
<n>
with interfaces that client switches will connect to.interface <n> desc PROVISION switchport access vlan 3967 switchport mode access switchport nonegotiate no cdp enable spanning-tree portfast spanning-tree bpdufilter enable
-
Interface config assumes the following details for the provisioning environment, adjust accordingly.
VLAN Subnet IP Allocation 3967 172.31.255.0/24 .1 - Gateway (optional)
.2 - FreeZTP server
.5 - .254 DHCP range
Author: derek-shnosh, Rev: 2, Date: 2018.1224, FreeZTP v1.1.0
IOS-XE 3.7.4 cannot upgrade to 16.3.6 via smart-install because new force
isn't appended. This workaround utilizes EEM applets in a Jinja2 switch template to download install the updated image.
Would you like to enter the initial configuration dialog? [yes/no]:
Loading ztp_ios_upgrade from 172.17.251.251 (via Vlan1): !
[OK - 38 bytes]
Preparing install operation ...
[1]: Downloading file tftp://172.17.251.251/cat3k_caauniversalk9.16.03.06.
SPA.bin to active switch 1
[1]: Finished downloading file tftp://172.17.251.251/cat3k_caauniversalk9.16.03.06.
SPA.bin to active switch 1
[1]: Copying software from active switch 1 to switch 2
[1]: Finished copying software to switch 2
[1 2]: Starting install operation
[1 2]: Expanding bundle cat3k_caa-universalk9.16.03.06.SPA.bin
[1 2]: Copying package files
[1 2]: Package files copied
[1 2]: Finished expanding bundle cat3k_caa-universalk9.16.03.06.SPA.bin
[1 2]: Verifying and copying expanded package files to flash:
[1 2]: Verified and copied expanded package files to flash:
[1 2]: Starting compatibility checks
[1]: % Candidate package compatibility checks failed because the following
package dependencies were not satisfied. Operation aborted.
[2]: % Candidate package compatibility checks failed because the following
package dependencies were not satisfied. Operation aborted.
[1]: % An internal error was encountered. Operation aborted.
[2]: % An internal error was encountered. Operation aborted.
ERROR: Software Installation Failed: 35 2
Loading network-confg from 172.17.251.251 (via Vlan1): !
[OK - 69 bytes]
Loading ZTP-23CFBA478F-confg from 172.17.251.251 (via Vlan1): !
[OK - 77012 bytes]
The behavior is expected due to the command syntax difference as you suspected. We documented the behavior in a bug below: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCvd49193. The running code 3.7.4E is affected and this issue is fixed on 3.6.8E.
Is it possible for you to try the following?
1- Upgrade from 3.7.4E to 3.6.8E via smart install
2- Upgrade from 3.6.8E to 16.3.6 via smart install
- Validated on Cisco 3850-12X48U-S switches with IOS-XE 3.7.4E installed out of the box; upgrading to IOS-XE 16.3.6.
- Default TFTP blocksize is 512 on IOS-XE 3.7.4E, and 8192 on IOS-XE 16.3.6; adding this to the template significantly reduces the image transfer time.
- The J2 template should not contain any configuration or syntax that is incompatible in IOS-XE version (3.7.4E). Any commands that are compatible with later IOS-XE versions only should be added to the
post_ztp_2
applet.
- Triggered by the switch receiving a DHCP address on Vlan1; notes regarding the
maxrun
andwait
;action 01.00 ... maxrun 900
- 15 minutes to accommodate the 2 minute wait, the 4-5 minute TFTP download, and the 5-6 minute install.action 01.01 wait 120
- Vlan1 obtains a DHCP address approximately 1.5 minutes before the switch will allow configurations if stacked (see IOS-XE 3.7.4E Log below). This wait can be omitted for stand-alone switches.
*21:32:15.992: %DHCP-6-ADDRESS_ASSIGN: Interface Vlan1 assigned DHCP address 172.17.250.6, mask 255.255.254.0
...
*21:33:42.831: %HA_CONFIG_SYNC-6-BULK_CFGSYNC_SUCCEED: Bulk Sync succeeded
*21:33:43.824: %RF-5-RF_TERMINAL_STATE: 1 ha_mgr: Terminal state reached for (SSO)
- Triggered by IOS-XE 16.x specific redundancy syslog message; notes regarding the
maxrun
andwait
;action 01.00 ... maxrun 600
- 10 minutes to accommodate the 2 minute wait, any optional configuration changes and the software package clean process.action 01.01 wait 120
- Redundancy syslog message is logged approximately 1.75 minutes before the switch will allow configurations if stacked (see IOS-XE 16.3.6 Log below). This wait can be omitted for stand_alone switches.
21:54:16.002 PDT: %IOSXE_REDUNDANCY-6-PEER: Active detected switch 2 as standby.
...
21:56:00.711 PDT: %HA_CONFIG_SYNC-6-BULK_CFGSYNC_SUCCEED: Bulk Sync succeeded
21:56:01.735 PDT: %RF-5-RF_TERMINAL_STATE: Terminal state reached for (SSO)
- Switch is powered up connected to the provisioning network and initiates smart-install. Switch requests an upgrade first but no image is downloaded since image download is disabled in FreeZTP.
- Switch requests config, FreeZTP gives a (merged) config containing the required config.
- Switch applies configuration with Vlan1 configured for DHCP addressing.
post_ztp_1
is triggered by Vlan1 obtaining a DHCP address.
- [EEM Applet
post_ztp_1
loaded to memory.]- Applet deletes itself from running config, downloads the bin file, cleans up temp configs and writes the startup-config.
- Switch then runs the
software install
command, answers y to reload prompt from install command.
- Switch reloads.
post_ztp_2
is triggered by a syslog command specific to IOS-XE 16.x (%IOSXE_REDUNDANCY-6-PEER).This trigger may need to be changed! I'm unsure if this syslog message is present when provisioning a stand-alone switch as I never tested this scenario. I will update if I get an opportunity to test this on a stand-alone switch.
- [EEM applet
post_ztp_2
loaded into memory.]- Applet deletes itself from running configuration and writes the startup-config.
- (Optional) Add action sequences for any IOS-XE 16.x specific commands.
- Applet runs the package clean process to delete the old .pkg and packages.conf file(s).
-
Disable FreeZTP image downloads, replace
<SCOPE>
with the name of your configured DHCP scope.ztp set dhcpd <SCOPE> imagediscoveryfile-option disable && \ ztp request dhcpd-commit && \ ztp service restart
-
Allocate a provisioning interface as
prov_int
; i.e. the interface connected to the provisioning network. -
Modify the variables in the template config snippet below to suit network/needs, then add the whole snippet to the J2 switch template.
These four variables can be defined in the keystore or left in the template.
Variable Description tftp_addr
Address of TFTP server, typically FreeZTP. access_vlan
Vlan to configure on the provisioning interface (Gi1/0/48) after upgrade/reload is complete. prov_int
Interface to be used for provisioning; e.g. Te1/0/48 (3850-12X48U-S interfaces 37-48 are TenGigabitEthernet.) image.bin/ver
Name of the image file to download, and the image version short-hand.
!-- EEM applet to upgrade switches accordingly (ALL SUBSEQUENT LINES ARE REQUIRED).
!---- {%set sw_count=idarray|count%}
!---- {%set tftp_addr="172.17.251.251"%}
!---- {%set access_vlan="501"%}
!---- {%set prov_int="Te1/0/48"%}
!---- {%set image={"bin":"cat3k_caa-universalk9.16.03.06.SPA.bin",
"ver":"16.3.6"}%}
!
!-- Required for EEM applet to function as intended.
logging buffered 20480 debugging
file prompt quiet
ip tftp blocksize 8192
!-- Required for TFTP transfers from FreeZTP (or other reachable TFTP server; i.e. `tftp_addr`).
interface Vlan1
ip address dhcp
no shutdown
!-- Interface that is connected to the provisioning network, must remain on Vlan1 for TFTP download.
interface GigabitEthernet1/0/48
description TMP//PROVISION:Omit config; updated with post_ztp_2 EEM applet.
switchport
switchport mode access
switchport nonegotiate
switchport access vlan 1
spanning-tree portfast
no shutdown
!
event manager environment q "
!
event manager applet sw_upgrade
!-- Check all switches in stack to see if an upgrade is needed.
event syslog occurs 1 pattern "Configured from tftp://{{tftp_addr}}" maxrun 960
action 00.00 syslog msg "\n ## Configuration received via TFTP, run 'sw_upgrade' EEM applet in 120s."
action 00.01 wait 120
action 00.02 syslog msg "\n ## Checking all switches' version."
action 00.03 cli command "enable"
action 00.04 cli command "show mod | i ^.[1-9]"
action 00.05 set stack "$_cli_result"
action 00.06 syslog msg "\n ## Current list of switches in stack;\n$stack"
action 00.07 set error_list ""
action 00.08 set upgrade_list ""
!{% for sw in idarray %}
!{% set i = loop.index %}
action 0{{i}}.00 set sw_num "{{i}}"
action 0{{i}}.01 set pri "16"
action 0{{i}}.02 decrement pri {{i}}
action 0{{i}}.03 regexp "{{sw}}" "$stack"
action 0{{i}}.04 if $_regexp_result ne "1"
action 0{{i}}.05 append error_list "\n ## {{sw}} is allocated (idarray_{{i}}) but was not found in the stack."
action 0{{i}}.06 else
action 0{{i}}.07 set i "0"
action 0{{i}}.08 foreach line "$stack" "\n"
action 0{{i}}.09 increment i
action 0{{i}}.10 if $i le "{{sw_count}}"
action 0{{i}}.11 string trim "$line"
action 0{{i}}.12 set line "$_string_result"
action 0{{i}}.13 regexp "{{sw}}" "$line"
action 0{{i}}.14 if $_regexp_result eq "1"
action 0{{i}}.15 regexp "([0-9\.A-Z]+$)" "$line" curr_ver
action 0{{i}}.16 if $curr_ver ne "{{image.ver}}"
action 0{{i}}.17 append upgrade_list "{{i}}"
action 0{{i}}.18 end
action 0{{i}}.19 break
action 0{{i}}.20 end
action 0{{i}}.21 end
action 0{{i}}.22 end
action 0{{i}}.23 end
!{% endfor %}
action 10.00 wait 5
action 10.01 if $error_list ne ""
action 10.02 syslog msg "\n ## The following errors occurred; $error_list"
action 10.03 end
action 11.00 cli command "conf t"
action 11.01 cli command "no event man app sw_upgrade"
action 12.00 if $upgrade_list eq ""
action 12.01 syslog msg "\n ## All switches are running version {{target_ver}}, skipping download->upgrade/reload.\n ## Finalizing config in 20s."
action 12.02 cli command "no event man env q"
action 12.03 cli command "event man env ztp_upgraded no"
action 12.04 cli command " event man app post_upgrade"
action 12.05 cli command " event timer countdown time 20 maxrun 480"
action 12.06 cli command " no action 00.00"
action 12.07 cli command " no action 00.01"
action 12.08 cli command " end"
action 12.09 cli command "write mem" pattern "confirm|#"
action 12.10 cli command ""
action 12.11 else
action 12.12 syslog msg "\n ## One or more switches require an upgrade to version {{target_ver}} ($upgrade_list).\n ## Proceeding with download->upgrade/reload."
action 12.13 cli command "event man env ztp_upgraded yes"
action 12.14 cli command "event man app post_upgrade"
action 12.15 cli command " event syslog occurs 1 pattern $q%IOSXE_REDUNDANCY-6-PEER$q maxrun 630"
action 12.16 cli command " no event man env q"
action 12.17 cli command "end"
action 12.18 cli command "write mem" pattern "confirm|#"
action 12.19 cli command ""
action 12.20 syslog msg "\n ## (Standby) Downloading image..."
action 12.21 cli command "copy tftp://{{tftp_addr}}/{{image.bin}} flash:"
action 12.22 syslog msg "\n ## (Standby) Image downloaded, upgrading..."
action 12.23 cli command "software install file flash:{{image.bin}} new force" pattern "proceed|#"
action 12.24 syslog msg "\n ## Upgrade complete, rebooting."
action 12.25 cli command "y"
action 12.26 end
!
event manager applet post_upgrade
!-- Clean up VL-1 and `prov_int` configs, write mem, and then perform package clean.
!-- (Optional) Add any desired configs between actions 03.00 and 04.00.
event none
action 00.00 syslog msg "\n ## Switch reloaded on new image, running 'post_upgrade' EEM applet in 150s."
action 00.01 wait 150
action 00.02 syslog msg "\n ## Applying global configs ignored by smart-install and generating crypto key."
action 00.03 cli command "enable"
action 00.04 set upgr "$ztp_upgraded"
action 01.00 cli command "conf t"
action 01.01 cli command "no event man env ztp_upgraded"
action 01.02 cli command "no event man app post_upgrade"
!-- action 02.00 cli command "" {# Use actions 02.00 - 02.99 for desired configs or configs specific to later IOS-XE version(s). #}
action 03.00 syslog msg "\n ## Disabling VL-1 SVI, updating Te#/0/48 configs, and writing startup config."
action 03.01 cli command "int vl 1"
action 03.02 cli command " no desc"
action 03.03 cli command " no ip addr"
action 03.04 cli command " shut"
action 03.05 cli command "default range {{prov_int}}"
action 03.06 cli command "int {{prov_int}}"
!-- action 03.07 cli command "" {# Use actions 3.07 - 3.99 to configure `prov_int` as desired. #}
action 04.00 cli command "end"
action 04.01 cli command "write mem" pattern "confirm|#"
action 04.02 cli command ""
action 05.00 if $upgr eq "yes"
action 05.01 syslog msg "\n ## (Standby) ZTP upgrade detected, performing software package clean..."
action 05.02 cli command "req plat soft pack clean sw all" pattern "proceed|#"
action 05.03 cli command "y"
action 05.04 syslog msg "\n ## Unused .bin or .pkg files from previous version(s) have been deleted."
action 05.05 else
action 05.06 syslog msg "\n ## ZTP upgrade not detected, skipping software package clean."
action 05.07 end
action 05.08 syslog msg "\n ## Start-up config written, ({{hostname}}) is ready for deployment, OK to power off."
Author: derek-shnosh, Rev: 5b, Date: 2019.0823, FreeZTP v1.1.0
Include this snippet in a FreeZTP Jinja2 template to create an EEM applet that will renumber and prioritize all switches in a stack according to how they were allocated in the FreeZTP keystore.
It's almost guaranteed that stacked switches will not be numbered in the desired order if powered on simultaneously for the first time. The first switch in the stack might be assigned switch number 3, and the second switch might be assigned switch number 1, etc.
This can be circumvented by waiting 2 minutes between powering up switches. However this becomes a bit cumbersome and inefficient when provisioning a large amount of switch stacks.
With this snippet all switches in the stack can be powered on simultaneously. After the election processes are complete the stack will continue booting and then perform the smart-install procedure where it will receive the EEM applet as part of the templated configuration.
In this use-case, FreeZTP's idarray
variables are being used to define stack member serial numbers. When the template is merged with the keystore, an action sequence is generated for each serial number (idarray_#
) associated with the hostname (keystore_id
). Switch serial numbers should be assigned to idarray_#
's as they're meant to be numbered in the stack (see the CSV example below).
- Configuration snippet to be added to the Jinja2 template.
- The first section serves to have the
IDARRAY
variables (passed from FreeZTP) appear in the merged config; these lines are ignored by the switch due to the leading!
notations (commented out). - Everything below
!-- EEM applet to renumber switches accordingly
is required.
- The first section serves to have the
!-- Variables (keys) parsed from CSV keystore.
!---- IDARRAY_1 (switch 1 serial number): {{idarray_1}}
!---- IDARRAY_2 (switch 2 serial number): {{idarray_2}}
!---- IDARRAY_3 (switch 3 serial number): {{idarray_3}}
!---- IDARRAY_4 (switch 4 serial number): {{idarray_4}}
!---- IDARRAY_5 (switch 5 serial number): {{idarray_5}}
!---- IDARRAY_6 (switch 6 serial number): {{idarray_6}}
!---- IDARRAY_7 (switch 7 serial number): {{idarray_7}}
!---- IDARRAY_8 (switch 8 serial number): {{idarray_8}}
!---- IDARRAY_9 (switch 9 serial number): {{idarray_9}}
!---- IDARRAY (all serials): {{idarray}}
!
!-- EEM applet to renumber switches accordingly (ALL SUBSEQUENT LINES ARE REQUIRED).
!---- SW_COUNT (count of serials found in IDARRAY): {%set sw_count=idarray|count%}{{sw_count}}
event manager applet sw_stack
event syslog occurs 1 pattern "%SYS-5-CONFIG_I: Configured from tftp" maxrun 75
action 00.00 syslog msg "\n ## FreeZTP configuration received via TFTP, run 'sw_stack' EEM applet in 15s."
action 00.01 wait 15
action 00.02 cli command "enable"
action 00.03 cli command "show mod | i ^.[1-9]"
action 00.04 set stack "$_cli_result"
action 00.05 syslog msg "\n ## Checking all switches' version and stack membership, adjusting where necessary.\n ## Current order;\n$stack"
action 00.06 set error_list ""
action 00.07 set change_list ""
action 00.08 set upgrade_list ""
{%for sw in idarray%}
{%- set i=loop.index%}
action 0{{i}}.00 set sw_num "{{i}}"
action 0{{i}}.01 set pri "16"
action 0{{i}}.02 decrement pri {{i}}
action 0{{i}}.03 regexp "{{sw}}" "$stack"
action 0{{i}}.04 if $_regexp_result ne "1"
action 0{{i}}.05 syslog msg "\n ## {{sw}} (Sw-{{i}} serial) not found in the stack, check 'show mod' output."
action 0{{i}}.06 append error_list "\n ## {{sw}} is allocated (idarray_{{i}}) but was not found in the stack."
action 0{{i}}.07 else
action 0{{i}}.08 set i "0"
action 0{{i}}.09 foreach line "$stack" "\n"
action 0{{i}}.10 increment i
action 0{{i}}.11 if $i le "{{sw_count}}"
action 0{{i}}.12 string trim "$line"
action 0{{i}}.13 set line "$_string_result"
action 0{{i}}.14 regexp "{{sw}}" "$line"
action 0{{i}}.15 if $_regexp_result eq "1"
action 0{{i}}.16 regexp "([0-9\.A-Z]+$)" "$line" curr_ver
action 0{{i}}.17 cli command "switch $i priority $pri" pattern "continue|#"
action 0{{i}}.18 cli command "y"
action 0{{i}}.19 if $i eq $sw_num
action 0{{i}}.20 append change_list "\n ## {{sw}} (Priority: $pri // Numbered: $sw_num // Version: $curr_ver)"
action 0{{i}}.21 else
action 0{{i}}.22 cli command "switch $i renumber $sw_num" pattern "continue|#"
action 0{{i}}.23 cli command "y"
action 0{{i}}.24 append change_list "\n ## {{sw}} (Priority: $pri // Renumbered: $i > $sw_num* // Version: $curr_ver)"
action 0{{i}}.25 end
action 0{{i}}.26 break
action 0{{i}}.27 end
action 0{{i}}.28 end
action 0{{i}}.29 end
action 0{{i}}.30 end
{%endfor%}
action 10.00 wait 5
action 10.01 if $error_list ne ""
action 10.02 syslog msg "\n ## The following errors occurred; $error_list"
action 10.03 end
action 10.04 syslog msg "\n ## Switches below have been assigned a priority and renumbered* as needed; $change_list"
action 10.05 cli command "conf t"
action 10.06 cli command "no event man app sw_stack"
action 10.07 cli command "end"
action 10.08 cli command "write mem" pattern "confirm|#"
action 10.09 cli command ""
action 10.10 syslog msg "\n ## EEM applet (sw_stack) deleted and config written, reload for changes to take effect."
!
In this example, there are four switches allocated to the stack ASW-TR01-01;
Order/Array # | Serial # | MAC Address | Notes |
---|---|---|---|
idarray_1 |
FOC11111111 | abcd.ef11.1111 | Should be switch 1 in the stack, with a priority of 15. |
idarray_2 |
FOC22222222 | abcd.ef22.2222 | Should be switch 2 in the stack, with a priority of 14. |
idarray_3 |
FOC33333333 | abcd.ef33.3333 | Should be switch 3 in the stack, with a priority of 13. |
idarray_4 |
FOC44444444 | abcd.ef44.4444 | Should be switch 4 in the stack, with a priority of 12. |
Variables defined in the keystore (FreeZTP);
keystore_id
= Hostname of the switch.association
= J2 template configured in FreeZTP.idarray_#
= Switch serial number(s).
idarray_#
fields are populated with serial numbers in accordance with how they are to be numbered in the stack.
keystore_id,association,idarray_1,idarray_2,idarray_3,idarray_4,idarray_5,idarray_6,idarray_7,idarray_8,idarray_9
ASW-TR01-01,TEST,FOC11111111,FOC22222222,FOC33333333,FOC44444444,,,,,
The default priority for all switches (out of the box) is 1; these priorities do not change during the election process.
Notice that all switches have a priority of 1 in the output below. The priority range is 1-15; the (online) switch with highest priority will be chosen as the active 'supervisor' switch during any election processes/failure events.
ASW-TR01-01#show switch
Switch/Stack Mac Address : abcd.ef22.2222 - Local Mac Address
Mac persistency wait time: Indefinite
H/W Current
Switch# Role Mac Address Priority Version State
------------------------------------------------------------
*1 Active abcd.ef22.2222 1 V02 Ready
2 Standby abcd.ef44.4444 1 V02 Ready
3 Member abcd.ef11.1111 1 V02 Ready
4 Member abcd.ef33.3333 1 V02 Ready
The default number for all switches (out of the box) is also 1; as the switches boot they detect stack neighbors and perform an election process which renumbers the switches automatically.
The election process for this example resulted in the switches being numbered as follows;
ASW-TR01-01#show module
Switch Ports Model Serial No. MAC address Hw Ver. Sw Ver.
------ ----- --------- ----------- -------------- ------- --------
1 62 WS-C3850-12X48U-S FOC22222222 abcd.ef22.2222 V02 03.07.04E
2 62 WS-C3850-12X48U-S FOC44444444 abcd.ef44.4444 V02 03.07.04E
3 62 WS-C3850-12X48U-S FOC11111111 abcd.ef11.1111 V02 03.07.04E
4 62 WS-C3850-12X48U-S FOC33333333 abcd.ef33.3333 V02 03.07.04E
- Switches are connected via stack cables and powered up simultaneously.
An interface on only one of the switches needs to be connected to the provisioning network.
- Switches complete the election process and stack initiates smart-install.
- Stack requests config, FreeZTP gives a (merged) config containing the code.
- J2 variable
sw_count
is set during the merge process by counting serial numbers found inidarray
.
- J2 variable
- Stack applies configuration and a syslog message is generated;
sw_stack
applet is triggered. - [EEM Applet
sw_stack
loaded to memory.]- Waits 120 seconds for the stack redundancy operations to complete.
- Executes command
show module | inc ^.[1-9]
(output as read by EEM for current example);This output is what EEM stores as
$stack
for parsing. All line numbers correlate with the stack's current switch allocation numbers; i.e. first line contains information for switch 1, second line contains information for switch 2, etc...1 62 WS-C3850-12X48U-S FOC22222222 abcd.ef22.2222 V02 03.07.04E 2 62 WS-C3850-12X48U-S FOC44444444 abcd.ef44.4444 V02 03.07.04E 3 62 WS-C3850-12X48U-S FOC11111111 abcd.ef11.1111 V02 03.07.04E 4 62 WS-C3850-12X48U-S FOC33333333 abcd.ef33.3333 V02 03.07.04E ASW-TR01-01#
- [J2 Loop] For each switch, searches the entire output for its serial number;
- If not found, a syslog message will be generated and the applet will move onto the next switch.
- [EEM Loop] If found, the applet searches the output line-by-line until it finds the serial number;
The last line of the output is the switch hostname (elevated prompt), which is ignored when searching for switch serial number(s); i.e. the number of lines that the applet will search is limited by the Jinja2
sw_count
variable set in the template.- If the line number where the serial number was found matches the allocated switch number (
idarray_#
), only the priority will be set. - If the line number where the serial number was found does not match the allocated switch number, the priority will be set and the switch will be renumbered.
- If the line number where the serial number was found matches the allocated switch number (
- Syslog messages are generated outlining any errors and all changes made to priorities and numbers.
- Applet deletes itself from running configuration, writes the startup-config, and generates a syslog message stating that the process is complete.
- The stack can now be reloaded to finish the renumbering process.
- Merged configuration that is pushed to the switch from FreeZTP for this example;
- J2 templating functions do not print; i.e. anything enclosed with
{% %}
will not appear in the merged config. - Any line starting with a
!
will be ignored by the switch.
- J2 templating functions do not print; i.e. anything enclosed with
!-- Variables (keys) parsed from CSV keystore.
!---- IDARRAY_1 (switch 1 serial number): FOC11111111
!---- IDARRAY_2 (switch 2 serial number): FOC22222222
!---- IDARRAY_3 (switch 3 serial number): FOC33333333
!---- IDARRAY_4 (switch 4 serial number): FOC44444444
!---- IDARRAY_5 (switch 5 serial number):
!---- IDARRAY_6 (switch 6 serial number):
!---- IDARRAY_7 (switch 7 serial number):
!---- IDARRAY_8 (switch 8 serial number):
!---- IDARRAY_9 (switch 9 serial number):
!---- IDARRAY (all serials): ['FOC11111111', 'FOC22222222', 'FOC33333333', 'FOC44444444']
!
!-- EEM applet to renumber switches accordingly (ALL SUBSEQUENT LINES ARE REQUIRED).
!---- SW_COUNT (count of serials found in IDARRAY): 4
event manager applet sw_stack
event syslog occurs 1 pattern "%SYS-5-CONFIG_I: Configured from tftp" maxrun 75
action 00.00 syslog msg "\n ## FreeZTP configuration received via TFTP, run 'sw_stack' EEM applet in 15s."
action 00.01 wait 15
action 00.02 cli command "enable"
action 00.03 cli command "show mod | i ^.[1-9]"
action 00.04 set stack "$_cli_result"
action 00.05 syslog msg "\n ## Checking all switches' version and stack membership, adjusting where necessary.\n ## Current order;\n$stack"
action 00.06 set error_list ""
action 00.07 set change_list ""
action 00.08 set upgrade_list ""
action 01.00 set sw_num "1"
action 01.01 set pri "16"
action 01.02 decrement pri 1
action 01.03 regexp "FOC11111111" "$stack"
action 01.04 if $_regexp_result ne "1"
action 01.05 syslog msg "\n ## FOC11111111 (Sw-1 serial) not found in the stack, check 'show mod' output."
action 01.06 append error_list "\n ## FOC11111111 is allocated (idarray_1) but was not found in the stack."
action 01.07 else
action 01.08 set i "0"
action 01.09 foreach line "$stack" "\n"
action 01.10 increment i
action 01.11 if $i le "4"
action 01.12 string trim "$line"
action 01.13 set line "$_string_result"
action 01.14 regexp "FOC11111111" "$line"
action 01.15 if $_regexp_result eq "1"
action 01.16 regexp "([0-9\.A-Z]+$)" "$line" curr_ver
action 01.17 cli command "switch $i priority $pri" pattern "continue|#"
action 01.18 cli command "y"
action 01.19 if $i eq $sw_num
action 01.20 append change_list "\n ## FOC11111111 (Priority: $pri // Numbered: $sw_num // Version: $curr_ver)"
action 01.21 else
action 01.22 cli command "switch $i renumber $sw_num" pattern "continue|#"
action 01.23 cli command "y"
action 01.24 append change_list "\n ## FOC11111111 (Priority: $pri // Renumbered: $i > $sw_num* // Version: $curr_ver)"
action 01.25 end
action 01.26 break
action 01.27 end
action 01.28 end
action 01.29 end
action 01.30 end
action 02.00 set sw_num "2"
action 02.01 set pri "16"
action 02.02 decrement pri 2
action 02.03 regexp "FOC22222222" "$stack"
action 02.04 if $_regexp_result ne "1"
action 02.05 syslog msg "\n ## FOC22222222 (Sw-2 serial) not found in the stack, check 'show mod' output."
action 02.06 append error_list "\n ## FOC22222222 is allocated (idarray_2) but was not found in the stack."
action 02.07 else
action 02.08 set i "0"
action 02.09 foreach line "$stack" "\n"
action 02.10 increment i
action 02.11 if $i le "4"
action 02.12 string trim "$line"
action 02.13 set line "$_string_result"
action 02.14 regexp "FOC22222222" "$line"
action 02.15 if $_regexp_result eq "1"
action 02.16 regexp "([0-9\.A-Z]+$)" "$line" curr_ver
action 02.17 cli command "switch $i priority $pri" pattern "continue|#"
action 02.18 cli command "y"
action 02.19 if $i eq $sw_num
action 02.20 append change_list "\n ## FOC22222222 (Priority: $pri // Numbered: $sw_num // Version: $curr_ver)"
action 02.21 else
action 02.22 cli command "switch $i renumber $sw_num" pattern "continue|#"
action 02.23 cli command "y"
action 02.24 append change_list "\n ## FOC22222222 (Priority: $pri // Renumbered: $i > $sw_num* // Version: $curr_ver)"
action 02.25 end
action 02.26 break
action 02.27 end
action 02.28 end
action 02.29 end
action 02.30 end
action 03.00 set sw_num "3"
action 03.01 set pri "16"
action 03.02 decrement pri 3
action 03.03 regexp "FOC33333333" "$stack"
action 03.04 if $_regexp_result ne "1"
action 03.05 syslog msg "\n ## FOC33333333 (Sw-3 serial) not found in the stack, check 'show mod' output."
action 03.06 append error_list "\n ## FOC33333333 is allocated (idarray_3) but was not found in the stack."
action 03.07 else
action 03.08 set i "0"
action 03.09 foreach line "$stack" "\n"
action 03.10 increment i
action 03.11 if $i le "4"
action 03.12 string trim "$line"
action 03.13 set line "$_string_result"
action 03.14 regexp "FOC33333333" "$line"
action 03.15 if $_regexp_result eq "1"
action 03.16 regexp "([0-9\.A-Z]+$)" "$line" curr_ver
action 03.17 cli command "switch $i priority $pri" pattern "continue|#"
action 03.18 cli command "y"
action 03.19 if $i eq $sw_num
action 03.20 append change_list "\n ## FOC33333333 (Priority: $pri // Numbered: $sw_num // Version: $curr_ver)"
action 03.21 else
action 03.22 cli command "switch $i renumber $sw_num" pattern "continue|#"
action 03.23 cli command "y"
action 03.24 append change_list "\n ## FOC33333333 (Priority: $pri // Renumbered: $i > $sw_num* // Version: $curr_ver)"
action 03.25 end
action 03.26 break
action 03.27 end
action 03.28 end
action 03.29 end
action 03.30 end
action 04.00 set sw_num "4"
action 04.01 set pri "16"
action 04.02 decrement pri 4
action 04.03 regexp "FOC44444444" "$stack"
action 04.04 if $_regexp_result ne "1"
action 04.05 syslog msg "\n ## FOC44444444 (Sw-4 serial) not found in the stack, check 'show mod' output."
action 04.06 append error_list "\n ## FOC44444444 is allocated (idarray_4) but was not found in the stack."
action 04.07 else
action 04.08 set i "0"
action 04.09 foreach line "$stack" "\n"
action 04.10 increment i
action 04.11 if $i le "4"
action 04.12 string trim "$line"
action 04.13 set line "$_string_result"
action 04.14 regexp "FOC44444444" "$line"
action 04.15 if $_regexp_result eq "1"
action 04.16 regexp "([0-9\.A-Z]+$)" "$line" curr_ver
action 04.17 cli command "switch $i priority $pri" pattern "continue|#"
action 04.18 cli command "y"
action 04.19 if $i eq $sw_num
action 04.20 append change_list "\n ## FOC44444444 (Priority: $pri // Numbered: $sw_num // Version: $curr_ver)"
action 04.21 else
action 04.22 cli command "switch $i renumber $sw_num" pattern "continue|#"
action 04.23 cli command "y"
action 04.24 append change_list "\n ## FOC44444444 (Priority: $pri // Renumbered: $i > $sw_num* // Version: $curr_ver)"
action 04.25 end
action 04.26 break
action 04.27 end
action 04.28 end
action 04.29 end
action 04.30 end
action 10.00 wait 5
action 10.01 if $error_list ne ""
action 10.02 syslog msg "\n ## The following errors occurred; $error_list"
action 10.03 end
action 10.04 syslog msg "\n ## Switches below have been assigned a priority and renumbered* as needed; $change_list"
action 10.05 cli command "conf t"
action 10.06 cli command "no event man app sw_stack"
action 10.07 cli command "end"
action 10.08 cli command "write mem" pattern "confirm|#"
action 10.09 cli command ""
action 10.10 syslog msg "\n ## EEM applet (sw_stack) deleted and config written, reload for changes to take effect."
!
- This has been confirmed functional on stacked C3850-12X48U-S switches running the following IOS-XE versions;
- IOS-XE 3.7.4E
- IOS-XE 16.3.6
Below is an abbreviated and sanitized log output from 4 stacked switches, real serial numbers have been replaced by those in this example.
Would you like to enter the initial configuration dialog? [yes/no]:
Loading network-confg from 172.17.251.251 (via Vlan1): !
[OK - 94 bytes]
Loading ZTP-23D9F46EC4-confg from 172.17.251.251 (via Vlan1): !
[OK - 154241 bytes]
...
*Oct 17 2018 12:45:34.347 PDT: %SYS-5-CONFIG_I: Configured from tftp://172.17.251.251/ZTP-23D9F46EC4-confg by console
*Oct 17 2018 12:45:34.365 PDT: %HA_EM-6-LOG: sw_stack:
## FreeZTP configuration received via TFTP, run 'sw_stack' EEM applet in 15s.
...
*Oct 17 2018 12:47:34.376 PDT: %HA_EM-6-LOG: sw_stack:
## Checking all switches' version and stack membership, adjusting where necessary.
*Oct 17 2018 12:47:44.755 PDT: %HA_EM-6-LOG: sw_stack:
## Switches below have been assigned a priority and renumbered* as needed;
## FOC11111111 (Priority: 15 // Renumbered: 3 > 1* // Version: 03.07.04E )
## FOC22222222 (Priority: 14 // Renumbered: 1 > 2* // Version: 03.07.04E )
## FOC33333333 (Priority: 13 // Renumbered: 4 > 3* // Version: 03.07.04E )
## FOC44444444 (Priority: 12 // Renumbered: 2 > 4* // Version: 03.07.04E )
*Oct 17 2018 12:47:52.732 PDT: %SYS-5-CONFIG_I: Configured from console by eem_svc on vty0 (EEM:sw_stack)
*Oct 17 2018 12:47:52.756 PDT: %HA_EM-6-LOG: sw_stack:
## EEM applet `sw_stack` deleted and config written, reload for changes to take effect.
...
Author: Paul S. Chapman, Rev: 1, Date: 2020.0704, FreeZTP v1.3.1 and later
For pratical purposes, having fewer baseline templates in a deployment means less maintenance for common settings. These advanced Jinja 2 templating tricks can make this possible. The examples presented below will show a combination of Jinja 2 and Cisco configuration and the expected post-merge output.
Sometimes it may be desirable to use custom variable for Jinja to use during its processing of the template. This example shows how to take a variable from ZTP and place it in another variable used by Jinja.
ZTP Configuration
ztp set keystore myswitch model1 c9300-48uxm
Jinja 2 Example
{%set switch=model1%}
banner login You've connected to a {{switch}}
Post-merge Result
banner login You've connected to a c9300-48uxm
Dictionaries follow a combination of Python and Jinja syntax.
Jinja 2 Example
{%set hostdata={"name":"myswitch1","ip":"192.0.2.14"%}
banner login You've connected to {{hostdata.name}} at {{hostdata.ip}}
Post-merge Result
banner login You've connected to myswitch1 at 192.0.2.14
Substrings follow common python style in the form var[x:x]
.
Jinja 2 Example
!{%set model1="c9300-48uxm"%}
!{%set prefix=model1[:5]%}
banner login You've connected to a {{prefix}}
Post-merge Result
banner login You've connected to a c9300
In many cases, python style formatting can be applied as a method in the form of var.method()
.
Jinja 2 Example
{%set model1="c9300-48uxm"%}
banner login You've connected to a {{model1.upper()}}
Post-merge Result
banner login You've connected to a C9300-48UXM
Lists can be created in the usual python style, var=["item1","item2",...]
. Lists can also be manipulated with standard python methods like var.append("item3")
This example shows how to extend a list based on whether or not a variable exists.
{% set o = listvar.append( var ) if var %}
Where
- '
if var
' is a Jinja 2 style condition. In this case, if the variable contains data, then proceed with theset
portion of the statement - '
listvar.append( var )
' extends the list - '
o =
' sets the operation result code to a discardable variable so that it does not show up in the merged config
ZTP Configuration
ztp set keystore myswitch model1 c9300-48uxm
ztp set keystore myswitch model2 c9300-48u
Jinja 2 Example
!{% set models = [] %}
!{% set o = models.append( model1 ) if model1 %}
!{% set o = models.append( model2 ) if model2 %}
!{% set o = models.append( model3 ) if model3 %}
!{% set o = models.append( model4 ) if model4 %}
Post-merge Result (Contents of variable)
Note that no blank items exist from model3 and model4
models=["c9300-48uxm","c9300-48u"]
Conditional logic can be used to include or exclude portions of the template.
ZTP Configuration
ztp set keystore myswitch model1 c9300
Jinja 2 Example
!{% if model1[:5] == "c9300" %}
system mtu 9198
!{% else %}
system mtu jumbo 9198
!{% endif %}
Post-merge Result (Assuming all other criteria are met for myswitch keystore to be used)
!
system mtu 9198
!
For practical purposes, looping logic is particularly useful to bring variable configurations together (e.g. switch stacks with mixed models, multi model support, etc.)
ZTP Configuration
ztp set keystore myswitch1 model1 c9300-48uxm
ztp set keystore myswitch1 model2 c9300-48u
#
ztp set keystore myswitch2 model1 ws-c3560cx-12pc-s
Jinja 2 Example
!{% set models = [] %}
!{% set o = models.append( model1 ) if model1 %}
!{% set o = models.append( model2 ) if model2 %}
!{% set o = models.append( model3 ) if model3 %}
!
!{% for model in models %}{% if model[:5] == "c9300" %}
switch {{ loop.index }} provision {{ model }}
!{% endif %}{% endfor %}
Post-merge Result - myswitch1
!
!
!
!
!
!
switch 1 provision c9300-48uxm
!
switch 2 provision c9300-48u
Post-merge Result - myswitch2
!
!
!
!
!
!
!
Author: Paul S. Chapman, Rev: 1.2, Date: 2021.0417, FreeZTP v1.4.1
This is an expansion on the switch upgrade EEM presented by Derek Schnosh. The goal is to provide an autmation set that can upgrade a wide list of Cisco Catalyst platforms including 3560CX, 3850, 9300, 94xx, 9500, and Industrial Ethernet platforms. To support these platforms, the automation must be able to account for variations in file system names (flash vs bootflash), installation methods (request platform, install add, archive, and bootvar), and boot cadences.
1000+ switch deployment. Switches would be taken to the field new-in-box and configured by ZTP. Inventory for project contained a mix of 5 different Cisco Catalyst models, each having unique port names (e.g. TeX/0/X, TwX/0/X, GiX/0/X, Gi0/X, etc.), file system names, and upgrade methodologies. Actual serial numbers, stack/standalone arrangement, hostname, and IP would be unknown until time of deployment. Additional non-ZTP automation was used to dynamically aquire data from field techs and update related ZTP Keystores and ID Arrays (not documented here).
The script suite presented contains 4 basic sections: discovery, upgrade, finalize, and cleanup.
- Discovery
- Acquires
- Current OS Version - ZTP must pass new version to config in Jinja merge for comparison and upgrade decision.
- HW model of the switch (or stack master) to determine upgrade method
- File system name (flash or bootflash) for variations between chassis and fixed config switches
- Module count to set necessary pauses in script. (Single supervisor chassis automatically 1.)
- Start upgrade if needed, otherwise start finalize
- Acquires
- Upgrade determines install method, then executes install
- Method 1:
request platform software package upgrade...
- Method 2:
install add file...
- Method 3:
archive download-sw...
- Integrated removal of old OS
- Method 4: Change boot statement in configuration
- Pre-remove old OS before installing new
- Method 1:
- Finalize
- Remove legacy OS if installed via
request
orinstall
commands - Add supplementary configuration (items which may be initially undesireable in main config, like AAA)
- Remove legacy OS if installed via
- Cleanup
- Presented as a cron job that runs at midnight, but could be triggered another way
- In this case, it allowed for other cron-based scripts to be cleaned off the switch a few hours after deployment.
- Presented as a cron job that runs at midnight, but could be triggered another way
- ZTP version 1.4.1 (examples use new global keystore feature)
- Cisco Catalyst 9300 (24U / 24UX / 48U / 48UXM) in stacked and standalone configurations
- Versions: 16.6.6, 16.9.3, 16.9.3s, 16.11.1, 16.12.1s
- Cisco Catalyst 94xx in single- and dual-supervisor configurations
- Version: 16.9.3, 16.12.1s
- Cisco Catalyst 3560CX (8PC / 8PD / 12PC)
- Versions: 15.2(6)E1, 15.2(7)E, 15.2(7)E0s
- Based on testing it is expected that scripts will work on Catalyst 3850, IE-3200, IE-3400, and IE-4010
- Comments in the form
action ### comment <note>
added throughout suite to help provide clarity - All EEM applets configured with
event none
to allow manual execution during testing / troubleshooting- Where needed compound triggers are used to allow
event none
to be present
- Where needed compound triggers are used to allow
- Simplified action tags were adopted to reduce line length
- All tags end in 0 to allow insertion of additional commands (typically in troubleshooting)
- While all efforts have been made to reduce time from initial boot to ready, testing shows time is consistently 18-25 minutes for Catalyst 9K and 25-35 minutes for Catalyst 3560cx
- Variation typically due to slow speed writing to flash, particularly on the 3560
- Both jumbo and standard frame configurations were tested
- Apache was installed on ZTP server to provide HTTP download
- Order of operations change in system_check to fix stack detection issues.
- Migrated embedded Jinja2 substitutions to event manager environment variables (global). This improves readability and simplifies manual configuration when ZTP variables are not available.
- Added Catalyst 9200 to system_upgrade (uses
install
method) - Changed example ZTP configuration to use "c9k" and "c9klite".
c9k_image
covers all Catalyst 9K except ones that use the "lite" image (e.g. Catalyst 9200).
The sample leverages ZTP keystores to provide variables that are consumed in the configuration merge.
- OS File Name
- OS Version
- ZTP server IP (optional) - allow baseline portability between environments (prod / test)
- Environment (Optional) - allow dynamic add/remove of command groups based on prod / test concept
This is a minimal configuration which shows variables provided by ZTP and consumed by Jinja 2 templating. Actual usage could be altered to used External Keystores or other features.
ztp set keystore GLOBAL ztp_env test
ztp set keystore GLOBAL ztp_ip_addr 10.254.64.20
ztp set keystore GLOBAL c9k_ver 16.9.3s
ztp set keystore GLOBAL c9k_image cat9k_iosxe.16.09.03s.SPA.bin
ztp set keystore GLOBAL c9klite_image cat9k_iosxe.16.09.03s.SPA.bin
ztp set keystore GLOBAL c3560cx_ver 15.2(7)e0s
ztp set keystore GLOBAL c3560cx_image c3560cx-universalk9-tar.152-7.E0s.tar
ztp set global-keystore GLOBAL
#
ztp set keystore myswitch1 model1 c9300-48uxm
#
ztp set keystore myswitch2 model1 ws-c3560cx-12pc-s
#
ztp set association myswitch1 template SWITCH_BASELINE
ztp set association myswitch2 template SWITCH_BASELINE
Place these commands anywhere above the EEM scripts.
- Uses Jinja 2 if-else-endif logic
model1[:5]
takes first 5 characters from string passed from ZTP
GLOBAL.c9k_ver
orGLOBAL.c3560cx_ver
-->image.ver
GLOBAL.c9k_image
orGLOBAL.c9klite_image
orGLOBAL.c3560cx_image
-->image.bin
image.ver
andimage.bin
are standard variables used through EEM scripts
!{% if model1[:5] == "c9200" %}
! {%set image={"bin":GLOBAL.c9klite_image,"ver":GLOBAL.c9k_ver}%}
!{% elif model1[:2] == "c9" %}
! {%set image={"bin":GLOBAL.c9k_image,"ver":GLOBAL.c9k_ver}%}
!{% else %}
!---- {%set image={"bin":GLOBAL.c3560cx_image,"ver":GLOBAL.c3560cx_ver}%}
!{% endif %}
event manager environment q "
event manager environment ver {{image.ver}}
event manager environment lver {{image.ver.lower()}}
event manager environment image {{image.bin}}
event manager environment ztp_ip {{GLOBAL.ztp_ip_addr}}
event manager history size events 50
event manager applet system_check authorization bypass
event tag 1 syslog occurs 1 pattern "Configured from tftp://$ztp_ip" maxrun 300
event tag 2 none
trigger
correlate event 1 or event 2
action 0010 wait 30
action 0020 cli command "enable"
action 0030 syslog priority informational msg "## Configuration received via TFTP."
action 0040 comment Init variables
action 0050 set upgrade_required "false"
action 0060 set module_ctr 0
action 0070 set sup_count 0
action 0080 set loop_ctr 0
action 0090 syslog priority informational msg "## Gathering information."
action 0100 comment ##### Get Module Count #####
action 0110 comment Get module for alt script timings. Single sup chassis gets count of 1.
action 0120 comment Stacks & dual-sup modular chassis log RF-5-RF_TERMINAL_STATE when ready.
action 0130 comment Standalone & single sup chassis are ready at SYS-5-RESTART.
action 0140 cli command "show module | include ^\ *[0-9]+\ "
action 0150 comment Look for ^ from command failure (non-stackable switches)
action 0160 string match "*^*" "$_cli_result"
action 0170 if $_string_result eq "1"
action 0180 comment Non-stackable, standalone chassis found. Statically set module count to 1.
action 0190 syslog priority informational msg "## Pausing 30s for post-boot processes."
action 0200 wait 30
action 0210 set module_ctr 1
action 0220 else
action 0230 comment Stack, stackable, or modular chassis found
action 0240 set modules $_cli_result
action 0250 foreach line $modules "\n"
action 0260 string match nocase "*supervisor*" "$line"
action 0270 if $_string_result eq "1"
action 0280 increment sup_count 1
action 0290 end
action 0300 comment Check current line for MAC address (ignore if not present (e.g. switch prompt))
action 0310 regexp "[0-9a-fA-F]+\.[0-9a-fA-F]+\.[0-9a-fA-F]+\ " $line
action 0320 if $_regexp_result eq "1"
action 0330 increment module_ctr 1
action 0340 end
action 0350 end
action 0360 if $sup_count eq 1
action 0370 comment Single sup chassis found. Statically set module count to 1.
action 0380 set module_ctr 1
action 0390 end
action 0400 syslog priority informational msg "## Modules in switch: \n$modules"
action 0410 comment Pause script as needed to allow SSO to complete
action 0420 if $module_ctr gt "1"
action 0430 syslog priority informational msg "## Pausing up to 210s for SSO."
action 0440 while $loop_ctr lt 42
action 0450 cli command "show logging | in RF-5-RF_TERMINAL_STATE"
action 0460 regexp "RF-5-RF_TERMINAL_STATE" $_cli_result
action 0470 if $_regexp_result eq "1"
action 0480 break
action 0490 else
action 0500 increment loop_ctr 1
action 0510 wait 5
action 0520 end
action 0530 end
action 0540 end
action 0550 end
action 0560 comment Store module count for future applets
action 0570 cli command "configure terminal"
action 0580 cli command "event manager environment module_count $module_ctr"
action 0590 cli command "end"
action 0600 syslog priority informational msg "## Module Count: $module_count"
action 0610 comment ##### Disable Autorun of Current Applet #####
action 0620 cli command "configure terminal"
action 0630 cli command "event manager applet system_check authorization bypass"
action 0640 cli command " event tag 1 none maxrun 960"
action 0650 cli command "end"
action 0660 comment ##### Get Model #####
action 0670 comment Get system model from show version (for stacks, gets master)
action 0680 cli command "show version | in \)\ processor"
action 0690 comment Regex matches cisco + model name
action 0700 regexp "[cC]isco\ [a-zA-Z0-9\-]+" "$_cli_result" regexp_match
action 0710 comment Remove cisco from string to isolate model, then put in all lower case
action 0720 string replace $regexp_match 0 5 ""
action 0730 string tolower "$_string_result"
action 0740 set result "$_string_result"
action 0750 comment Store model name for future applets
action 0760 cli command "configure terminal"
action 0770 cli command "event manager environment system_model $result"
action 0780 cli command "end"
action 0790 syslog priority informational msg "## System Model: $system_model"
action 0800 comment ##### Get Version #####
action 0810 comment Get system version from SNMP
action 0820 info type snmp oid 1.3.6.1.2.1.1.1.0 get-type exact
action 0830 comment Regex matches version + version name
action 0840 regexp "[vV]ersion\ [a-zA-Z0-9\.\(\)]+" "$_info_snmp_value" regexp_match
action 0850 comment Remove word version from string to isolate value, then put in lower case
action 0860 string replace $regexp_match 0 7 ""
action 0870 string tolower "$_string_result"
action 0880 set system_version "$_string_result"
action 0890 if $system_version ne "$lver"
action 0900 set upgrade_required "true"
action 0910 end
action 0920 syslog priority informational msg "## System Version: $system_version"
action 0930 syslog priority informational msg "## ZTP Version: $ver"
action 0940 comment ##### Get File System #####
action 0950 cli command "show file systems | in \*"
action 0960 regexp "boot" $_cli_result
action 0970 cli command "configure terminal"
action 0980 comment Set fs env var based on presence or absence of word boot
action 0990 if $_regexp_result eq 1
action 1000 cli command "event manager environment fs bootflash"
action 1010 else
action 1020 cli command "event manager environment fs flash"
action 1030 end
action 1040 cli command "end"
action 1050 syslog priority informational msg "## File System: $fs"
action 1060 comment ##### Prepare for Next Applet #####
action 1070 if $upgrade_required eq "false"
action 1080 cli command "configure terminal"
action 1090 cli command "event manager environment os_upgraded no"
action 1100 cli command "end"
action 1110 syslog priority informational msg "## Job Complete. Starting Finalize applet."
action 1120 cli command "event manager run system_finalize"
action 1130 else
action 1140 syslog priority informational msg "## Switches require an upgrade to version $ver."
action 1150 cli command "configure terminal"
action 1160 cli command "event manager environment os_upgraded yes"
action 1170 cli command "event manager applet system_finalize authorization bypass"
action 1180 if $module_ctr gt 1
action 1190 syslog priority informational msg "## Configuring multi-module trigger for Finalize applet"
action 1200 cli command " event tag 1 syslog occurs 1 pattern $q%RF-5-RF_TERMINAL_STATE$q maxrun 630"
action 1210 else
action 1220 syslog priority informational msg "## Configuring single-module trigger for Finalize applet"
action 1230 cli command " event tag 1 syslog occurs 1 pattern $q%SYS-5-RESTART$q maxrun 630"
action 1240 end
action 1250 cli command "end"
action 1260 cli command "write mem" pattern "confirm|#"
action 1270 cli command ""
action 1280 syslog priority informational msg "## Job Complete. Starting upgrade."
action 1290 cli command "event manager run system_upgrade"
action 1300 end
event manager applet system_upgrade authorization bypass
event none maxrun 1800
action 010 set download_first 0
action 020 set short_model 0
action 030 cli command "enable"
action 040 regexp "(c9200|c9300|c9404r|c9407r|c9410r|c9500|c3850|ie-3200|ie-3400)" $system_model short_model
action 050 comment #### Get Install & Cleanup Method #####
action 060 cli command "configure terminal"
action 070 regexp "(c9300|c3850)" $short_model
action 080 if $_regexp_result eq "1"
action 090 comment IOS-XE Fixed configuration switch upgrade method
action 100 cli command "event manager environment install_method request"
action 110 set download_first 1
action 120 end
action 130 regexp "(c9200|c9404r|c9407r|c9410r|c9500)" $short_model
action 140 if $_regexp_result eq "1"
action 150 comment IOS-XE Chassis / vStackwise install method
action 160 cli command "event manager environment install_method install"
action 170 set download_first 1
action 180 end
action 190 regexp "(ie-3200|ie-3400)" $short_model
action 200 if $_regexp_result eq "1"
action 210 comment IOS Direct replacement of boot variable
action 220 cli command "event manager environment install_method bootvar"
action 230 set download_first 1
action 240 end
action 250 comment Check if install_method env var exists
action 260 handle-error type ignore
action 270 set test $install_method
action 280 if $_error eq FH_EMEMORY
action 290 comment Var absent. All other switches use archive method
action 300 handle-error type exit
action 310 comment IOS Tarball install method. Auto removes old image from switch
action 320 cli command "event manager environment install_method archive"
action 330 end
action 340 cli command "end"
action 350 handle-error type exit
action 360 syslog priority informational msg "## Installation Method: $install_method"
action 370 comment ##### Load OS #####
action 380 if $download_first eq "1"
action 390 syslog priority informational msg "## Downloading image..."
action 400 comment HTTP may offer better download performance for large images.
action 410 comment Server address and image from jinja merge.
action 420 comment Delete previous download, if present, to prevent script hang
action 430 cli command "delete /force $fs:$image"
action 440 cli command "copy http://$ztp_ip/$image $fs:" pattern "Destination|#"
action 450 cli command ""
action 460 end
action 470 comment Save before install to prevent unwanted save prompts
action 480 cli command "write mem" pattern "confirm|#"
action 490 cli command ""
action 500 if $install_method eq "request"
action 510 syslog priority informational msg "## Upgrading..."
action 520 cli command "request platform software package install switch all file $fs:$image force new auto-copy verbose" pattern "proceed|#"
action 530 cli command "y"
action 540 reload
action 550 elseif $install_method eq "install"
action 560 syslog priority informational msg "## Upgrading..."
action 570 comment WARNING - Do not use ISSU method. Allow entire chassis to reboot.
action 580 cli command "install add file $fs:$image activate commit" pattern "proceed|#"
action 590 cli command "y"
action 600 elseif $install_method eq "bootvar"
action 610 syslog priority informational msg "## Upgrading..."
action 620 comment Get booted image from SNMP, then delete to clear space
action 630 info type snmp oid 1.3.6.1.2.1.16.19.6.0 get-type exact
action 640 cli command "delete /force $_info_snmp_value"
action 650 cli command "configure terminal"
action 660 cli command "no boot manual"
action 670 cli command "no boot system"
action 680 cli command "boot system $fs:$image"
action 690 cli command "end"
action 700 cli command "write mem" pattern "confirm|#"
action 710 cli command ""
action 720 reload
action 730 elseif $install_method eq "archive"
action 740 syslog priority informational msg "## Downloading and Upgrading..."
action 750 cli command "archive download-sw /imageonly /overwrite /allow-feature-upgrade http://$ztp_ip/$image"
action 760 reload
action 770 end
event manager applet system_finalize authorization bypass
event tag 1 none maxrun 120
event tag 2 none
trigger
correlate event 1 or event 2
action 010 syslog priority informational msg "## Starting Finalize..."
action 020 cli command "enable"
action 030 syslog priority informational msg "## Applying supplemental configuration."
action 040 cli command "configure terminal"
{% if GLOBAL.ztp_env == "prod" %}
action 050 cli command "errdisable recovery interval 900"
action 060 cli command "no logging console"
action 070 cli command "aaa authentication login default group TACACS-SERVERS local"
action 080 cli command "aaa authorization exec default group TACACS-SERVERS local if-authenticated"
action 090 cli command "aaa authorization commands 15 default group TACACS-SERVERS local if-authenticated"
action 100 cli command "aaa accounting exec default start-stop group TACACS-SERVERS"
action 110 cli command "aaa accounting commands 15 default start-stop group TACACS-SERVERS"
action 120 cli command "line vty 0 15"
action 130 cli command " access-class 30 in"
{% endif %}
action 140 cli command "no snmp-server community secretcommunity RO"
action 150 cli command "interface Vlan1"
action 160 cli command " description UNUSED. SHUTDOWN."
action 170 cli command " no ip address"
action 180 cli command " shutdown"
action 190 cli command "end"
action 200 cli command "write mem" pattern "confirm|#"
action 210 cli command ""
action 220 syslog priority informational msg "## Job complete."
event manager applet system_clean authorization bypass
event tag 1 timer cron name system_clean_cron cron-entry "0 0 * * *" maxrun 630
event tag 2 none
trigger
correlate event 1 or event 2
action 010 syslog priority informational msg "## Starting Cleanup applet."
action 020 cli command "enable"
action 030 if $os_upgraded eq "yes"
action 040 if $module_count gt 1
action 050 comment Stack or dual-sup. Applet triggered at Bulk Sync. Start immediately.
action 060 syslog priority informational msg "## Stack rebooted to new image. SSO Terminal State reached."
action 070 else
action 080 comment Standalone switch. Applet triggered at boot. Pause for boot processes to complete.
action 090 syslog priority informational msg "## Switch rebooted to new image. Waiting 60s."
action 100 wait 60
action 110 end
action 120 syslog priority informational msg "## Removing unused packages..."
action 130 if $install_method eq "request"
action 140 cli command "request platform software package clean switch all" pattern "proceed|#"
action 150 cli command "y"
action 160 elseif $install_method eq "install"
action 170 cli command "install remove inactive" pattern "\[y\/n\]|#"
action 180 cli command "y"
action 190 end
action 200 syslog priority informational msg "## Unused packages have been removed."
action 210 end
action 220 syslog priority informational msg "## Removing EEM applets and environment variables."
action 230 cli command "configure terminal"
action 240 cli command "no event manager environment q"
action 250 cli command "event manager environment ver"
action 260 cli command "event manager environment lver"
action 270 cli command "event manager environment image"
action 280 cli command "event manager environment ztp_ip"
action 290 cli command "no event manager environment system_model"
action 300 cli command "no event manager environment module_count"
action 310 cli command "no event manager environment fs"
action 320 cli command "no event manager environment install_method"
action 330 cli command "no event manager environment os_upgraded"
action 340 cli command "no event manager applet system_check"
action 350 cli command "no event manager applet system_upgrade"
action 360 cli command "no event manager applet system_finalize"
action 370 cli command "no event manager applet system_clean"
action 380 cli command "end"
action 390 cli command "write mem" pattern "confirm|#"
action 400 cli command ""
action 410 cli command "exit"
action 420 syslog priority informational msg "## Cleanup applet complete."
Author: Paul S. Chapman, Rev: 1, Date: 2021.0417, FreeZTP v1.4.1
This is an alternate to the Stack Renumbering presented by Derek Schnosh. The goal was to provide the same result with minimal requirements from the ZTP Jinja2 merge and a finite EEM script length.
ZTP automatically passes an ordered list of serials to Jinja2 as idarray
. Leveraging this, we include or exclude the EEM from the merge based on whether ZTP passes more than 1 serial.
{% if idarray|length > 1 %}
...
{% endif %}
To build our "stack order" list we join the idarray
into a string using space as the delimiter. The ID array is an ordered list (e.g. idarray_1
is always the first position in the idarray
list). We then set it as an EEM environment variable for use by the script.
{% set stack_order=idarray|join(' ') %}
event manager environment stack_order {{ stack_order }}
The proposed EEM pulls the list of live switches using the show module
command and builds an ordered list of serials (e.g. module 1 is item 1 in the list). Since both serial number lists are ordered, then we can assume that a mismatch for a given position in the order means that switch needs to be renumbered. The script implements loop counters as designated_pos
and current_pos
to keep track of the positions being compared.
Below is the complete script. It is bounded by if
/ endif
to dynamically exclude the EEM from standalone switches.
The primary trigger event tag 1 none
is a place holder. Depending on your needs, substitute the event with one appropriate for your deployment.
!{% if idarray|length > 1 %}{% set stack_order=idarray|join(' ') %}
event manager environment stack_order {{ stack_order }}
!
event manager applet stack_reorder authorization bypass
event tag 1 none
event tag 2 none
trigger
correlate event 1 or event 2
action 010 syslog priority informational msg "## Checking stack order against known order from ZTP."
action 020 set designated_pos "0"
action 030 set current_pos "0"
action 040 set renum "0"
action 050 cli command "enable"
action 060 comment Get list of modules
action 070 cli command "show module | include ^\ *[0-9]+\ "
action 080 set modules $_cli_result
action 090 foreach line $modules "\n"
action 100 comment Extract 4th field (serial number) from line as sub1
action 110 regexp "^\ [1-8]\ +[0-9]+\ +[A-Z0-9-]+\ +([A-Z0-9]+)" $line match sub1
action 120 if $_regexp_result eq "1"
action 130 append member " $sub1"
action 140 end
action 150 end
action 160 comment Remove leading space from list
action 170 string trim $member
action 180 comment Loop through ordered serial list from ZTP
action 190 foreach designated_sn $stack_order
action 200 increment designated_pos 1
action 210 comment Find matching serial for online switch
action 220 foreach current_sn $_string_result
action 230 increment current_pos 1
action 240 if $designated_sn eq $current_sn
action 250 comment When serials match, counters should match. Renumber on mismatch.
action 260 if $designated_pos ne $current_pos
action 270 cli command "switch $current_pos renumber $designated_pos" pattern "continue|#"
action 280 cli command "y"
action 290 set renum "1"
action 300 syslog priority informational msg "## Renumbering switch $current_pos to $designated_pos."
action 310 end
action 320 end
action 330 end
action 340 set current_pos "0"
action 350 end
action 360 if $renum eq "1"
action 370 cli command "copy running-config startup-config" pattern "config|#"
action 380 cli command ""
action 390 syslog priority informational msg "## Switch order reset. Rebooting in 10s."
action 400 wait 10
action 410 reload
action 420 end
!{% endif %}