def ztp_features(verboseFlag=False): features = getFeatures() for feat in features: if verboseFlag: print('%s: %s: %s' % (feat, getCfg('info-' + feat, ztp_cfg=ztp_cfg), getCfg(feat, ztp_cfg=ztp_cfg))) else: if getCfg(feat) is True: print(feat)
def __createProvScriptJson(self): '''! Create ZTP JSON data to execute provisioning script specified by DHCP Option 239 URL. ''' json_data = '{"ztp": {"provisioning-script":{"plugin":{"url":"file://' + getCfg( 'provisioning-script') + '","ignore-section-data":true}}\ ,"restart-ztp-no-config":false}}' f = open(getCfg('ztp-json'), 'w') f.write(json_data) f.close
def __forceRestartDiscovery(self, msg): # Remove existing leases to source new provisioning data self.__cleanup_dhcp_leases() _msg = '%s. Waiting for %d seconds before restarting ZTP.' % ( msg, getCfg('restart-ztp-interval')) logger.warning(_msg) updateActivity(_msg) time.sleep(getCfg('restart-ztp-interval')) self.ztp_mode = 'DISCOVERY' # Force install of ZTP configuration profile self.__ztp_profile_loaded = False # Restart link-scan self.__intf_state = dict()
def signal_handler(signum, frame): '''! This signal handler is called on SIGTERM or SIGINT ''' logger.warning('Received terminate signal. Shutting down.') updateActivity('Received terminate signal. Shutting down.') # Wait for some time count = getCfg('sighandler-wait-interval') while count > 0: done = True for pid in runcmd_pids: if check_pid(pid): done = False try: (wpid, status) = os.waitpid(pid, os.WNOHANG) if wpid == pid: print('Process pid %d returned with status %d.' % (pid, status)) except OSError as v: print('pid %d : %s' % (pid, str(v))) if done: break time.sleep(1) count -= 1 # Kill any process which might still be running for pid in runcmd_pids: if check_pid(pid): print('Process %d still alive, send kill signal.' % (pid)) os.kill(pid, signal.SIGKILL) sys.exit(0)
def setlogFile(self, log_file=None): '''! Set the log file to store all logs generated during ZTP @param log_file (str) log file @exception Raise TypeError if incorrect parameter type ''' rsyslog_conf_file = getCfg("rsyslog-ztp-log-file-conf", '/etc/rsyslog.d/10-ztp.conf') if log_file is None or (isString(log_file) and log_file == ''): if os.path.isfile(rsyslog_conf_file): os.remove(rsyslog_conf_file) return if not isString(log_file): raise TypeError("Log file must be a string") self.__log_file = log_file change = True if os.path.isfile(rsyslog_conf_file): fh = open(rsyslog_conf_file, 'r') if fh.readline().strip( ) == ':programname, contains, "sonic-ztp" ' + log_file: change = False fh.close() if change: fh = open(rsyslog_conf_file, 'w') fh.write(':programname, contains, "sonic-ztp" ' + log_file) fh.close() runCommand('systemctl restart rsyslog', capture_stdout=False)
def getcfg(self): ztp_cfg = ZTPCfg() ad = getCfg('admin-mode', ztp_cfg=ztp_cfg) if ad == False: return "disabled" else: return "enabled"
def __init_json(self): content = """{ "ztp": { "0002-test-plugin": { "message" : "0002-test-plugin", "message-file" : "/etc/ztp.results", "fail" : true, "ignore-result" : true, "halt-on-failure" : true }, "0003-test-plugin": { "plugin" : { "name" : "test-plugin" }, "message" : "0003-test-plugin", "message-file" : "/etc/ztp.results", "fail" : false }, "0001-test-plugin": { "plugin" : "test-plugin", "message" : "0001-test-plugin", "message-file" : "/etc/ztp.results", "fail" : false, "ignore-result" : true } } }""" f = open(getCfg('ztp-json'), "w") f.write(content) f.close()
def __init__(self, json_src_file=None, json_dst_file=None): '''! Constructor for ConfigurationSection class. @param json_src_file (str, optional) Configuration section input.json file to be prcoessed. If not specified, /etc/sonic/ztp_data.json file is used. @param json_dst_file (str, optional) Destination file to which processed JSON data is saved to. If not specified, json_src_file is used as destination file. @exception Raise ValueError if any error or exception encountered while processing the json_src_file ''' if json_src_file is not None: self.json_src_file = json_src_file else: self.json_src_file = getCfg('ztp-json') if json_dst_file is None: self.json_dst_file = self.json_src_file else: self.json_dst_file = json_dst_file try: self.objJson, self.jsonDict = JsonReader(self.json_src_file, self.json_dst_file, indent=4) except: raise ValueError('ZTP JSON load failed')
def __cleanup_dhcp_leases(self): # Use ZTP interface used to obtain provisioning information runCommand('rm -f /var/lib/dhcp/dhclient*.eth0.leases', capture_stdout=False) if getCfg('feat-inband'): runCommand('rm -f /var/lib/dhcp/dhclient*.Ethernet*.leases', capture_stdout=False)
def __writeConfigSections(self, key, val): '''! Split ZTP JSON into individual configuration sections. @param key (str) Configuration section name @param value (object) Configuration section data ''' section_dir = getCfg('ztp-tmp-persistent') + '/' + key section_file = section_dir + '/' + getCfg('section-input-file') try: if os.path.isdir(section_dir) is False: os.makedirs(section_dir) self.objJson.writeJson(section_file, {key: val}, getCfg('json-indent')) except: raise ValueError( 'Unable to write Configuration Section %s JSON data to file %s' % (key, section_file))
def __cleanup(self): '''! Remove stale ZTP session data. ''' dir_list = ['ztp-tmp', 'ztp-tmp-persistent'] try: # Remove temporary files created by previous ZTP run for d in dir_list: if os.path.isdir(getCfg(d)): shutil.rmtree(getCfg(d), ignore_errors=True) # Create them again for d in dir_list: os.makedirs(getCfg(d)) except OSError as e: logger.error( 'Exception [%s] encountered while cleaning up temp directories.' % str(e))
def test_ztp_graphservice_do_not_exist(self, tmpdir): '''! Test when the plugin is a graphservice and does not exist. ''' content = """{ "ztp": { "0001-test-provisioning-script": { "halt-on-failure": false, "ignore-result": false, "plugin": "graphservice", "reboot-on-failure": false, "reboot-on-success": false, "status": "BOOT", "timestamp": "2019-04-18 19:49:49" }, "config-fallback": false, "halt-on-failure": false, "ignore-result": false, "reboot-on-failure": false, "reboot-on-success": false, "restart-ztp-no-config": true, "restart-ztp-on-failure": false, "status": "BOOT", "timestamp": "2019-04-18 19:49:49", "ztp-json-version": "1.0" } }""" self.__write_file( "/tmp/test_firmware_" + socket.gethostname() + ".json", content) d = tmpdir.mkdir("valid") fh = d.join("test.json") fh.write(""" { "ztp" : { "dynamic-url" : { "source" : { "prefix" : "file:///tmp/test_firmware_", "identifier" : "hostname", "suffix" : ".json" } } } } """) d2 = tmpdir.mkdir("dest") fh2 = d2.join("dest_file") ztpjson = ZTPJson(str(fh), json_dst_file=str(fh2)) assert (self.__read_file(str(fh2)) == content) content = """ #!/bin/sh echo Hello exit 2 """ plugin_name = ztpjson.plugin('0001-test-provisioning-script') assert (plugin_name == getCfg('plugins-dir') + '/graphservice')
def test_invalid_log_level(self): '''! Test invalid log level ''' saved_value = getCfg('log-level') setCfg('log-level', None) log = Logger() assert (log != None) setCfg('log-level', saved_value)
def __read_ztp_interface(self): intf_file = getCfg('ztp-run-dir') + '/ztp.lock/interface' if os.path.isfile(intf_file): f = open(intf_file, 'r') try: self.__ztp_interface = f.readline().strip().split(':')[1] except: self.__ztp_interface = None pass f.close()
def __link_scan(self): '''! Scan all in-band interface's operational status to detect a link up event ''' # Do not attempt link scan when in test mode if self.test_mode: return False if self.__link_scan_enabled is None: # Check if ZTP configuration is active (rc, op, errStr ) = runCommand("redis-cli -n 4 HGET \"ZTP|mode\" \"profile\"") if rc == 0 and len(op) != 0 and op[0] == 'active': self.__link_scan_enabled = 'True' else: self.__link_scan_enabled = 'False' if self.__link_scan_enabled == 'False': return False link_scan_result = False (rc, port_table_data, err) = runCommand('redis-dump -d 0 -k PORT_TABLE:Ethernet*') if rc != 0: port_table = None else: try: port_table = json.loads(''.join(port_table_data)) except: port_table = None intf_data = os.listdir('/sys/class/net') if getCfg('feat-inband'): r_intf = re.compile("Ethernet.*|eth.*") else: r_intf = re.compile("eth.*") intf_list = list(filter(r_intf.match, intf_data)) for intf in intf_list: try: if intf[0:3] == 'eth': fh = open('/sys/class/net/{}/operstate'.format(intf), 'r') operstate = fh.readline().strip().lower() fh.close() else: operstate = port_table.get('PORT_TABLE:' + intf).get( 'value').get('oper_status').lower() except: operstate = 'down' if ((self.__intf_state.get(intf) is None) or \ (self.__intf_state.get(intf) != operstate)) and \ operstate == 'up': link_scan_result = True logger.info('Link up detected for interface %s' % intf) self.__intf_state[intf] = operstate return link_scan_result
def getIdentifier(self): '''! Obtain the resolved identifier string based on the identifier data. @return In case of success: \n Identifier string \n In case of error: \n None ''' try: identifier_data = self.identifier_data if isinstance(identifier_data, dict) is False: if identifier_data == "hostname": return socket.gethostname() elif identifier_data == "hostname-fqdn": return socket.getfqdn() elif identifier_data == "serial-number": return sysEeprom.get_serial_number() elif identifier_data == "product-name": return sysEeprom.get_product_name() elif identifier_data == "sonic-version": return get_sonic_version() else: return identifier_data elif identifier_data.get('url') is not None: url_data = identifier_data.get('url') (fd, filename) = tempfile.mkstemp(prefix='identifier_', dir=getCfg('ztp-tmp')) os.close(fd) urlObj = URL(url_data, filename) updateActivity('Downloading identifier script from \'%s\'' % (urlObj.getSource())) rc, identifier_script = urlObj.download() if rc == 0 and os.path.isfile(identifier_script): updateActivity( 'Executing identifier script downloaded from \'%s\'' % (urlObj.getSource())) (rc, cmd_stdout, cmd_stderr) = runCommand([identifier_script]) if rc != 0: logger.error( 'Error encountered while executing identifier %s Exit code: (%d).' % (identifier_script, rc)) return None if len(cmd_stdout) == 0: return "" else: return cmd_stdout[0] except (TypeError, ValueError) as e: logger.error( 'Exception: [%s] encountered while processing identifier data in dynamic URL.' % str(e)) return None return None
def test_load_config_file(self): '''! Assuming that the key [ztp-json] in ztp_cfg.json exist and is a valid json file, verify that the constructor is loading this file. ''' self.__init_json() config_section = ConfigSection() assert (config_section != None) json_src_file = getCfg('ztp-json') ztpcfg = ConfigSection(json_src_file, json_src_file) assert (ztpcfg != None)
def test_load_config_file_nexist(self): '''! Assuming that the key [ztp-json] in ztp_cfg.json exist and is a valid json file, verify that the constructor is loading this file. ''' self.__init_json() json_src_file = getCfg('ztp-json') shutil.move(json_src_file, json_src_file + '.saved') with pytest.raises(ValueError): ztpjson = ZTPJson() shutil.move(json_src_file + '.saved', json_src_file)
def pluginArgs(self, section_name): '''! Resolve the plugin arguments used to be passed as command line arguments to the plugin used to process configuration section @param section_name (str) Configuration section name whose plugin arguments needs to be resolved. @return Concatenated string of all the argements defined \n If no arguments are defined or error encountered: \n None ''' if isString(section_name) is False: raise TypeError('Invalid argument used as section name') elif self.ztpDict.get(section_name) is None: logger.error('Configuration Section %s not found.' % section_name) return None # Obtain plugin data plugin_data = self.ztpDict.get(section_name).get('plugin') # Get the location of this configuration section's input data parsed from the input ZTP JSON file plugin_input = getCfg( 'ztp-tmp-persistent') + '/' + section_name + '/' + getCfg( 'section-input-file') plugin_args = '' ignore_section_data_arg = getField(plugin_data, 'ignore-section-data', bool, False) _plugin_json_args = getField(plugin_data, 'args', str, None) if ignore_section_data_arg is False: plugin_args = plugin_input if _plugin_json_args is not None: plugin_args = plugin_args + ' ' + _plugin_json_args logger.debug('Plugin arguments for %s evaluated to be %s.' % (section_name, plugin_args)) return plugin_args
def getActivityString(): if ztp_active() != 0: return 'ZTP Service is not running' activity_str = None f = getCfg('ztp-activity') if os.path.isfile(f): fh = open(f, 'r') activity_str = fh.readline().strip() fh.close() if activity_str is not None and activity_str != '': return getTimeString(activity_str)
def __buildDefaults(self, section): '''! Helper API to include missing objects in a configuration section and validate their values. Below are the objects that are validated and added if not present: \n - ignore-result\n - reboot-on-success\n - reboot-on-failure\n - halt-on-failure\n - timestamp\n Below are the objects whose value is validated:\n - status - suspend-exit-code If the specified value is invalid of if the object is not specified, its value is read from ztp_cfg.json @param section (dict) Configuration Section input data read from JSON file. ''' default_objs = [ 'ignore-result', 'reboot-on-success', 'reboot-on-failure', 'halt-on-failure' ] # Loop through objects and update them with default values for key in default_objs: _val = getField(section, key, bool, getCfg(key)) section[key] = _val # set status if section.get('status') is None: section['status'] = 'BOOT' section['timestamp'] = getTimestamp() elif isString(section.get('status')) is False or \ section.get('status') not in ['BOOT', 'IN-PROGRESS', 'SUSPEND', 'DISABLED', 'FAILED', 'SUCCESS']: logger.warning( 'Invalid value (%s) used for configuration section status. Setting it to DISABLED.' % section.get('status')) section['status'] = 'DISABLED' section['timestamp'] = getTimestamp() # Validate suspend-exit code if section.get('suspend-exit-code') is not None: suspend_exit_code = getField(section, 'suspend-exit-code', int, None) if suspend_exit_code is None or suspend_exit_code < 0: del section['suspend-exit-code'] # Add timestamp if missing if section.get('timestamp') is None: section['timestamp'] = getTimestamp()
def __buildDefaults(self): '''! Helper API to include missing objects in a configuration section and validate their values. ''' # Call more abstract API to insert default values self._ConfigSection__buildDefaults(self.ztpDict) default_objs = [ 'restart-ztp-on-failure', 'restart-ztp-no-config', 'config-fallback' ] # Loop through objects and update them with default values for key in default_objs: _val = getField(self.ztpDict, key, bool, getCfg(key)) self.ztpDict[key] = _val
def __detect_intf_state(self): '''! Identifies all the interfaces on which ZTP discovery needs to be performed. Link state of each identified interface is checked and stored in a dictionary for reference. @return True - If an interface moved from link down to link up state False - If no interface transitions have been observed ''' link_up_detected = False intf_data = os.listdir('/sys/class/net') if getCfg('feat-inband'): r_intf = re.compile("Ethernet.*|eth.*") else: r_intf = re.compile("eth.*") intf_list = list(filter(r_intf.match, intf_data)) for intf in natsorted(intf_list): try: if intf[0:3] == 'eth': fh = open('/sys/class/net/{}/operstate'.format(intf), 'r') operstate = fh.readline().strip().lower() fh.close() else: if self.applDB.exists(self.applDB.APPL_DB, 'PORT_TABLE:' + intf): port_entry = self.applDB.get_all( self.applDB.APPL_DB, 'PORT_TABLE:' + intf) operstate = port_entry.get('oper_status').lower() else: operstate = 'down' except: operstate = 'down' if ((self.__intf_state.get(intf) is None) or \ (self.__intf_state.get(intf).get('operstate') != operstate)) and \ operstate == 'up': link_up_detected = True logger.info('Link up detected for interface %s' % intf) if self.__intf_state.get(intf) is None: self.__intf_state[intf] = dict() self.__intf_state[intf]['operstate'] = operstate # Weed out any stale interfaces that may exist when an expanded port is joined back intf_snapshot = list(self.__intf_state.keys()) for intf in intf_snapshot: if intf not in intf_list: del self.__intf_state[intf] return link_up_detected
def __removeZTPProfile(self): '''! If ZTP configuration profile is operational, remove ZTP configuration profile and load startup configuration file. If there is no startup configuration file, load factory default configuration. ''' # Do not attempt to remove ZTP configuration if working in unit test mode if self.test_mode: return # Remove ZTP configuration profile if loaded updateActivity('Verifying configuration') # Use a fallback default configuration if configured to _config_fallback = '' if (self.objztpJson is not None and (self.objztpJson['status'] == 'FAILED' or self.objztpJson['status'] == 'SUCCESS') \ and self.objztpJson['config-fallback']) or \ (self.objztpJson is None and getCfg('config-fallback') is True): _config_fallback = ' config-fallback' # Execute profile removal command with appropriate options rc = runCommand(getCfg('ztp-lib-dir') + '/ztp-profile.sh remove' + _config_fallback, capture_stdout=False) # Remove ZTP configuration startup-config if os.path.isfile(getCfg('config-db-json')) is True: try: config_db = None with open(getCfg('config-db-json')) as json_file: config_db = json.load(json_file) json_file.close() if config_db is not None and config_db.get('ZTP'): logger.info("Deleting ZTP configuration saved in '%s'." % (getCfg('config-db-json'))) del config_db['ZTP'] with open(getCfg('config-db-json'), 'w') as json_file: json.dump(config_db, json_file, indent=4) json_file.close() except Exception as e: logger.error( "Exception [%s] encountered while verifying '%s'." % (str(e), getCfg('config-db-json'))) self.__ztp_profile_loaded = False
def __loadZTPProfile(self, event): '''! Load ZTP configuration profile if there is no saved configuration file. This establishes connectivity to all interfaces and starts DHCP discovery. ''' # Do not attempt to install ZTP configuration if working in unit test mode if self.test_mode: return False if self.__ztp_profile_loaded is False: updateActivity('Checking running configuration') logger.info( 'Checking running configuration to load ZTP configuration profile.' ) cmd = getCfg('ztp-lib-dir') + '/ztp-profile.sh install ' + event # When performing ZTP discovery, force load ZTP profile. When # ZTP is resuming previous session, use configuration already loaded during # config-setup rc = runCommand(cmd, capture_stdout=False) self.__ztp_profile_loaded = True return True return False
def __createGraphserviceJson(self): '''! Create ZTP JSON data to load graph file specified by DHCP Option 225 URL. Also includes ACL JSON file if specified by DHCP Option 226. ''' # Verify that graph file can be downloaded if self.__downloadURL(getCfg('graph-url'), '/tmp/test_minigraph.xml') is False: return False else: # Clean up os.remove('/tmp/test_minigraph.xml') # Verify that acl json file can be downloaded if os.path.isfile(getCfg('acl-url')): if self.__downloadURL(getCfg('acl-url'), '/tmp/test_acl.json') is False: return False else: # Clean up os.remove('/tmp/test_acl.json') # Read the url file and identify the URL to be downloaded f = open(getCfg('graph-url'), 'r') graph_url_str = f.readline().strip() f.close() acl_url_str = None if os.path.isfile(getCfg('acl-url')): f = open(getCfg('acl-url'), 'r') acl_url_str = f.readline().strip() f.close() json_data = '{"ztp":{"graphservice": { "minigraph-url" : { "url":"' + graph_url_str + '"}' if acl_url_str is not None and len(acl_url_str) != 0: json_data = json_data + ', "acl-url" : { "url":"' + acl_url_str + '"}' json_data = json_data + '}, "restart-ztp-no-config":false} }' f = open(getCfg('ztp-json'), 'w') f.write(json_data) f.close return True
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' import sys import os import multiprocessing import json import pytest from .testlib import createPySymlink from ztp.ZTPLib import runCommand, getCfg from ztp.DecodeSysEeprom import sysEeprom sys.path.append(getCfg('plugins-dir')) createPySymlink(getCfg('plugins-dir')+'/configdb-json') createPySymlink('/usr/lib/ztp/plugins/configdb-json') from configdb_json import ConfigDBJson class TestClass(object): '''! This class allow to define unit tests for class Snmp ''' def __read_file(self, fname): try: f = open(fname, 'r') return f.read()
def __init__(self, url=None, dst_file=None, incl_http_headers=None, is_secure=None, timeout=None, retry=None, curl_args=None, encrypted=None): '''! Constructor for the class, and optionally provide the parameters which can be used later by getUrl() @param url (str, optional) url of the file you want to get @param dst_file (str, optional) Filename for the data being stored. \n If not specified, it will be derived from the url (last part of it, e.g. basename). @param incl_http_headers (bool, optional) Include or not the additional HTTP headers (product name, serial number, mac address) @param is_secure (bool, optional) Every SSL connection curl makes is verified or not to be secure @param timeout (int, optional) Maximum number of seconds allowed for curl's connection to take. \n This only limits the connection phase. @param retry (int, optional) Number of times curl will retry in case of a transient error @param curl_args (str, optional) Options you want to pass to curl command line program @param encrypted (bool) Is the connectin with the server being encrypted? @return In case of success: \n Tupple: (0, data) \n In case of error: \n Tupple: (error code, html content) ''' ## url of the file you want to get self.__url = url ## Name of the file where the file retrieved from the server is saved self.__dst_file = dst_file ## Do we send the HTTP headers (product name, serial number, mac address)? if incl_http_headers is None: self.__incl_http_headers = getCfg('include-http-headers') else: self.__incl_http_headers = incl_http_headers ## Should curl consider the SSL connection as secure or not? if is_secure is not None: self.__is_secure = is_secure else: self.__is_secure = getCfg('https-secure') ## Maximum number of seconds allowed for curl's connection to take if timeout is None: self.__timeout = getCfg('curl-timeout') else: self.__timeout = timeout ## Number of times curl will retry in case of a transient error if retry is None: self.__retry = getCfg('curl-retries') else: self.__retry = retry ## Optional curl cli program options self.__curl_args = curl_args ## Is the connection with the server encrypted? self.__encrypted = encrypted # Read system eeprom ## Product name read from the system eeprom self.__product_name = sysEeprom.get_product_name() ## Serial number read from the system eeprom self.__serial_number = sysEeprom.get_serial_number() ## MAC address read from the system eeprom self.__mac_addr = sysEeprom.get_mac_addr() # We need some items from the global ZTP config file ## Read which http-user-agent curl will be returning to the server self.__user_agent = getCfg('http-user-agent') # Read SONiC version self.__sonic_version = get_sonic_version() # Generate the http headers ## We include Product name, S/N and MAC address in the http headers sent to the server self.__http_headers = [] if self.__product_name is not None: self.__http_headers.append('PRODUCT-NAME: ' + self.__product_name) if self.__serial_number is not None: self.__http_headers.append('SERIAL-NUMBER: ' + self.__serial_number) if self.__mac_addr is not None: self.__http_headers.append('BASE-MAC-ADDRESS: ' + self.__mac_addr) if self.__sonic_version is not None: self.__http_headers.append('SONiC-VERSION: ' + self.__sonic_version)
def getUrl(self, url=None, dst_file=None, incl_http_headers=None, is_secure=True, timeout=None, retry=None, curl_args=None, encrypted=None, verbose=False): '''! Fetch a file using a given url. The content retrieved from the server is stored into a file. In case of a server erreur, the html content is stored into the file. This can be used to have a finer diagnostic of the issue. @param url (str, optional) url of the file you want to get. \n If the url is not given, the one specified in the constructor will be used. @param dst_file (str, optional) Filename for the data being stored. \n If not specified here and in the colnstructor, it will be derived from the url \n (last part of it, e.g. basename). @param incl_http_headers (bool, optional) Include or not the additional HTTP headers \n (product name, serial number, mac address) @param is_secure (bool, optional) Every SSL connection curl makes is verified or not to be secure. \n By default, the SSL connection is assumed secure. @param timeout (int, optional) Maximum number of seconds allowed for curl's connection to take. \n This only limits the connection phase. @param retry (int, optional) Number of times curl will retry in case of a transient error @param curl_args (str, optional) Options you want to pass to curl command line program. \n If no parameters are given here, the one given in the constructor will be used. @param encrypted (bool) Is the connection with the server being encrypted? @return Return a tuple: \n - In case of success: (0 destination_filename)\n - In case of error: (error_code None)\n See here to see the status codes returned:\n https://ec.haxx.se/usingcurl-returns.html \n Note that we return error 20 in case of an unknown error. ''' # Use arguments provided in the constructor if url is None and self.__url is not None: url = self.__url if dst_file is None and self.__dst_file is not None: dst_file = self.__dst_file if incl_http_headers is None and self.__incl_http_headers is not None: incl_http_headers = self.__incl_http_headers if is_secure is None and self.__is_secure is not None: is_secure = self.__is_secure if timeout is None and self.__timeout is not None: timeout = self.__timeout if retry is None and self.__retry is not None: retry = self.__retry if curl_args is None and self.__curl_args is not None: curl_args = self.__curl_args if encrypted is None and self.__encrypted is not None: encrypted = self.__encrypted # We can't run without a URL if url is None: return (-1, dst_file) # If no filename is provided, we use the last part of the url if dst_file is None: dst_file = os.path.basename(url) # If there is no path in the provided filename, we store the file under this default location try: if dst_file.find('/') == -1: dst_file = getCfg('ztp-tmp') + '/' + dst_file except (AttributeError) as e: logger.error("!Exception : %s" % (str(e))) return (20, None) # Create curl command cmd = '/usr/bin/curl -f -v -s -o ' + dst_file if self.__user_agent is not None: cmd += ' -A "' + self.__user_agent + '"' # --user-agent if is_secure is False: cmd += ' -k' # --insecure if timeout is not None and isinstance(timeout, int) is True: cmd += ' --connect-timeout ' + str(timeout) if retry is not None and isinstance(retry, int) is True: cmd += ' --retry ' + str(retry) if incl_http_headers is not None: for h in self.__http_headers: cmd += ' -H \"' + h + '"' # --header if curl_args is not None: cmd += ' ' + curl_args cmd += ' ' + url if verbose is True: logger.debug('%s' % (cmd)) # Execute curl command _retries = retry while True: _start_time = time.time() (rc, cmd_stdout, cmd_stderr) = runCommand(cmd) _current_time = time.time() if rc != 0 and rc in [ 5, 6, 7 ] and _retries != 0 and (_current_time - _start_time) < timeout: logger.debug( "!Error (%d) encountered while processing the command : %s" % (rc, cmd)) time.sleep(timeout - (_current_time - _start_time)) _retries = _retries - 1 continue if rc != 0: logger.error( "!Error (%d) encountered while processing the command : %s" % (rc, cmd)) for l in cmd_stdout: # pragma: no cover logger.error(str(l)) for l in cmd_stderr: logger.error(str(l)) if os.path.isfile(dst_file): os.remove(dst_file) return (20, None) else: break os.chmod(dst_file, stat.S_IRWXU) # Use curl result return (0, dst_file)
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' import sys import signal import os import stat import pytest from ztp.ZTPLib import runCommand, getField, getCfg, printable sys.path.append(getCfg('plugins-dir')) class TestClass(object): '''! This class allow to define unit tests for class Firmware \endcode ''' def test_cmd(self, tmpdir): (rc1, cmd_stdout1, cmd_stderr1) = runCommand('/bin/cat /proc/devices') (rc2, cmd_stdout2, cmd_stderr2) = runCommand('/bin/cat /proc/devices', use_shell=True) (rc3, cmd_stdout3, cmd_stderr3) = runCommand(['/bin/cat', '/proc/devices']) (rc4, cmd_stdout4,