def main(): """ - identify any PnP unclaimed APs - map to local database to identify the floor to be provisioned to - claim the device - verify PnP process workflow - re-sync the WLC controller - verify the AP on-boarded using the Cisco DNA Center Inventory - reachability, IP address, access switch info, WLC info - create, update a ServiceNow incident with the information - close ServiceNow incident if PnP completes successfully """ # run the application on demand, scanning for a new device in the Cisco DNA Center PnP Provisioning tab print('\n\nApplication "dnac_pnp_ap.py" started') # device info and site pnp_device_assign = AP_ASSIGN_SITE site_name = pnp_device_assign['site_name'] floor_name = pnp_device_assign['floor_name'] pnp_device_name = pnp_device_assign['device_hostname'] print('\nThis application will assign the device \n', pnp_device_name, ' to the site: ', pnp_device_assign['site_name'] + ' / ' + floor_name) # logging, debug level, to file {application_run.log} logging.basicConfig( filename='application_run.log', level=logging.DEBUG, format= '%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH) # check if any devices in 'Unclaimed' and 'Initialized' state, if not wait for 10 seconds and run again pnp_unclaimed_device_count = 0 while pnp_unclaimed_device_count == 0: try: pnp_unclaimed_device_count = dnac_apis.pnp_get_device_count( 'Unclaimed', dnac_token) if pnp_unclaimed_device_count != 0: # get the pnp device info pnp_devices_info = dnac_apis.pnp_get_device_list(dnac_token) device_state = pnp_devices_info[0]['deviceInfo']['state'] device_onb_state = pnp_devices_info[0]['deviceInfo'][ 'onbState'] # verify if device is ready to be claimed: state = Unclaimed "and" onboard_state = Initialized if device_state == 'Unclaimed' and device_onb_state == 'Initialized': break else: pnp_unclaimed_device_count = 0 except: pass time.sleep(10) print('\nFound Unclaimed PnP devices count: ', pnp_unclaimed_device_count) pnp_device_id = pnp_devices_info[0]['id'] comment = '\nUnclaimed PnP device info:' comment += '\nPnP Device Hostname: ' + pnp_device_name comment += '\nPnP Device Id: ' + pnp_device_id print(comment) # create service now incident incident_number = service_now_apis.create_incident( 'AP PnP API Provisioning: ' + pnp_device_name, comment, SNOW_DEV, 3) print('Created new ServiceNow Incident: ', incident_number) # get the floor id to assign device to using pnp floor_id = dnac_apis.get_floor_id(site_name, 'Floor 3', dnac_token) print('Floor Id: ', floor_id) print('\nAP PnP Provisioning Started (this may take few minutes)') # start the claim process of the device to site claim_result = dnac_apis.pnp_claim_ap_site(pnp_device_id, floor_id, 'TYPICAL', dnac_token) comment = '\nClaim Result: ' + claim_result # update ServiceNow incident print(comment) service_now_apis.update_incident(incident_number, comment, SNOW_DEV) # check claim status every 5 seconds, build a progress status list, end when state == provisioned, exit status_list = [] claim_status = dnac_apis.pnp_get_device_info(pnp_device_id, dnac_token)['state'] status_list.append(claim_status) while claim_status != 'Provisioned': claim_status = dnac_apis.pnp_get_device_info(pnp_device_id, dnac_token)['state'] if claim_status not in status_list: status_list.append(claim_status) time.sleep(5) comment = '' for status in status_list: comment += '\nPnP Device State: ' + status # update service now incident print(comment) service_now_apis.update_incident(incident_number, comment, SNOW_DEV) # sync the PnP WLC, wait 60 seconds to complete dnac_apis.sync_device(PnP_WLC_NAME, dnac_token) print('\nDNA Center Device Re-sync started: ', PnP_WLC_NAME) # wait 60 seconds and check for inventory for AP info time.sleep(60) # collect AP info ap_device_id = dnac_apis.get_device_id_name(pnp_device_name, dnac_token) ap_device_info = dnac_apis.get_device_info(ap_device_id, dnac_token) ap_reachability = ap_device_info['reachabilityStatus'] ap_controller_ip = ap_device_info['associatedWlcIp'] ap_ip_address = ap_device_info['managementIpAddress'] ap_device_location = dnac_apis.get_device_location(pnp_device_name, dnac_token) ap_access_switch_info = dnac_apis.get_physical_topology( ap_ip_address, dnac_token) ap_access_switch_hostname = ap_access_switch_info[0] ap_access_switch_port = ap_access_switch_info[1] # collect WLC info wlc_info = dnac_apis.get_device_info_ip(ap_controller_ip, dnac_token) wlc_hostname = wlc_info['hostname'] comment = '\nProvisioned Access Point Info:\n - Reachability: ' + ap_reachability comment += '\n - IP Address: ' + ap_ip_address comment += '\n - Connected to: ' + ap_access_switch_hostname + ' , Interface: ' + ap_access_switch_port comment += '\n - Location: ' + ap_device_location comment += '\n - WLC Controller: ' + wlc_hostname + ' , IP Address: ' + ap_controller_ip print(comment) service_now_apis.update_incident(incident_number, comment, SNOW_DEV) print('\n\nAP PnP provisoning completed') service_now_apis.close_incident(incident_number, SNOW_DEV) print( '\nPnP provisioning completed successfully, ServiceNow incident closed' ) print('\n\nEnd of Application "dnac_pnp_ap.py" Run')
def main(): """ This script will monitor device configuration changes. It could be executed on demand, periodically (every 60 minutes, for example) or continuously. It will collect the configuration file for each DNA Center managed device, compare with the existing cached file, and detect if any changes. When changes detected, identify the last user that configured the device, and create a new ServiceNow incident. Automatically roll back all non-compliant configurations, or save new configurations if approved in ServiceNow. Device configuration files managemnt using RESTCONF and NETCONF Compliance checks at this time: - no Access Control Lists changes - no logging changes - no duplicated IPv4 addresses """ # run the demo continuously, looping print('Application config_mon.py started') # create a local directory for all the configuration files # check if 'Config_Files' folder exists and create one if it does not if not os.path.exists('Config_Files'): os.makedirs('Config_Files') os.chdir('Config_Files') # logging, debug level, to file {application_run.log} logging.basicConfig( filename='application_run.log', level=logging.DEBUG, format= '%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') while True: # get the DNA C auth token dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH) print('\nDNA C AUTH TOKEN: ', dnac_token, '\n') temp_run_config = 'temp_run_config.txt' # get the DNA C managed devices list (excluded wireless, for one location) all_devices_info = dnac_apis.get_all_device_info(dnac_token) all_devices_hostnames = [] for device in all_devices_info: if device['family'] == 'Switches and Hubs' or device[ 'family'] == 'Routers': if IOS_XE_HOSTNAME in device['hostname']: all_devices_hostnames.append(device['hostname']) # get the config files, compare with existing (if one existing). Save new config if file not existing. for device in all_devices_hostnames: device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) filename = str(device) + '_run_config.txt' # save the running config to a temp file f_temp = open(temp_run_config, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() # check if device has an existing configuration file (to account for newly discovered DNA C devices) # if yes, run the diff function # if not, save the device configuration to the local device database # this will create the local "database" of configs, one file/device if os.path.isfile(filename): diff = compare_configs(filename, temp_run_config) if diff != '': # retrieve the device location using DNA C REST APIs location = dnac_apis.get_device_location( device, dnac_token) # find the users that made configuration changes with open(temp_run_config, 'r') as f: user_info = 'User info no available' for line in f: if 'Last configuration change' in line: user_info = line # define the incident description and comment short_description = 'Configuration Change Alert - ' + device comment = 'The device with the name: ' + device + '\nhas detected a Configuration Change' print(comment) # create ServiceNow incident using ServiceNow APIs incident = service_now_apis.create_incident( short_description, comment, SNOW_DEV, 3) # get the device health from DNA Center current_time_epoch = utils.get_epoch_current_time() device_details = dnac_apis.get_device_health( device, current_time_epoch, dnac_token) device_sn = device_details['serialNumber'] private_mngmnt_ip_address = device_details[ 'managementIpAddr'] device_mngmnt_ip_address = NAT[private_mngmnt_ip_address] device_family = device_details['platformId'] device_os_info = device_details[ 'osType'] + ', ' + device_details['softwareVersion'] device_health = device_details['overallHealth'] updated_comment = '\nDevice location: ' + location updated_comment += '\nDevice family: ' + device_family updated_comment += '\nDevice OS info: ' + device_os_info updated_comment += '\nDevice S/N: ' + device_sn updated_comment += '\nDevice Health: ' + str( device_health) + '/10' updated_comment += '\nDevice management IP address: ' + device_mngmnt_ip_address print(updated_comment) # update ServiceNow incident service_now_apis.update_incident(incident, updated_comment, SNOW_DEV) updated_comment = '\nThe configuration changes are\n' + diff + '\n\n' + user_info print(updated_comment) # update ServiceNow incident service_now_apis.update_incident(incident, updated_comment, SNOW_DEV) # start the compliance validation # ACL changes validation_result = 'Pass' validation_comment = '' if ('+access-list' in diff) or ('-access-list' in diff): updated_comment = '\nValidation against ACL changes failed' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) validation_result = 'Failed' else: validation_comment = '\nPassed ACL Policy' # logging changes if ('+logging' in diff) or ('-logging' in diff): updated_comment = '\nValidation against logging changes failed' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) validation_result = 'Failed' else: validation_comment += '\nPassed Logging Policy' # IPv4 duplicates diff_list = diff.split('\n') diff_config = '!\n' for command in diff_list: if 'ip address' in command: diff_config += command.replace('+', '') + '\n!' # save the diff config that include only IP addresses in a file f_diff = open('temp_config_file.txt', 'w') f_diff.write(diff_config) f_diff.seek(0) # reset the file pointer to 0 f_diff.close() duplicate_ip_result = dnac_apis.check_ipv4_duplicate( 'temp_config_file.txt') if duplicate_ip_result: updated_comment = '\nValidation against duplicated IPv4 addresses failed' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) validation_result = 'Failed' else: validation_comment += '\nPassed Duplicate IPv4 Prevention' print(updated_comment) # procedure to restore configurations as policy validations failed if validation_result == 'Failed': updated_comment = '\nConfiguration changes do not pass validation,\nConfiguration roll back initiated' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) print(updated_comment) # start the config roll back baseline_config = 'flash:/config_mon_baseline' netconf_restconf.restconf_rollback_to_saved_config( baseline_config, device_mngmnt_ip_address, IOS_XE_USER, IOS_XE_PASS) # check if rollback is successful after 5 seconds time.sleep(5) device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) filename = str(device) + '_run_config.txt' # save the running config to a temp file f_temp = open(temp_run_config, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() diff = compare_configs(filename, temp_run_config) if diff != ' ': updated_comment = '\nConfiguration rolled back successfully' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) # close ServiceNow incident service_now_apis.close_incident(incident, SNOW_DEV) else: updated_comment = '\nConfiguration rolled back not successful' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) # start procedure to ask for approval as validation passed else: service_now_apis.update_incident( incident, 'Approve these changes (YES/NO)?\n' + validation_comment, SNOW_DEV) service_now_apis.update_incident( incident, 'Waiting for Management Approval', SNOW_DEV) print( '\nConfiguration changes pass compliance checks, approval waiting' ) # start the approval YES/NO procedure # start a loop to check for 2 min if approved of not approval = 'NO' timer_count = 0 while timer_count <= 5: if service_now_apis.find_comment(incident, 'YES'): # start the save of running config to new baseline on device # flash:/config_mon_baseline netconf_restconf.netconf_save_running_config_to_file( 'flash:/config_mon_baseline', device_mngmnt_ip_address, IOS_XE_PORT, IOS_XE_USER, IOS_XE_PASS) print( 'New baseline configuration saved on network device: ' + device) # establish new baseline config on server time.sleep(5) device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) filename = str(device) + '_run_config.txt' # save the running config to the device config file f_temp = open(filename, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() approval = 'YES' # update ServiceNow incident updated_comment = '\nApproval received, saved device configuration, established new baseline configuration' print(updated_comment) service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) service_now_apis.close_incident( incident, SNOW_DEV) break elif service_now_apis.find_comment(incident, 'NO'): break else: timer_count += 1 time.sleep(10) if timer_count == 5: service_now_apis.update_incident( incident, 'Approval Timeout', SNOW_DEV) # check if Approval is 'NO' at the end of the timer if approval == 'NO': # start the config roll back baseline_config = 'flash:/config_mon_baseline' netconf_restconf.restconf_rollback_to_saved_config( baseline_config, device_mngmnt_ip_address, IOS_XE_USER, IOS_XE_PASS) # check if rollback is successful after 3 seconds time.sleep(5) device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) # save the running config to a temp file f_temp = open(temp_run_config, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() filename = str(device) + '_run_config.txt' diff = compare_configs(filename, temp_run_config) if diff != ' ': updated_comment = '\nConfiguration changes not approved,\nConfiguration rolled back successfully' print(updated_comment) service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) service_now_apis.close_incident( incident, SNOW_DEV) else: updated_comment = '\nConfiguration changes not approved,\nConfiguration rolled back not successful' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) else: print('Device: ' + device + ' - No configuration changes detected') else: # new device discovered, save the running configuration to a file in the folder with the name # {Config_Files} f_config = open(filename, 'w') f_config.write(device_run_config) f_config.seek(0) f_config.close() # retrieve the device management IP address private_mngmnt_ip_address = dnac_apis.get_device_management_ip( device, dnac_token) device_mngmnt_ip_address = NAT[private_mngmnt_ip_address] # Save the running configuration as a baseline configuration local on each device # flash:/config_mon_baseline netconf_restconf.netconf_save_running_config_to_file( 'flash:/config_mon_baseline', device_mngmnt_ip_address, IOS_XE_PORT, IOS_XE_USER, IOS_XE_PASS) print('Device: ' + device + ' - New device discovered') print('Wait for 10 seconds and start again') time.sleep(10) print('\n\nEnd of Application Run')
InsecureRequestWarning) # Disable insecure https warnings # the syslog entry that triggered the event syslog_info = "%ETHPORT-5-IF_ADMIN_UP: Interface Ethernet1/1 is up" # retrieve the device hostname device_name = cli("show run | in hostname") # define the incident description and comment update_comment = "The Nexus switch with the " + device_name + "\n has recovered from the Uplink failure" update_comment += "\n\nSyslog: " + syslog_info # find the ServiceNow incident file = open("uplink_ticket.txt", "r") incident = file.read() file.close() # update the ServiceNow incident service_now_apis.update_incident(incident, update_comment, SNOW_DEV) # close ServiceNow incident time.sleep(1) service_now_apis.close_incident(incident, SNOW_DEV) # delete the incident name from the file in /bootflash/PCW incident_file = open("/bootflash/NX-OS-ITSM/uplink_ticket.txt", "w") incident_file.write("INCIDENT") incident_file.close() print("End Application Run Uplink Interface Restored")