Ejemplo n.º 1
0
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')
Ejemplo n.º 2
0
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')
Ejemplo n.º 3
0
def main():
    """
    This script will monitor device configuration changes. It could be executed on demand as in this lab, or periodically,
    every 60 minutes (for example). It will collect the configuration file for each device, compare with the existing cached file,
    detect if any changes.
    When changes detected, identify the last user that configured the device, and create a new ServiceNoe incident.
    """

    # 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)
    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':
            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

        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

                # get the device management IP address
                device_mngmnt_ip_address = dnac_apis.get_device_management_ip(
                    device, dnac_token)

                # define the incident description and comment
                short_description = "Configuration Change Alert"
                comment = "The device with the name: " + device + "\nhas detected a Configuration Change"
                comment += "\n\nThe device location is: " + location
                comment += "\n\nThe device management IP address is: " + device_mngmnt_ip_address
                comment += "\n\nThe configuration changes are\n" + diff + "\n\n" + user_info

                print(comment)

                # create ServiceNow incident using ServiceNow APIs
                incident = service_now_apis.create_incident(
                    short_description, comment, SNOW_DEV, 3)
            else:
                print('Device: ' + device +
                      ' - No configuration changes detected')

        else:
            f_config = open(filename, "w")
            f_config.write(device_run_config)
            f_config.seek(0)
            f_config.close
Ejemplo n.º 4
0
# retrieve the ios xe device management ip address, Gi0/0, not the VPG IP address
IOS_XE_HOST_IP = execute('sh run int gi1 | in ip address').split(' ')[3]

# retrieve the device hostname using RESTCONF
DEVICE_HOSTNAME = netconf_restconf.get_restconf_hostname(
    IOS_XE_HOST_IP, IOS_XE_USER, IOS_XE_PASS)
print(str('\nThe device hostname: ' + DEVICE_HOSTNAME))

# create a new Service Now incident
description = 'Monitored route: ' + monitored_route + ' Lost, device hostname: ' + DEVICE_HOSTNAME

comment = 'The device with the name: ' + DEVICE_HOSTNAME + ' has detected the loss of a critical route'
comment += '\n\nThe route: ' + monitored_route + ' - is missing from the routing table'

snow_incident = service_now_apis.create_incident(description, comment,
                                                 SNOW_DEV, 1)
"""

# The following commands to be use when Cisco DNA Center is available

# get DNA C AUth JWT token
dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH)

# get device location
DEVICE_LOCATION = dnac_apis.get_device_location(DEVICE_HOSTNAME, dnac_token)
print(str("\nDevice Location: " + DEVICE_LOCATION))

# get device details
epoch_time = utils.get_epoch_current_time()
device_details = dnac_apis.get_device_health(DEVICE_HOSTNAME, epoch_time, dnac_token)
device_sn = device_details['serialNumber']
Ejemplo n.º 5
0
        device_sn = device_info_list[-1]

        # retrieve the device location using DNA C REST APIs
        # dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH)
        # location = dnac_apis.get_device_location(device_name, dnac_token)
        location = "Lake Oswego/OR/USA"

        # define the incident description and comment
        short_description = "Unauthorized Configuration Change - IOS XE Automation"
        comment = "The device with the name: " + device_name + "\n has detected an Unauthorized Configuration Change"
        comment += "\n\nThe device SN is: " + device_sn
        comment += "\nThe device location is: " + location
        comment += "\n\nThe configuration changes are\n" + diff + "\n\nConfiguration changed by user: "******"Configuration rollback to baseline successful - IOS XE Automation"
        service_now_apis.update_incident(incident, update_comment, SNOW_DEV)

        # find existing, or create new Webex Teams room

        webex_team_id = webex_teams_apis.get_team_id(WEBEX_TEAMS_SPACE)
        if webex_team_id is None:
            webex_teams_apis.create_space(WEBEX_TEAMS_SPACE)

        webex_teams_apis.post_space_markdown_message(WEBEX_TEAMS_SPACE,
Ejemplo n.º 6
0
def main():
    """
    Vendor will join Webex Teams Room with the name {ROOM_NAME}
    It will ask for access to an IP-enabled device - named {IPD}
    The code will map this IP-enabled device to the IP address {10.93.140.35}
    Access will be provisioned to allow connectivity from DMZ VDI to IPD
    """

    # save the initial stdout
    initial_sys = sys.stdout

    # the user will be asked if interested to run in demo mode or in
    # production (logging to files - erna_log.log, erna_err.log))

    # user_input = utils.get_input_timeout('If running in Demo Mode please enter y ', 10)

    user_input = 'y'  # remove this line if you want to run in production
    if user_input != 'y':

        # open a log file 'erna.log'
        file_log = open('erna_log.log', 'w')

        # open an error log file 'erna_err.log'
        err_log = open('erna_err.log', 'w')

        # redirect the stdout to file_log and err_log
        sys.stdout = file_log
        sys.stderr = err_log

        # configure basic logging to send to stdout, level DEBUG, include timestamps
        logging.basicConfig(level=logging.DEBUG,
                            stream=sys.stdout,
                            format='%(asctime)s - %(levelname)s - %(message)s')

    # logging, debug level, to file {erna_run.log}
    logging.basicConfig(
        filename='erna_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')

    # the local date and time when the code will start execution

    date_time = str(datetime.datetime.now().replace(microsecond=0))
    print('\nThe Application "ERNA.py" started running at this time ' +
          date_time)

    user_input = utils.get_input_timeout('Enter y to skip next section : ', 10)

    user_input = 'y'  # remove this line if you want to follow the approval process

    if user_input != 'y':
        # verify if Webex Teams Space exists, if not create Webex Teams Space, and add membership (optional)

        webex_teams_room_id = webex_teams_apis.get_space_id(ROOM_NAME)
        if webex_teams_room_id is None:
            webex_teams_room_id = webex_teams_apis.create_space(ROOM_NAME)
            print('- ', ROOM_NAME, ' -  Webex Teams room created')

            # invite membership to the room
            webex_teams_apis.add_space_membership(ROOM_NAME, APPROVER_EMAIL)

            webex_teams_apis.post_space_message(
                ROOM_NAME, 'To require access enter :  IPD')
            webex_teams_apis.post_space_message(ROOM_NAME, 'Ready for input!')
            print('Instructions posted in the room')
        else:
            print('- ', ROOM_NAME, ' -  Existing Webex Teams room found')

            webex_teams_apis.post_space_message(
                ROOM_NAME, 'To require access enter :  IPD')
            webex_teams_apis.post_space_message(ROOM_NAME, 'Ready for input!')
        print('- ', ROOM_NAME, ' -  Webex Teams room id: ',
              webex_teams_room_id)

        # check for messages to identify the last message posted and the user's email who posted the message
        # check for the length of time required for access

        last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0]

        while last_message == 'Ready for input!':
            time.sleep(5)
            last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0]
            if last_message == 'IPD':
                last_person_email = (
                    webex_teams_apis.last_user_message(ROOM_NAME))[1]
                webex_teams_apis.post_space_message(
                    ROOM_NAME,
                    'How long time do you need access for? (in minutes)  : ')
                time.sleep(10)
                if (
                        webex_teams_apis.last_user_message(ROOM_NAME)
                )[0] == 'How long time do you need access for? (in minutes)  : ':
                    timer = 30 * 60
                else:
                    timer = int(
                        webex_teams_apis.last_user_message(ROOM_NAME)[0]) * 60
            elif last_message != 'Ready for input!':
                webex_teams_apis.post_space_message(ROOM_NAME,
                                                    'I do not understand you')
                webex_teams_apis.post_space_message(
                    ROOM_NAME, 'To require access enter :  IPD')
                webex_teams_apis.post_space_message(ROOM_NAME,
                                                    'Ready for input!')
                last_message = 'Ready for input!'

        print('\nThe user with this email: ', last_person_email,
              ' asked access to IPD for ', (timer / 60), ' minutes')

    # get the WJT Auth token to access DNA
    dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH)
    print('\nThe DNA Center auth token is: ', dnac_token)

    # IPD IP address - DNS lookup if available

    IPD_IP = '10.93.140.35'
    last_person_email = '*****@*****.**'
    approver_email = '*****@*****.**'
    timer = 3600

    # locate IPD in the environment using DNA C
    ipd_location_info = dnac_apis.locate_client_ip(IPD_IP, dnac_token)

    remote_device_hostname = ipd_location_info[
        0]  # the network device the IPD is connected to
    vlan_number = ipd_location_info[2]  # the access VLAN number
    interface_name = ipd_location_info[
        1]  # the interface number is connected to

    device_location = dnac_apis.get_device_location(
        remote_device_hostname, dnac_token)  # network device location
    location_list_info = device_location.split('/')
    remote_device_location = location_list_info[-1]  # select the building name

    log_ipd_info = '\n\nThe IPD is connected to this device: ' + remote_device_hostname
    log_ipd_info += '\n\nThis interface: ' + interface_name + ', access VLAN: ' + vlan_number
    log_ipd_info += '\n\nLocated       : ' + remote_device_location
    print(log_ipd_info)

    # request approval

    if user_input != 'y':
        webex_teams_apis.post_space_message(
            ROOM_NAME,
            ('The user with this email ' + last_person_email +
             ' asked access to IPD for ' + str(timer / 60) + ' minutes'))
        webex_teams_apis.post_space_message(
            ROOM_NAME,
            'The IPD is connected to the switch ' + remote_device_hostname +
            ' at our location ' + remote_device_location)
        webex_teams_apis.post_space_message(ROOM_NAME, 'To approve enter: Y/N')

        # check for messages to identify the last message posted and the user's email who posted the message.
        # looking for user - Director email address, and message = 'Y'

        last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0]

        while last_message == 'To approve enter: Y/N':
            time.sleep(5)
            last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0]
            approver_email = (webex_teams_apis.last_user_message(ROOM_NAME))[1]
            if last_message == 'y' or 'Y':
                if approver_email == APPROVER_EMAIL:
                    print('Access Approved')
                else:
                    last_message = 'To approve enter: Y/N'

        print('\nApproval process completed')

    # get UCSD API key
    # ucsd_key = ucsd_apis.get_ucsd_api_key()

    # execute UCSD workflow to connect VDI to VLAN, power on VDI
    # ucsd_apis.execute_ucsd_workflow(ucsd_key, UCSD_CONNECT_FLOW)

    log_ucsd_info = '\n\nUCSD connect flow executed'
    print(log_ucsd_info)

    # deployment of cli configuration files to the dc router

    dc_device_hostname = 'PDX-RO'
    template_project = 'ERNA'
    print('\nThe DC device name is: ', dc_device_hostname)

    dc_config_file = 'DC_Config.txt'
    dc_config_templ = dc_config_file.split('.')[
        0]  # select the template name from the template file

    cli_file = open(dc_config_file, 'r')  # open file with the template
    cli_config = cli_file.read()  # read the file

    # validation of dc router cli template
    dc_valid = dnac_apis.check_ipv4_duplicate(dc_config_file)
    log_dc_info = ''
    if dc_valid is False:
        print('\n\nDC Router CLI Template validated')
        log_dc_info = '\n\nDC Router CLI Templates validated'

    dnac_apis.upload_template(dc_config_templ, template_project, cli_config,
                              dnac_token)  # upload the template to DNA C
    depl_id_dc = dnac_apis.deploy_template(dc_config_templ, template_project,
                                           dc_device_hostname,
                                           dnac_token)  # deploy dc template

    log_dc_info += '\n\nDeployment of the configurations to the DC router, ' + dc_device_hostname
    log_dc_info += ' started, deployment id: ' + depl_id_dc
    log_dc_config = '\nDC Router Config \n\n' + cli_config
    print(log_dc_info)

    time.sleep(1)

    # deployment of cli configuration files to the remote router

    remote_config_file = 'Remote_Config.txt'
    remote_config_templ = remote_config_file.split('.')[
        0]  # select the template name from the template file

    cli_file = open(remote_config_file, 'r')  # open file with the template
    cli_config = cli_file.read()  # read the file

    # update the template with the localized info for the IPD
    # replace the $VlanId with the localized VLAN access
    # replace the $IPD with the IPD ip address

    cli_config = cli_config.replace('$IPD', IPD_IP)
    cli_config = cli_config.replace('$VlanId', vlan_number)

    remote_updated_config_file = 'Remote_Config_Updated.txt'
    updated_cli_file = open(remote_updated_config_file, 'w')
    updated_cli_file.write(cli_config)
    updated_cli_file.close()

    # validation of remote router cli template
    remote_valid = dnac_apis.check_ipv4_duplicate(remote_updated_config_file)
    log_remote_info = ''
    if remote_valid is False:
        log_remote_info = '\n\nRemote Device CLI Template validated'
        print(log_remote_info)

    dnac_apis.upload_template(remote_config_templ, template_project,
                              cli_config,
                              dnac_token)  # upload the template to DNA C
    depl_id_remote = dnac_apis.deploy_template(
        remote_config_templ, template_project, remote_device_hostname,
        dnac_token)  # deploy remote template
    time.sleep(1)

    log_remote_info += '\n\nDeployment of the configurations to the Remote device, ' + remote_device_hostname
    log_remote_info += ' started, deployment id: ' + depl_id_remote
    log_remote_config = '\nRemote Device Config \n\n' + cli_config
    print(log_remote_info)

    time.sleep(1)

    # check the deployment status after waiting for all jobs to complete - 10 seconds
    print('\nWait for DNA Center to complete template deployments')
    time.sleep(10)

    dc_status = dnac_apis.check_template_deployment_status(
        depl_id_dc, dnac_token)
    remote_status = dnac_apis.check_template_deployment_status(
        depl_id_remote, dnac_token)

    log_templ_depl_info = '\n\nTemplates deployment status DC: ' + dc_status + ', Remote: ' + remote_status
    print(log_templ_depl_info)

    if dc_status == 'SUCCESS' and remote_status == 'SUCCESS':
        print('\nAll templates deployment have been successful\n')
        templ_deploy_status = True

    # synchronization of devices configured - DC and Remote Router
    dc_sync_status = dnac_apis.sync_device(dc_device_hostname, dnac_token)[0]
    remote_sync_status = dnac_apis.sync_device(remote_device_hostname,
                                               dnac_token)[0]

    if dc_sync_status == 202:
        print('\nDNA Center started the DC Router resync')
    if remote_sync_status == 202:
        print('\nDNA Center started the Remote Router resync')

    dc_router_tunnel = netconf_restconf.get_restconf_int_oper_status(
        'Tunnel201')
    remote_router_tunnel = netconf_restconf.get_netconf_int_oper_status(
        'Tunnel201')

    log_tunnel_info = '\n\nThe Tunnel 201 interfaces operational state: '
    log_tunnel_info += '\n\nFrom ' + remote_device_hostname + ' using NETCONF - ' + dc_router_tunnel
    log_tunnel_info += '\n\nFrom ' + dc_device_hostname + ' using RESTCONF - ' + remote_router_tunnel

    print(log_tunnel_info)

    print('\nWait for DNA Center to complete the resync of the two devices')

    time.sleep(180)

    print('\nSync completed, Path Trace started')
    # start a path trace to check the path segmentation
    path_trace_id = dnac_apis.create_path_trace('172.16.202.1', IPD_IP,
                                                dnac_token)

    print('\nWait for Path Trace to complete')
    time.sleep(30)

    path_trace_info = dnac_apis.get_path_trace_info(path_trace_id, dnac_token)

    log_path_trace = '\n\nPath Trace status: ' + path_trace_info[0]
    log_path_trace += '\n\nPath Trace details: ' + str(path_trace_info[1])
    print(log_path_trace)

    # create ASAv outside interface ACL to allow traffic

    outside_acl_id = asav_apis.get_asav_access_list(OUTSIDE_INT)
    asav_status = asav_apis.create_asav_access_list(outside_acl_id,
                                                    OUTSIDE_INT, VDI_IP,
                                                    IPD_IP)
    if asav_status == 201:
        log_asav_info = '\n\nASAv access list updated to allow traffic from ' + VDI_IP + ' to ' + IPD_IP + ' on the interface ' + OUTSIDE_INT
    else:
        log_asav_info = '\n\nError updating the ASAv access list on the interface ' + OUTSIDE_INT
    print(log_asav_info)

    # Webex Teams notification

    webex_teams_apis.post_space_message(
        ROOM_NAME,
        'Requested access to this device: IPD, located in our office: ' +
        remote_device_location + ' by user ' + last_person_email +
        ' has been granted for ' + str(int(timer / 60)) + ' minutes')
    log_access_info = '\n\nRequested access to this device: IPD, located in our office: '
    log_access_info += remote_device_location + ' by user: '******' has been granted for ' + str(
        int(timer / 60)) + ' minutes'

    # create and update ServiceNow incident

    snow_user = '******'
    snow_description = 'ERNA Automation - Vendor Remote Access to IPD: ' + IPD_IP

    snow_incident = service_now_apis.create_incident(snow_description,
                                                     log_ipd_info, snow_user,
                                                     '2')
    comments = log_ucsd_info + log_dc_info + log_dc_config + log_remote_info + log_remote_config + log_templ_depl_info
    comments += log_tunnel_info + log_path_trace + log_asav_info + log_access_info
    service_now_apis.update_incident(snow_incident, comments, snow_user)

    date_time = str(datetime.datetime.now().replace(microsecond=0))
    print('\n\nEnd of application "ERNA.py" provisioning run at this time ',
          date_time)

    # time.sleep(timer)  # un-comment this line if time limit is required
    input('Press any key to continue de-provisioning  ')

    #
    #  restore configurations to initial state
    #

    #  restore DC router config

    dc_rem_file = 'DC_Remove.txt'
    dc_rem_templ = dc_rem_file.split('.')[0]

    cli_file = open(dc_rem_file, 'r')
    cli_config = cli_file.read()

    dnac_apis.upload_template(dc_rem_templ, template_project, cli_config,
                              dnac_token)
    depl_id_dc_rem = dnac_apis.deploy_template(dc_rem_templ, template_project,
                                               dc_device_hostname, dnac_token)

    print('\nDC Router restored to the baseline configuration')
    log_remove_info = '\n\nDC Router restored to the baseline configuration'

    time.sleep(1)

    #  restore Remote router config

    remote_rem_file = 'Remote_Remove.txt'
    remote_rem_templ = remote_rem_file.split('.')[0]

    cli_file = open(remote_rem_file, 'r')
    cli_config = cli_file.read()

    # update the template with the local info for the IPD
    # replace the $VlanId with the local VLAN access
    # replace the $IPD with the IPD ip address

    cli_config = cli_config.replace('$IPD', IPD_IP)
    cli_config = cli_config.replace('$VlanId', vlan_number)

    remote_updated_remove_file = 'Remote_Remove_Updated.txt'
    updated_cli_file = open(remote_updated_remove_file, 'w')
    updated_cli_file.write(cli_config)
    updated_cli_file.close()

    dnac_apis.upload_template(remote_rem_templ, template_project, cli_config,
                              dnac_token)
    depl_id_remote_rem = dnac_apis.deploy_template(remote_rem_templ,
                                                   template_project,
                                                   remote_device_hostname,
                                                   dnac_token)

    print('\nRemote Router restored to the baseline configuration')
    log_remove_info += '\n\nRemote Device restored to the baseline configuration'

    time.sleep(1)

    # remove the ASAv outside interface ACLE that allowed traffic between VDI and IPD

    outside_acl_id = asav_apis.get_asav_access_list(OUTSIDE_INT)
    asav_status = asav_apis.delete_asav_access_list(outside_acl_id,
                                                    OUTSIDE_INT)
    if asav_status == 204:
        log_asav_remove_info = '\n\nASAv access list on the interface ' + OUTSIDE_INT + ' restored to the baseline configuration'
    else:
        log_asav_remove_info = 'Error while restoring the ASAv access list on the interface ' + OUTSIDE_INT
    print(log_asav_remove_info)

    # execute UCSD workflow to disconnect VDI to VLAN, power on VDI
    # ucsd_apis.execute_ucsd_workflow(ucsd_key, UCSD_DISCONNECT_FLOW)

    log_ucsd_remove_info = '\n\nUCSD disconnect flow executed'
    print(log_ucsd_remove_info)

    # sync the remote and DC device
    dc_sync_status = dnac_apis.sync_device(dc_device_hostname, dnac_token)[0]
    remote_sync_status = dnac_apis.sync_device(remote_device_hostname,
                                               dnac_token)[0]

    log_sync_info = ''
    if dc_sync_status == 202:
        log_sync_info = '\n\nDNA Center started the DC Router resync'
    if remote_sync_status == 202:
        log_sync_info += '\n\nDNA Center started the Remote Router resync'

    print(log_sync_info)

    # Webex Teams notification

    webex_teams_apis.post_space_message(
        ROOM_NAME, 'Access to this device: IPD has been terminated')
    webex_teams_apis.post_space_message(
        ROOM_NAME, '----------------------------------------------')

    # update the database with script execution

    access_log_file = open('access_logs.csv', 'a')
    data_to_append = str('\n\n' + date_time) + ','
    access_log_file.write(data_to_append)
    data_to_append = last_person_email + ',' + log_ipd_info + ',' + approver_email
    data_to_append += ',' + log_dc_info + ',' + log_remote_info + ',' + log_templ_depl_info + ','
    data_to_append += log_path_trace + ',' + log_asav_info + ',\n' + snow_incident
    data_to_append_nolines = data_to_append.replace('\n\n', '\n')
    access_log_file.write(data_to_append_nolines)
    access_log_file.close()

    print('\nRecords database updated, file saved')

    comments = log_remove_info + log_asav_remove_info + log_ucsd_remove_info + log_sync_info
    service_now_apis.update_incident(snow_incident, comments, snow_user)

    # restore the stdout to initial value
    sys.stdout = initial_sys

    # the local date and time when the code will end execution

    date_time = str(datetime.datetime.now().replace(microsecond=0))
    print('\n\nEnd of application "ERNA.py" run at this time ', date_time)