├── LICENSE ├── CHANGELOG.md ├── .gitignore ├── README.md └── ztp.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright [2021] [Arun Kumar Sakthivel] 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [V1.0] 4 | 5 | ### Added 6 | 7 | - Added persistent logging feature to save ZTP running-logs 8 | - Added a toggle switch to enable/disable persistent logging by flipping the flag ‘log_tofile = False’ in the script 9 | - Starting IOS XE 17.2.x and above the persistent log files will be saved under '/flash/guest-share/ztp.log' , In older version logs will be located at '/flash/ztp.log' 10 | - Skipped MD5 checksum on software versions where it is unsupported due to a known limitation(see Troubleshooting / Known Issues below) 11 | - Minor stability improvements 12 | 13 | 14 | ### Changed 15 | 16 | - None 17 | 18 | ### Fixed 19 | 20 | - None 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/macos,windows,linux 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,linux 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### Windows ### 49 | # Windows thumbnail cache files 50 | Thumbs.db 51 | Thumbs.db:encryptable 52 | ehthumbs.db 53 | ehthumbs_vista.db 54 | 55 | # Dump file 56 | *.stackdump 57 | 58 | # Folder config file 59 | [Dd]esktop.ini 60 | 61 | # Recycle Bin used on file shares 62 | $RECYCLE.BIN/ 63 | 64 | # Windows Installer files 65 | *.cab 66 | *.msi 67 | *.msix 68 | *.msm 69 | *.msp 70 | 71 | # Windows shortcuts 72 | *.lnk 73 | 74 | # sphinx build folder 75 | build 76 | 77 | # Sphinx 78 | .doctrees 79 | 80 | # Visual Studio Code 81 | 82 | # End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zero Touch Provisioning (ZTP) for IOS XE based devices 2 | 3 | # Introduction 4 | When a network device like a switch or a router comes on-line, a fair amount of manual configuration has to happen before it is fully functional. 5 | At minimum, it needs to be updated to the proper software image and a golden configuration. Day zero automation techniques automate these processes, bringing up network devices into a functional state with minimal to no-touch. Hence the name Zero touch. 6 | The goal of Zero touch is to enable you to plug in a new network device and have it configured and transitioned into production automatically without the need for manual configuration. 7 | 8 | 9 | ## Getting Started 10 | 11 | Cisco IOS XE supports three Day Zero technologies: Network Plug-N-Play, Zero Touch Provisioning (ZTP) and Preboot eXectuion Environment (PXE). To keep us on track with our goal we will only talk about ZTP here. 12 | 13 | On IOS XE ZTP is supported in following ways, 14 | 15 | 1. AutoInstall 16 | 17 | All Cisco IOS-XE (and Cisco IOS) based devices try to automatically download a configuration file during the first boot. This is called as Autoinstall. 18 | When a IOS device boots, the AutoInstall process tries to download the configuration via TFTP. If a custom TFTP server is used, the configuration provided to the device can be built with information gathered from DHCP Server. 19 | 20 | 2. Python based 21 | 22 | When a device that supports python Zero-Touch Provisioning boots up, and does not find the startup configuration (during fresh install on Day Zero), the device enters the Zero-Touch Provisioning mode. The device locates a Dynamic Host Control Protocol (DHCP) server, bootstraps itself with its interface IP address, gateway, and Domain Name System (DNS) server IP address, and enables Guest Shell. The device then obtains the IP address or URL of a TFTP/HTTP server, and downloads the Python script to configure the device. Guest Shell provides the environment for the Python script to run. Guest Shell executes the downloaded Python script and configures the device for Day Zero. After Day Zero provisioning is complete, Guest Shell remains enabled. 23 | 24 | 3. Hybrid(Autoinstall+Python) 25 | 26 | On devices that support python ZTP and AutoInstall we can enable hybrid mode. Python based ZTP will always takes priority and will trigger a python script as in option 2 above and if that fails it will fall back to AutoInstall for generic configuration. This generic configuration file must have a hardcode name as cisconet.cfg and placed only in tftp server to be picked up by the device. 27 | 28 | DHCP Server Configuration for Python + AutoInstall 29 | 30 | DHCP Option 67: http://x.x.x.x/ztp.py 31 | 32 | DHCP Option 150: tftp-server-ip 33 | 34 | When ZTP runs, option 67 will be used to download and execute ztp.py. When AutoInstall runs, option 150 will be used to download and apply “cisconet.cfg” from the TFTP server 35 | 36 | 37 | 38 | ## Prerequisites 39 | 40 | |Platform | ZTP Minimum Release | XE Minimum Release 41 | |------------------ | :-------------------------: |-------------------------: 42 | |Catalyst 9200 | 16.12.1 | 16.9.2 43 | |Catalyst 9300/9500 | 16.5.1a | 16.5.1a 44 | |Catalyst 9800 | 16.12.1 | 16.10.1 45 | |ASR 1000 Fixed | 16.7.1 | 3.12.0 (1001-X) / 16.2.1 (1002-HX) 46 | |ASR 1000 Modular | 16.8.2 | Varies (3.x) 47 | |Catalyst 8000 | 17.3.2 | 17.3.2 48 | 49 | HTTP-based download of ZTP Python script available as of 16.8.1. 50 | 51 | ZTP not supported in IOS XE 16.12.4 due to a defect. 52 | 53 | ZTP solution requires a DHCP server, which will inform the network device about where to find python file/configuration/software image etc to download. This can be a location on the network and can be on a TFTP or HTTP server. 54 | 55 | 56 | ## Deployment 57 | When an XE device boots and there is no config and when DHCP provides option 67 with this python file from repo, it will be automatically downloaded to device and gets executed. 58 | 59 | ### DHCP Server 60 | A DHCP server is required for ZTP, as this is how the device learns about where to find the Python configuration file from. In our case, the DHCP server is the open source ISC DHCPd and the configuration file is at /etc/dhcp/dhcpd.conf in a Linux developer box. The option bootfile-name is also known as option 67 and it specifies the python file ztp.py 61 | 62 | Below is a sample dhcpd.conf and someuseful commands for ISC DHCP server for your use. 63 | 64 | option domain-name "lab_name"; 65 | default-lease-time 600; 66 | max-lease-time 7200; 67 | ddns-update-style none; 68 | authoritative; 69 | subnet x.x.x.x netmask x.x.x.x { 70 | range 10.1.1.150 10.1.1.159; 71 | option domain-name ""; 72 | option domain-name-servers x.x.x.x; 73 | option subnet-mask x.x.x.x; 74 | option broadcast-address 1x.x.x.x; 75 | option routers x.x.x.x; 76 | option ntp-servers x.x.x.x; 77 | default-lease-time 600000; 78 | max-lease-time 720000; 79 | 80 | class "C9300-24T" { match if substring (option vendor-class-identifier,0,15) = "\tC9300-24T"; } 81 | 82 | pool { 83 | allow members of "C9300-24T"; 84 | deny members of "ciscopnp"; 85 | range x.x.x.x x.x.x.x; 86 | option bootfile-name "http://x.x.x.x/ztp.py"; 87 | } 88 | 89 | #### Useful DHCP commands 90 | cat /etc/dhcp/dhcpd.conf | grep bootfile-name 91 | 92 | Example for DHCP Option 67 bootfile-name with HTTP: 93 | option bootfile-name "http://x.x.x.x/ztp.py"; 94 | 95 | ps xa |grep dhcpd 96 | 97 | tail -F /var/log/dhcpd.log & 98 | 99 | In our case the Python file for ZTP is called ztp.py and is hosted at the webserver root directory which is set within the Apache webserver configuration. 100 | 101 | ### Web Server 102 | ZTP accesses the python configuration file from HTTP or TFTP server(In our case we use HTTP). 103 | Before running ZTP check that the Apache HTTPD server is running with the following commands, this will follow the log file from the webserver so you will see when the file is accessed. 104 | 105 | ps xa | grep httpd 106 | 107 | tail -F /var/log/httpd/access_log & 108 | 109 | ### What this python script (from this repo) do? 110 | 111 | Gets downloaded automatically to the device. 112 | 113 | Start execution in the guest shell. 114 | 115 | Logs ZTP process to persistent storage on the device flash for failure analysis. 116 | 117 | Expects input from user about http server address, target version to upgrade/downgrade, image name, image MD5. 118 | 119 | checks if upgrade/downgrade required and takes appropriate action. 120 | 121 | If upgrade required, transfers image from http server to device flash. 122 | 123 | Deploys an EEM script to perform upgrade steps and post upgrade(cleanup) steps. 124 | 125 | Runs the EEM script. 126 | 127 | Pushes the entire golden config or a partial config. 128 | 129 | Notifies user of success/failure on both CLI prompt and logs. 130 | 131 | ## Usage 132 | 133 | See the support matrix above and use this script accordingly. This script is tested across all XE versions that supports ZTP. 134 | 135 | 136 | ## Support Information 137 | 138 | • GuestShell/ZTP needs 1.1GB free space on bootflash. May be unable to launch GuestShell to execute ZTP if < 1.1 GB is free on bootflash. 139 | 140 | • Md5 checksum will fail on IOSXE V16.6 and V16.7 due to known issue , so the script will bypass that MD5 checksum on that specific versions and continue with the rest of the workflow 141 | 142 | • On 16.6.x and 16.7.x ZTP If image file transfer need to happen , it *might* intermittently fail for first time and ZTP could report fail ,but an *automatic* re attempt will be done and it should be successful in the subsequent attempt. 143 | 144 | 145 | ### Support Contacts 146 | 147 | Arun Kumar Sakthivel (arsakthi) 148 | 149 | Chitransh Pratyush (cpratyus) 150 | 151 | ### Health Monitoring 152 | 153 | Log Files from running this Python Script are enabled by default , Logging can be disabled by setting the flag log_tofile = False in the script 154 | On IOS XE 17.2.x and above log files are stored at '/flash/guest-share/ztp.log'. In all other version logs will be located at '/flash/ztp.log' 155 | 156 | ## Authors 157 | 158 | Arun Kumar Sakthivel (arsakthi@cisco.com) 159 | 160 | Chitransh Pratyush (cpratyus) 161 | 162 | ## License 163 | 164 | This project is covered under the terms described in [LICENSE](./LICENSE) 165 | 166 | 167 | ## Acknowledgment 168 | 169 | Jeremy Cohoe (jcohoe) . His XE ZTP script is a starting point for our codebase. 170 | -------------------------------------------------------------------------------- /ztp.py: -------------------------------------------------------------------------------- 1 | 2 | # Importing cli module 3 | from cli import configure, cli, configurep, executep 4 | import re 5 | import time 6 | import urllib 7 | import sys 8 | import logging 9 | import os 10 | from logging.handlers import RotatingFileHandler 11 | import subprocess 12 | 13 | software_mappings = { 14 | 'C9300-24P': { 15 | 'software_image': 'cat9k_iosxe.17.06.01.SPA.bin', 16 | 'software_version': '17.06.01', 17 | 'software_md5_checksum': 'fdb9c92bae37f9130d0ee6761afe2919' 18 | }, 19 | 'C9500-24Q': { 20 | 'software_image': 'cat9k_iosxe.17.06.01.SPA.bin', 21 | 'software_version': '17.06.01', 22 | 'software_md5_checksum': 'fdb9c92bae37f9130d0ee6761afe2919' 23 | }, 24 | 'ASR1001-HX': { 25 | 'software_image': 'asr1000-universalk9.17.05.01a.SPA.bin', 26 | 'software_version': '17.05.01a', 27 | 'software_md5_checksum': '0e4b1fc1448f8ee289634a41f75dc215' 28 | } 29 | } 30 | 31 | http_server = '10.85.134.66' 32 | log_tofile = True 33 | release_set = ['16.06', '16.07'] 34 | 35 | def main(): 36 | 37 | try: 38 | print ('###### STARTING ZTP SCRIPT ######\n') 39 | # switch to enable/disbale persistent logger 40 | if(log_tofile == True): 41 | filepath = create_logfile() 42 | configure_logger(filepath) 43 | 44 | log_info('###### STARTING ZTP SCRIPT ######\n') 45 | print ('**** Determining Device Model ****\n') 46 | log_info('**** Determining Device Model ****\n') 47 | model = get_model() 48 | 49 | software_image = software_mappings[model]['software_image'] 50 | software_version = software_mappings[model]['software_version'] 51 | software_md5_checksum = software_mappings[model]['software_md5_checksum'] 52 | 53 | print ('**** Checking if upgrade is required or not ***** \n') 54 | log_info('**** Checking if upgrade is required or not ***** \n') 55 | update_status, current_version = upgrade_required(software_version) 56 | if update_status: 57 | #check if image transfer needed 58 | if check_file_exists(software_image): 59 | print(current_version) 60 | if current_version[0:5] not in release_set: 61 | if not verify_dst_image_md5(software_image, software_md5_checksum): 62 | print ('*** Attempting to transfer image to switch.. ***') 63 | log_info('*** Attempting to transfer image to switch.. ***') 64 | file_transfer(http_server, software_image) 65 | if not verify_dst_image_md5(software_image, software_md5_checksum): 66 | log_critical('*** Failed Xfer mds hash mismatch ***') 67 | raise ValueError('Failed Xfer') 68 | else: 69 | file_transfer(http_server, software_image) 70 | if current_version[0:5] not in release_set: 71 | if not verify_dst_image_md5(software_image, software_md5_checksum): 72 | log_critical('XXX Failed Xfer XXX') 73 | raise ValueError('XXX Failed Xfer XXX') 74 | 75 | print ('*** Deploying EEM upgrade script ***') 76 | log_info('*** Deploying EEM upgrade script ***') 77 | deploy_eem_upgrade_script(software_image) 78 | print ('*** Performing the upgrade - switch will reboot ***\n') 79 | log_info('*** Performing the upgrade - switch will reboot ***\n') 80 | cli('event manager run upgrade') 81 | time.sleep(600) 82 | print('*** EEM upgrade took more than 600 seconds to complete..Increase the sleep time by few minutes before retrying ***\n') 83 | log_info('*** EEM upgrade took more than 600 seconds to reload the device..Increase the sleep time by few minutes before retrying ***\n') 84 | else: 85 | print ('*** No upgrade is required!!! *** \n') 86 | log_info('*** No upgrade is required!!! *** \n') 87 | 88 | # Cleanup any leftover install files 89 | print ('*** Deploying Cleanup EEM Script ***') 90 | log_info('*** Deploying Cleanup EEM Script ***') 91 | deploy_eem_cleanup_script() 92 | print ('*** Running Cleanup EEM Script ***') 93 | log_info('*** Running Cleanup EEM Script ***') 94 | cli('event manager run cleanup') 95 | time.sleep(30) 96 | 97 | #print config file name to download 98 | config_file = '%s.cfg' % model 99 | print('**** Downloading config file ****\n') 100 | log_info('**** Downloading config file ****\n') 101 | file_transfer(http_server, config_file) 102 | print ('*** Trying to perform Day 0 configuration push **** \n') 103 | log_info('*** Trying to perform Day 0 configuration push **** \n') 104 | #configure_replace(config_file) 105 | configure_merge(config_file) 106 | configure('crypto key generate rsa modulus 4096') 107 | print ('###### END OF ZTP SCRIPT ######\n') 108 | log_info('###### END OF ZTP SCRIPT ######\n') 109 | 110 | except Exception as e: 111 | print('*** Failure encountered during day 0 provisioning . Aborting ZTP script execution. Error details below ***\n') 112 | log_critical('*** Failure encountered during day 0 provisioning . Aborting ZTP script execution. Error details below ***\n' + e) 113 | print(e) 114 | sys.exit(e) 115 | 116 | 117 | def configure_replace(file,file_system='flash:/' ): 118 | config_command = 'configure replace %s%s force' % (file_system, file) 119 | print("************************Replacing configuration************************\n") 120 | log_info('************************Replacing configuration************************\n') 121 | config_repl = executep(config_command) 122 | time.sleep(120) 123 | 124 | def configure_merge(file,file_system='flash:/'): 125 | print("************************Merging running config with given config file************************\n") 126 | log_info('************************Merging running config with given config file************************\n') 127 | config_command = 'copy %s%s running-config' %(file_system,file) 128 | config_repl = executep(config_command) 129 | time.sleep(120) 130 | 131 | def check_file_exists(file, file_system='flash:/'): 132 | dir_check = 'dir ' + file_system + file 133 | print ('*** Checking to see if %s exists on %s ***' % (file, file_system)) 134 | log_info('*** Checking to see if %s exists on %s ***' % (file, file_system)) 135 | results = cli(dir_check) 136 | if 'No such file or directory' in results: 137 | print ('*** The %s does NOT exist on %s ***' % (file, file_system)) 138 | log_info('*** The %s does NOT exist on %s ***' % (file, file_system)) 139 | return False 140 | elif 'Directory of %s%s' % (file_system, file) in results: 141 | print ('*** The %s DOES exist on %s ***' % (file, file_system)) 142 | log_info('*** The %s DOES exist on %s ***' % (file, file_system)) 143 | return True 144 | elif 'Directory of %s%s' % ('bootflash:/', file) in results: 145 | print ('*** The %s DOES exist on %s ***' % (file, 'bootflash:/')) 146 | log_info('*** The %s DOES exist on %s ***' % (file, 'bootflash:/')) 147 | return True 148 | else: 149 | log_critical('************************Unexpected output from check_file_exists************************\n') 150 | raise ValueError("Unexpected output from check_file_exists") 151 | 152 | 153 | def deploy_eem_cleanup_script(): 154 | install_command = 'install remove inactive' 155 | eem_commands = ['event manager applet cleanup', 156 | 'event none maxrun 600', 157 | 'action 1.0 cli command "enable"', 158 | 'action 2.0 cli command "%s" pattern "\[y\/n\]"' % install_command, 159 | 'action 2.1 cli command "y" pattern "proceed"', 160 | 'action 2.2 cli command "y"' 161 | ] 162 | results = configurep(eem_commands) 163 | print ('*** Successfully configured cleanup EEM script on device! ***') 164 | log_info('*** Successfully configured cleanup EEM script on device! ***') 165 | 166 | def deploy_eem_upgrade_script(image): 167 | install_command = 'install add file flash://' + image + ' activate commit' 168 | eem_commands = ['event manager applet upgrade', 169 | 'event none maxrun 600', 170 | 'action 1.0 cli command "enable"', 171 | 'action 2.0 cli command "%s" pattern "\[y\/n\/q\]"' % install_command, 172 | 'action 2.1 cli command "n" pattern "proceed"', 173 | 'action 2.2 cli command "y"' 174 | ] 175 | results = configurep(eem_commands) 176 | print ('*** Successfully configured upgrade EEM script on device! ***') 177 | log_info('*** Successfully configured upgrade EEM script on device! ***') 178 | 179 | def file_transfer(http_server, file): 180 | print('**** Start transferring file *******\n') 181 | log_info('**** Start transferring file *******\n') 182 | res = cli('copy http://%s/%s flash:%s' % (http_server,file,file)) 183 | print(res) 184 | log_info(res) 185 | print("\n") 186 | print('**** Finished transferring device configuration file *******\n') 187 | log_info('**** Finished transferring device configuration file *******\n') 188 | 189 | def find_certs(): 190 | certs = cli('show run | include crypto pki') 191 | if certs: 192 | certs_split = certs.splitlines() 193 | certs_split.remove('') 194 | for cert in certs_split: 195 | command = 'no %s' % (cert) 196 | configure(command) 197 | 198 | def get_serial(): 199 | print ("******** Trying to get Serial# *********** ") 200 | log_info("******** Trying to get Serial# *********** ") 201 | try: 202 | show_version = cli('show version') 203 | except Exception as e: 204 | time.sleep(90) 205 | show_version = cli('show version') 206 | try: 207 | serial = re.search(r"System Serial Number\s+:\s+(\S+)", show_version).group(1) 208 | except AttributeError: 209 | serial = re.search(r"Processor board ID\s+(\S+)", show_version).group(1) 210 | return serial 211 | 212 | def get_model(): 213 | print ("******** Trying to get Model *********** ") 214 | log_info("******** Trying to get Model *********** ") 215 | try: 216 | show_version = cli('show version') 217 | except Exception as e: 218 | time.sleep(90) 219 | show_version = cli('show version') 220 | model = re.search(r"Model Number\s+:\s+(\S+)", show_version) 221 | if model != None: 222 | model = model.group(1) 223 | else: 224 | model = re.search(r"cisco\s(\w+-.*?)\s", show_version) 225 | if model != None: 226 | model = model.group(1) 227 | return model 228 | 229 | def get_file_system(): 230 | pass 231 | 232 | def update_config(file,file_system='flash:/'): 233 | update_running_config = 'copy %s%s running-config' % (file_system, file) 234 | save_to_startup = 'write memory' 235 | print("************************Copying to startup-config************************\n") 236 | running_config = executep(update_running_config) 237 | startup_config = executep(save_to_startup) 238 | 239 | def upgrade_required(target_version): 240 | # Obtains show version output 241 | sh_version = cli('show version') 242 | current_version = re.search(r"Cisco IOS XE Software, Version\s+(\S+)", sh_version).group(1) 243 | print('**** Current Code Version is %s ****** \n' % current_version) 244 | print('**** Target Code Version is %s ****** \n' % target_version) 245 | log_info('**** Current Code Version is %s ****** \n' % current_version) 246 | log_info('**** Target Code Version is %s ****** \n' % target_version) 247 | # Returns False if on approved version or True if upgrade is required 248 | 249 | if (target_version == current_version): 250 | return False, current_version 251 | else: 252 | return True, current_version 253 | 254 | def verify_dst_image_md5(image, src_md5, file_system='flash:/'): 255 | verify_md5 = 'verify /md5 ' + file_system + image 256 | print ('Verifying MD5 for ' + file_system + image) 257 | # 258 | try: 259 | dst_md5 = cli(verify_md5) 260 | if src_md5 in dst_md5: 261 | print ('*** MD5 hashes match!! ***\n') 262 | log_info('*** MD5 hashes match!! ***\n') 263 | return True 264 | else: 265 | print ('**** Failed transfer due to MD5 checksum mismatch *****') 266 | log_info('**** Failed transfer due to MD5 checksum mismatch *****') 267 | return False 268 | except Exception as e: 269 | print ('**** MD5 checksum failed due to an exception *****') 270 | print(e) 271 | log_info('**** MD5 checksum failed due to an exception *****') 272 | log_info(e) 273 | return True 274 | #output = subprocess.Popen(['md5sum', '/flash/'+image],stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 275 | #stdout_data, stderr_data = output.communicate() 276 | #output.wait() 277 | #outputdata = (stdout_data.decode('utf-8')).split() 278 | #md5_returned = outputdata[0] 279 | #if src_md5 == md5_returned: 280 | # return True 281 | #else: 282 | # return False 283 | 284 | def create_logfile(): 285 | try: 286 | print ("******** Creating a persistent log file *********** ") 287 | path = '/flash/guest-share/ztp.log' 288 | #file_exists = os.path.isfile(path) 289 | #if(file_exists == False): 290 | #print ("******** ztp.log file dont exist . *********** ") 291 | with open(path, 'a+') as fp: 292 | pass 293 | return path 294 | except IOError: 295 | print("Couldnt create a log file at guset-share .Trying to use /flash/ztp.log as an alternate log path") 296 | path = '/flash/ztp.log' 297 | #file_exists = os.path.isfile(path) 298 | #if(file_exists == False): 299 | # print ("******** ztp.log file dont exist . *********** ") 300 | with open(path, 'a+') as fp: 301 | pass 302 | return path 303 | except Exception as e: 304 | print("Couldnt create a log file to proceed") 305 | 306 | 307 | def configure_logger(path): 308 | log_formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(message)s') 309 | logFile = path 310 | #create a new file > 5 mb size 311 | log_handler = RotatingFileHandler(logFile, mode='a', maxBytes=5*1024*1024, backupCount=10, encoding=None, delay=0) 312 | log_handler.setFormatter(log_formatter) 313 | log_handler.setLevel(logging.INFO) 314 | ztp_log = logging.getLogger('root') 315 | ztp_log.setLevel(logging.INFO) 316 | ztp_log.addHandler(log_handler) 317 | 318 | def log_info(message ): 319 | if(log_tofile == True): 320 | ztp_log = logging.getLogger('root') 321 | ztp_log.info(message) 322 | 323 | def log_critical(message ): 324 | if(log_tofile == True): 325 | ztp_log = logging.getLogger('root') 326 | ztp_log.critical(message) 327 | 328 | 329 | if __name__ == "__main__": 330 | main() 331 | --------------------------------------------------------------------------------