Lately we have been looking at various automation topics like Python where we can do some configuration or grab some output on a few boxes. Today we are going to kick the tires with Cisco’s Embedded Event Manager (EEM), the neat difference with this feature is that it runs directly on the Cisco device so it can run based on events like a command being entered or the router getting a new CDP neighbor.
The Basics
Most of the configuration we’ll be looking at today is under the event manager applet
We can call the script anything we want so of course I’ll use MEOWCAT, once instead the config we have 3 main options that we care about:
- EVENT – This is the “event” that causes the script to run
- ACTION – This is what the actual script that runs
- TRIGGER – This is optional but lets you adjust when the event is triggered.
The number of events varies depending on your platform and version but EEM is pretty much supported across the board.
R01(config-applet)#event ? application Application specific event cli CLI event config Configuration policy event counter Counter event env Environmental event gold GOLD event interface Interface event ioswdsysmon IOS WDSysMon event ipsla IPSLA Event mat MAC address table event neighbor-discovery Neighbor Discovery event none Manually run policy event oir OIR event resource Resource event rf Redundancy Facility event routing Routing event rpc Remote Procedure Call event snmp SNMP event snmp-notification SNMP Notification Event snmp-object SNMP object event syslog Syslog event tag event tag identifier timer Timer event track Tracking object event
Likewise the number of actions depends on your platform and version.
R01(config-applet)#action 10 ? add Add append Append to a variable break Break out of a conditional loop cli Execute a CLI command cns-event Send a CNS event comment add comment context Save or retrieve context information continue Continue to next loop iteration counter Modify a counter value decrement Decrement a variable divide Divide else else conditional elseif elseif conditional end end conditional block exit Exit from applet run file file operations force-switchover Force a software switchover foreach foreach loop gets get line of input from active tty handle-error On error action help Read/Set parser help buffer if if conditional increment Increment a variable info Obtain system specific information mail Send an e-mail multiply Multiply policy Run a pre-registered policy publish-event Publish an application specific event puts print data to active tty regexp regular expression match reload Reload system set Set a variable snmp-object-value Specify value for the SNMP get request snmp-trap Send an SNMP trap string string commands subtract Subtract syslog Log a syslog message track Read/Set a tracking object wait Wait for a specified amount of time while while loop
Actions are run from lowest number to highest number and IOS will automatically sort the actions when you exit, it is a good practice to use consistent numbering since going from 1 digit actions to 2 digits might mix things in a way you don’t want.
R01(config-applet)# action 1 syslog msg "TEST03" R01(config-applet)#action 08 syslog msg TEST02" R01(config-applet)#action 11 syslog msg "TEST04" R01(config-applet)#exit R01(config)# R01(config)#do sh run | s TEST event manager applet TEST event none action 08 syslog msg "TEST02"" action 1 syslog msg "TEST03" action 10 syslog msg "TEST01" action 11 syslog msg "TEST04" R01(config)#
CLI Example
Let’s look at a simple example that can stop some headaches in your network. We have all heard of a horror story where a junior accidentally causes an outage by typing switchport trunk allowed vlan 100
instead of switchport trunk allowed vlan add 100
We will stop that by preventing the evil command from running and then have the switch remind the junior!
First we name the script
SW01(config-if)#event manager applet ANTI-NOOB
Then we look for a CLI event that looks for switchport trunk allowed vlan
followed by any number, the sync yes tells the router to run the script instead of the command. We can also have both the script and command run etc.
SW01(config-applet)# event cli pattern "switchport trunk allowed vlan [0-9*]" sync yes
Next we just have the switch display our educational message then we exit, just like with vlans we need to exit for the config to become live.
SW01(config-applet)# action 100 puts "NO! BAD JUNIOR! NO! Use the Add keyword" SW01(config-applet)#exit
Now if we try to run the evil command we get our message instead and we can see the config didn’t apply.
SW01(config)#int g1/0 SW01(config-if)#switchport trunk allowed vlan none SW01(config-if)#switchport trunk allowed vlan 100 NO! BAD JUNIOR! NO! Use the Add keyword SW01(config-if)#do sh run int g1/0 | in allowed switchport trunk allowed vlan none
But we can still use the add keyword like normal.
SW01(config-if)#switchport trunk allowed vlan add 100 SW01(config-if)#do sh run int g1/0 | in allowed switchport trunk allowed vlan 100
Routing Example
Lets do another simple example where EEM makes and advertises a new Loopback if it detects a certain OSPF route in the routing table.
First we make an event that that matches the OSPF route we want to look for.
R01(config)# event manager applet ROUTE R01(config-applet)# event routing network 192.168.254.33/32 type add protocol OSPF
Next we have our actions add the loopback and advertise it
Note: EEM’s process starts as unprivileged so we need to enter enable
mode though we don’t need to specify the actual password. We also need to go to config mode if we are changing things.
R01(config-applet)# action 10 cli command "enable" R01(config-applet)# action 11 cli command "conf t" R01(config-applet)# action 12 cli command "interface l1" R01(config-applet)# action 13 cli command "ip add 1.1.1.1 255.255.255.255" R01(config-applet)# action 14 cli command "ip ospf 1 area 100"
Lastly we will add a syslog message so we know the script ran.
R01(config-applet)# action 20 syslog msg "Added New Loopback!" R01(config-applet)#exit
Lets test this out by making a new loopback with the 192.168.254.33/32 address and add it to OSPF
R03(config)#int l33 R03(config-if)#ip add 192.168.254.33 255.255.255.255 R03(config-if)#ip ospf 1 area 300
On R01 we see that our loopback is created.
Sep 2 22:00:26.131: %LINEPROTO-5-UPDOWN: Line protocol on Interface Loopback1, changed state to up R01(config)# Sep 2 22:00:26.519: %HA_EM-6-LOG: ROUTE: Added New Loopback! R01(config)#do sh run int l1 Building configuration... Current configuration : 83 bytes ! interface Loopback1 ip address 1.1.1.1 255.255.255.255 ip ospf 1 area 100 end
On R03 we see the 1.1.1.1 route! R03(config-if)#do sh ip route ospf | in IA D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area O IA 1.1.1.1 [110/3] via 10.2.3.2, 00:00:30, GigabitEthernet2.23
Destructive Change Example
One simple but effective use of EEM is scripting out destructive changes, say you have to change a IP and default gateway on a router through SSH – You can’t simply paste in the commands because you’ll lose connectivity when you change the IP or remove the gateway. With EEM we can have the router run all the commands even though it loses connectivity.
Lets see how R02 is configured now.
R02#show ip int br | ex unass Interface IP-Address OK? Method Status Protocol GigabitEthernet2.12 10.1.2.22 YES manual up up GigabitEthernet2.23 10.2.3.2 YES manual up up Loopback0 192.168.254.2 YES manual up up R02#show run | in ip route ip route 0.0.0.0 0.0.0.0 10.1.2.3
Then we’ll SSH into the router from R01 to keep things authentic
R01#ssh -l cisco 10.1.2.22 Password: R02>en Password: R02#conf t Enter configuration commands, one per line. End with CNTL/Z.
We will use event none
for this which means we will have to manually call the script when we are ready.
R02(config)#event manager applet CHANGEIP R02(config-applet)#event none
Then we just add all the commands we want to run, one things to note is that there is no ? or validation for the IOS commands in this section so make sure you don’t do a typo.
R02(config-applet)#action 10 cli command "enable" R02(config-applet)#action 11 cli command "conf t" R02(config-applet)#action 12 cli command "interface g2.12" R02(config-applet)#action 13 cli command "ip add 10.1.2.2 255.255.255.0" R02(config-applet)#action 14 cli command "no ip route 0.0.0.0 0.0.0.0 10.1.2.3" R02(config-applet)#action 15 cli command "ip route 0.0.0.0 0.0.0.0 10.1.2.1" R02(config-applet)#exit R02(config)#end
Then we run the script from privileged mode.
R02#event manager run CHANGEIP
We can see the changes worked!!!
R02#show ip int br | ex unass Interface IP-Address OK? Method Status Protocol GigabitEthernet2.12 10.1.2.2 YES manual up up GigabitEthernet2.23 10.2.3.2 YES manual up up Loopback0 192.168.254.2 YES manual up up R02# .Sep 2 22:20:58.183: %OSPF-5-ADJCHG: Process 1, Nbr 192.168.254.1 on GigabitEthernet2.12 from LOADING to FULL, Loading Done R02#show run | in ^ip route ip route 0.0.0.0 0.0.0.0 10.1.2.1
CDP Example
We’ll close this out by looking a more complex script that Cisco put out on their site, this will use set the interface description by using CDP info. We’ll also do some manipulations to make the output a bit nicer.
First we will make an event that runs when a new CDP neighbor is discovered.
SW01(config)# event manager applet auto-update-port-description authorization bypass SW01(config-applet)# description "Auto-update port-description based on CDP neighbors info" SW01(config-applet)# event neighbor-discovery interface regexp .*GigabitEthernet.* cdp add
Next we strip the domain-name from the CDP output
SW01(config-applet)# action 0.0 comment "Event line regexp: Deside which interface to auto-update description on" SW01(config-applet)# action 1.0 comment "Trim domain name" SW01(config-applet)# action 1.1 string trimright "$_nd_cdp_entry_name" ".testlab.com" SW01(config-applet)# action 1.2 set _host "$_string_result" SW01(config-applet)# action 1.3 set _host "$_string_result"
Then we do some manipulations to shorten the port name from GigabitEthernet to Gi etc.
SW01(config-applet)# action 2.0 comment "Convert long interface name to short" SW01(config-applet)# action 2.1 string first "Ethernet" "$_nd_port_id" SW01(config-applet)# action 2.2 if $_string_result eq "7" SW01(config-applet)# action 2.21 string replace "$_nd_port_id" 0 14 "Gi" SW01(config-applet)# action 2.3 elseif $_string_result eq 10 SW01(config-applet)# action 2.31 string replace "$_nd_port_id" 0 17 "Te" SW01(config-applet)# action 2.4 elseif $_string_result eq 4 SW01(config-applet)# action 2.41 string replace "$_nd_port_id" 0 11 "Fa" SW01(config-applet)# action 2.5 end SW01(config-applet)# action 2.6 set _int "$_string_result"
Lastly we set the description based on the CDP information
SW01(config-applet)# action 3.0 comment "Actual config of port description" SW01(config-applet)# action 3.1 cli command "enable" SW01(config-applet)# action 3.2 cli command "config t" SW01(config-applet)# action 3.3 cli command "interface $_nd_local_intf_name" SW01(config-applet)# action 3.4 cli command "description Connected to $_host Port $_int" SW01(config-applet)# action 3.5 cli command "do write" SW01(config-applet)# action 4.0 syslog msg "EEM script updated description on $_nd_local_intf_name and saved config"
Let’s test this out!!!!
First we’ll have a look at what neighbors we have on SW01
SW01#show cdp ne Capability Codes: R - Router, T - Trans Bridge, B - Source Route Bridge S - Switch, H - Host, I - IGMP, r - Repeater, P - Phone, D - Remote, C - CVTA, M - Two-port Mac Relay Device ID Local Intrfce Holdtme Capability Platform Port ID SW02.testlab.com Gig 3/0 134 R S I Gig 3/0 SW02.testlab.com Gig 3/1 164 R S I Gig 3/1 SW03.testlab.com Gig 3/3 145 R S I Gig 3/3 SW03.testlab.com Gig 3/2 165 R S I Gig 3/2 R01.testlab.com Gig 0/1 170 R I CSR1000V Gig 2 Total cdp entries displayed : 5
Since the script works when CDP discovers a new neighbor we need to clear the CDP table. Then we’ll see the syslog messages as CDP discovers each neighbor.
SW01#clear cdp table *Sep 2 21:59:55.954: %GRUB-5-CONFIG_WRITING: GRUB configuration is being updated on disk. Please wait... *Sep 2 21:59:56.880: %GRUB-5-CONFIG_WRITTEN: GRUB configuration was written to disk successfully. *Sep 2 21:59:56.923: %HA_EM-6-LOG: auto-update-port-description: EEM script updated description on GigabitEthernet3/1 and saved config *Sep 2 22:00:01.275: %GRUB-5-CONFIG_WRITING: GRUB configuration is being updated on disk. Please wait... *Sep 2 22:00:02.083: %GRUB-5-CONFIG_WRITTEN: GRUB configuration was written to disk successfully. *Sep 2 22:00:02.155: %HA_EM-6-LOG: auto-update-port-description: EEM script updated description on GigabitEthernet3/2 and saved config *Sep 2 22:00:14.658: %GRUB-5-CONFIG_WRITING: GRUB configuration is being updated on disk. Please wait... *Sep 2 22:00:15.440: %GRUB-5-CONFIG_WRITTEN: GRUB configuration was written to disk successfully. *Sep 2 22:00:15.481: %HA_EM-6-LOG: auto-update-port-description: EEM script updated description on GigabitEthernet3/0 and saved config *Sep 2 22:00:20.310: %GRUB-5-CONFIG_WRITING: GRUB configuration is being updated on disk. Please wait... *Sep 2 22:00:21.110: %GRUB-5-CONFIG_WRITTEN: GRUB configuration was written to disk successfully. *Sep 2 22:00:21.185: %HA_EM-6-LOG: auto-update-port-description: EEM script updated description on GigabitEthernet0/1 and saved config *Sep 2 22:00:28.503: %GRUB-5-CONFIG_WRITING: GRUB configuration is being updated on disk. Please wait... *Sep 2 22:00:29.358: %GRUB-5-CONFIG_WRITTEN: GRUB configuration was written to disk successfully. *Sep 2 22:00:29.428: %HA_EM-6-LOG: auto-update-port-description: EEM script updated description on GigabitEthernet3/3 and saved config
Now if we look at the interface descriptions we can see “Connected to Port
SW01#show interface description | in Gi Gi0/0 up up OOB management Gi0/1 up up Connected to R01 Port Gi2 Gi0/2 up up to R02 Gi0/3 up up to R03 Gi1/0 up up to S01 Gi1/1 up up Gi1/2 up up Gi1/3 up up Gi2/0 up up to RM01 Gi2/1 up up Gi2/2 up up Gi2/3 up up Gi3/0 up up Connected to SW02 Port Gi3/0 Gi3/1 up up Connected to SW02 Port Gi3/1 Gi3/2 up up Connected to SW03 Port Gi3/2 Gi3/3 up up Connected to SW03 Port Gi3/3
Wrapping up
That is all for today, hope y’all found it interesting.