From 396873b314b2fc5c0da88274d909566243216fba Mon Sep 17 00:00:00 2001 From: Eliot Pickhardt Date: Wed, 3 Aug 2022 13:53:28 -0500 Subject: [PATCH] Eliot's Files --- config/check_acl.py | 32 ++++++++ config/check_bgp.py | 29 +++++++ config/check_ospf.py | 62 +++++++++++++++ exec/test_ospf_neighbors.py | 63 +++++++++++++++ process/test_alarm_process.py | 141 ++++++++++++++++++++++++++++++++++ 5 files changed, 327 insertions(+) create mode 100644 config/check_acl.py create mode 100644 config/check_bgp.py create mode 100644 config/check_ospf.py create mode 100644 exec/test_ospf_neighbors.py create mode 100644 process/test_alarm_process.py diff --git a/config/check_acl.py b/config/check_acl.py new file mode 100644 index 0000000..1c7c65b --- /dev/null +++ b/config/check_acl.py @@ -0,0 +1,32 @@ +""" +Config script to ensure an ACL of the given name exists on the provided interface when ACL related configuration is committed +""" + +import cisco.config_validation as xr + +from cisco.script_mgmt import xrlog +syslog = xrlog.getSysLogger('check_acl') + +#These values should be changed as necessary +interface_name = "TenGigE0/0/0/10" +acl_name = "access-list-1" + +def check_acl(root): + #Get the interface with the given name + int_config = root.get_node("/ifmgr-cfg:interface-configurations/interface-configuration[active='act',interface-name='%s']" %interface_name) + if int_config: + syslog.info("Interface found") + + #Retrieve the list of ACLs under the interface + acl = int_config.get_list("/ip-pfilter-cfg:ipv4-packet-filter/inbound/acl-name-array") + if acl: + syslog.info("ACL list found") + + #Search for the ACL in the list + if acl_name in [x.value for x in acl]: + syslog.info("ACL found") + else: + syslog.error("ACL not found") + +#Run script when ACL related config is pushed +xr.register_validate_callback(["/ifmgr-cfg:interface-configurations/ifmgr-cfg:interface-configuration/ip-pfilter-cfg:ipv4-packet-filter/*"], check_acl) \ No newline at end of file diff --git a/config/check_bgp.py b/config/check_bgp.py new file mode 100644 index 0000000..f95c536 --- /dev/null +++ b/config/check_bgp.py @@ -0,0 +1,29 @@ +""" +Config script to set a BGP router id when any BGP configuration is committed. +""" +import cisco.config_validation as xr +from cisco.script_mgmt import xrlog + +syslog = xrlog.getSysLogger('check_bgp') + +#These should be edited to match desired values +instance_name = "default" +instance_as = 0 +four_byte_as = 100 +router_id = "10.1.1.1" + +def check_bgp(root): + + #Gets the autonomous system related to the BGP instance + aut_sys = root.get_node("/ipv4-bgp-cfg:bgp/instance[instance-name='%s']/instance-as[as=%i]/four-byte-as[as=%i]/default-vrf" %(instance_name, instance_as, four_byte_as)) + if aut_sys: + syslog.info("AS found") + + #Set the global BGP router id + aut_sys.set_node("/global/router-id", router_id) + syslog.info("New router id: %s" %aut_sys.get_node("/global/router-id").value) + else: + syslog.info("AS not found") + +#Run this script when any BGP related commit is pushed +xr.register_validate_callback(["/ipv4-bgp-cfg:bgp/instance/instance-as/*"], check_bgp) diff --git a/config/check_ospf.py b/config/check_ospf.py new file mode 100644 index 0000000..bb2f140 --- /dev/null +++ b/config/check_ospf.py @@ -0,0 +1,62 @@ +""" +Config script to check if certain ospf characteristics match desired values + +""" + + +import cisco.config_validation as xr + +from cisco.script_mgmt import xrlog + +syslog = xrlog.getSysLogger('check_ospf') + +#These values should be adjusted as seen fit +area_to_check = 0 +process_name = "100" +interface_name = "TenGigE0/0/0/2" +hello_interval_req = 30 +cost_minimum = 5 + +def check_ospf(root): + + #The ospf area to check + area = root.get_node("/ipv4-ospf-cfg:ospf/processes/process[process-name='%s']/default-vrf/area-addresses/area-area-id[area-id=%i]" %(process_name, area_to_check)) + + #If the area exists + if area: + syslog.info("Area %s found" %area_to_check) + + #The given interface to check + interface = area.get_node("/name-scopes/name-scope[interface-name='%s']" %interface_name) + if interface: + syslog.info("Interface %s found" %interface_name) + + #Get the leaf node representing the cost of the path + curr_cost = interface.get_node("/cost") + + #If the cost has been previously set + if curr_cost: + syslog.info("Current cost is %s" %curr_cost.value) + + #Check to see if the cost is less than the minimum + if curr_cost.value < cost_minimum: + xr.add_error(curr_cost, "Cost cannot be lower than %s" %cost_minimum) #Throw error + + #Get the leaf node representing the hello interval + curr_hello_interval = interface.get_node("/hello-interval") + if curr_hello_interval: + syslog.info("Current hello interval is %s seconds" %curr_hello_interval.value) + + #Check the value of the hello interval + if curr_hello_interval.value != hello_interval_req: + curr_hello_interval.set_node(None, hello_interval_req) #set the hello interval + syslog.info("Hello interval set to %s seconds" %hello_interval_req) + + #If the hello interval isn't previosly set + else: + syslog.info("No hello interval set previously, now set to %s seconds" %hello_interval_req) + interface.set_node("/hello-interval", hello_interval_req) #set the hello interval + + +#Only run the script if an ospf process is changed +xr.register_validate_callback(["/ipv4-ospf-cfg:ospf/processes/*"], check_ospf) diff --git a/exec/test_ospf_neighbors.py b/exec/test_ospf_neighbors.py new file mode 100644 index 0000000..25660e5 --- /dev/null +++ b/exec/test_ospf_neighbors.py @@ -0,0 +1,63 @@ +""" +How to run: +Use proper aaa settings: +aaa authorization exec default group tacacs+ local +aaa authorization eventmanager default local +aaa authentication login default group tacacs+ local + +script add exec /harddisk: test_ospf_neighbors.py +conf + +script exec test_ospf_neighbors.py checksum sha256sum +commit +end + +script run test_ospf_neighbors.py arguments '--process' '--area' + +Example: +script run test_ospf_neighbors.py arguments '10.1.1.4' 'HundredGigE0/0/0/32' +or +script run test_ospf_neighbors.py arguments '10.1.1.4' 'HundredGigE0/0/0/32' '--process' 100 '--area' 0 + +How to verify: +show logging last 10 +check for 'SCRIPT : Configuration succeeded' +""" +import argparse +from iosxr.xrcli.xrcli_helper import * +from cisco.script_mgmt import xrlog + +syslog = xrlog.getSysLogger('OSPF neighbor configuration') +helper = XrcliHelper(debug = True) + +def ospf_neighbors(): + parser = argparse.ArgumentParser() + + #optional and positional arguments + parser.add_argument("routerid", help = "ip address of router", type = str) + parser.add_argument("interface", help = "interface for OSPF configuration", type = str) + parser.add_argument("--process", help = "process for OSPF configuration", type = int, default = 100) + parser.add_argument("--area", help = "area for OSPF configuration", type = int, default = 0) + args = parser.parse_args() + router_id = args.routerid + interface_name = args.interface + process_id = args.process + area_id = args.area + + #This is identical to issuing the following commands in the configuration terminal + #(config) router ospf + #(config-ospf) router-id + #(config-ospf) area interface + #(config-ospf-ar-if) network point-to-point + #(config-ospf-ar-if) commit + + result = helper.xr_apply_config_string("router ospf %s \n\r router-id %s \n\r area %s interface %s \n\r network point-to-point" %(process_id, router_id, area_id, interface_name)) + + #print status messages to syslogx + if result['status'] == 'success': + syslog.info('SCRIPT : Configuration succeeded') + else: + syslog.error('SCRIPT : Configuration failed') + +if __name__ == '__main__': + ospf_neighbors() \ No newline at end of file diff --git a/process/test_alarm_process.py b/process/test_alarm_process.py new file mode 100644 index 0000000..9d0f15e --- /dev/null +++ b/process/test_alarm_process.py @@ -0,0 +1,141 @@ +""" +Process script to monitor the number of alarms present on the router + +Email notification will be sent when this number changes using Cisco SMTP server + +Must be connected to Cisco network for emailing capabilities to function properly + +To trigger script +Step 1: Add and configure script as shown in README.MD + +Step 2: Register the application with Appmgr + +Configuraton: +appmgr process-script my-process-app +executable test_process.py +run-args + +Step 3: Activate the registered application +appmgr process-script activate name my-process-app + +""" + + + +import time +import os +import xmltodict +import re + + +#For emailing functions +import smtplib as SMTP +from email.mime.text import MIMEText + +#For logging +from cisco.script_mgmt import xrlog +from iosxr.netconf.netconf_lib import NetconfClient + +log = xrlog.getScriptLogger('Alarm') +syslog = xrlog.getSysLogger('Alarm') + +def check_curr_alarm_num(prev_count): + """ + Checks current number of alarms + """ + curr_count = 0 + filter_string = """ + + + + + + + + + """ + + nc = NetconfClient(debug=True) + nc.connect() #Connects the NetconfClient + do_get(nc, filter=filter_string) #Makes a Netconf get call + ret_dict = _xml_to_dict(nc.reply, 'alarms') #Parses the data from the Netconf Reply into a dictionary format for easy retrieval + curr_count = int(ret_dict['alarms']['detail']['detail-system']['stats']['reported']) #Retrieves number of alamrs from the created dictionary + count_file = open("count.txt", "wt") + count_file.write("Current number of alarms: %s \n" %curr_count) #Writes number of alarms to file + count_file.close() + if curr_count != prev_count: #Checks number of alarms compared to previous number + syslog.error("New Alarm Detected: new count = %s, old count = %s" %(curr_count, prev_count)) #If the number is different, print a syslog error + _send_email(curr_count, prev_count) #Send an email notifying network monitor + nc.close() #Close the Netconf client + + +def _send_email(curr_count, prev_count): + SMTP_server = 'outbound.cisco.com' + sender = 'epickhar@cisco.com' + dest = 'epickhar@cisco.com' + + text_subtype = 'plain' + + + #Email contents + content = """\ + NEW ALARM DETECTED: NEW COUNT = %s, OLD COUNT = %s + """ %(curr_count, prev_count) + + subject = "Alarm Change Detected" + msg = MIMEText(content, text_subtype) + msg['Subject'] = subject + msg['From'] = sender + + #Connect to SMTP server with port number + conn = SMTP.SMTP(SMTP_server, 25) + conn.set_debuglevel(False) + + #Send email + try: + conn.sendmail(sender, dest, msg.as_string()) + except: + syslog.error("Message Failed to Send") + finally: + conn.quit() + +def _xml_to_dict(xml_output, xml_tag=None): + """ + convert netconf rpc request to dict + :param xml_output: + :return: + """ + if xml_tag: + pattern = "\s+(<%s.*).*" % (xml_tag, xml_tag) + else: + pattern = "(.*)" + xml_output = xml_output.replace('\n', ' ') + xml_data_match = re.search(pattern, xml_output) + ret_dict = xmltodict.parse(xml_data_match.group(1)) + return ret_dict + +def do_get(nc, filter=None, path=None): + """ + makes netconf rpc get request + :param nc: Netconf client + :return bool if request successful: + """ + try: + if path is not None: + nc.rpc.get(file=path) + elif filter is not None: + nc.rpc.get(request=filter) + else: + return False + except Exception as e: + return False + return True + +if __name__ == '__main__': + prev_count = 0 + while(1): #Process script, run continuously + check_curr_alarm_num(prev_count) + count_file = open("count.txt", "rt") #Get the previous count from the file + prev_count = int(count_file.readline()[26:]) #Parse data + count_file.close() #Close file + time.sleep(60) #Run every minute