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 __init__(self, url_data, destination=None): '''! Constructor for the URL class. @param url_data (dict) Information to be used to download the file @param destination (str, optional) When specified, this represents the filename used to save the \n downloaded file as. If destination value is available in url_data, this parameter is ignored. ''' argError = False self.__destination = None if isString(url_data): self.__source = url_data self.__destination = destination elif isinstance(url_data, dict) is False or \ url_data.get('source') is None or \ isString(url_data.get('source')) is False: argError = True elif isinstance(url_data, dict): self.__source = url_data.get('source') if url_data.get('destination') is None: self.__destination = destination else: self.__destination = url_data.get('destination') if self.__destination is not None and isString( self.__destination) is False: argError = True if argError: logger.debug('URL provided with invalid argument types.') raise TypeError('URL provided with invalid argument types.') self.url_data = url_data if isinstance(url_data, dict): self.objDownload = Downloader(self.__source, \ self.__destination, \ incl_http_headers=getField(self.url_data, 'include-http-headers', bool, None), \ is_secure=getField(self.url_data, 'secure', bool, None), \ curl_args=self.url_data.get('curl-arguments'), \ encrypted=getField(self.url_data, 'encrypted', bool, None), \ timeout=getField(self.url_data, 'timeout', int, None)) else: self.objDownload = Downloader(self.__source, self.__destination)
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 updateStatus(self, obj, status): '''! Update status of configuration section. Also update the timestamp indicating date/time when it is updated. The changes are also saved to the JSON file on disk which corresponds to the configuration section. @param status (str) Value to be stored as status ''' if isinstance(obj, dict) and isString(status): self.objJson.set(obj, 'status', status) self.objJson.set(obj, 'timestamp', getTimestamp(), True) else: logger.error('Invalid argument type.') raise TypeError('Invalid argument type')
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 setLevel(self, log_level): '''! Set the current level of logging. @param log_level (int) log level which will be shown on stdout (DEBUG, INFO, WARNING, ERROR or CRITICAL) @exception Raise TypeError if incorrect parameter type ''' if not isString(log_level) and not type(log_level) == int: raise TypeError("Log Level must be a number or a string") if type(log_level) == int: self.__log_level = log_level else: self.__log_level = self.__str_to_int_level(log_level) if type(log_level) == int: if (self.__log_level > self.DEBUG) or (self.__log_level < self.CRITICAL): self.__log_level = self.INFO syslog.setlogmask(syslog.LOG_UPTO(self.__log_level))
def __init__(self, dyn_url_data, destination=None): '''! Constructor for the DynamicURL class. @param dyn_url_data (dict) Information to be used to download the file @param destination (str, optional) When specified, this represents the filename used to save the \n downloaded file as. If destination value is available in url_data, this parameter is ignored. @exception Raise ValueError if insufficient input is provided. @exception Raise TypeError if invalid input is provided or if switch identifier could not be resolved. Switch identifier string is resolved as part of the constructor. ''' if dyn_url_data is None or isinstance(dyn_url_data, dict) is False or \ dyn_url_data.get('source') is None or isinstance(dyn_url_data.get('source'), dict) is False or \ dyn_url_data.get('source').get('identifier') is None: logger.debug('DynamicURL provided with invalid argument types.') raise TypeError('DynamicURL provided with invalid argument types') self.__destination = None if dyn_url_data.get('destination') is not None: self.__destination = dyn_url_data.get('destination') elif destination is not None: self.__destination = destination if self.__destination is not None and \ isString(self.__destination) is False: logger.debug('DynamicURL provided with invalid argument types.') raise TypeError('DynamicURL provided with invalid argument types') self.dyn_url_data = dyn_url_data source = dyn_url_data.get('source') self.__source = '' if source.get('prefix') is not None: if isString(source.get('prefix')): self.__source = source.get('prefix') else: raise TypeError( 'DynamicURL provided with invalid argument types.') objIdentifier = Identifier(source.get('identifier')) identifier = objIdentifier.getIdentifier() if identifier is None: raise ValueError( 'DynamicURL source identifier could not be evaluated.') else: self.__source = self.__source + identifier if source.get('suffix') is not None: if isString(source.get('suffix')): self.__source = self.__source + source.get('suffix') else: raise TypeError( 'DynamicURL provided with invalid argument types.') self.objDownload = Downloader(self.__source, \ self.__destination, \ incl_http_headers=getField(self.dyn_url_data, 'include-http-headers', bool, None), \ is_secure=getField(self.dyn_url_data, 'secure', bool, None), \ curl_args=self.dyn_url_data.get('curl-arguments'), \ encrypted=getField(self.dyn_url_data, 'encrypted', bool, None), \ timeout=getField(self.dyn_url_data, 'timeout', int, None))
def plugin(self, section_name): '''! Resolve the plugin used to process a configuration section. If the plugin is specified as a url object, the plugin is downloaded. @param section_name (str) Configuration section name whose plugin needs to be resolved. @return If plugin is resolved using configuration section data: \n Expanded file path to plugin file used to process configuration section. \n If plugin is not found 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 plugin_data = self.ztpDict.get(section_name).get('plugin') name = None if plugin_data is not None and isinstance(plugin_data, dict): logger.debug( 'User defined plugin detected for configuration section %s.' % section_name) plugin_file = getCfg( 'ztp-tmp-persistent') + '/' + section_name + '/' + 'plugin' try: # Re-use the plugin if already present if os.path.isfile(plugin_file) is True: return plugin_file if plugin_data.get('dynamic-url'): dyn_url_data = plugin_data.get('dynamic-url') if isinstance(dyn_url_data, dict) and dyn_url_data.get( 'destination') is not None: objDynUrl = DynamicURL(dyn_url_data) else: objDynUrl = DynamicURL(dyn_url_data, plugin_file) rc, plugin_file = objDynUrl.download() return plugin_file elif plugin_data.get('url'): url_data = plugin_data.get('url') if isinstance( url_data, dict) and url_data.get('destination') is not None: objUrl = URL(url_data) else: objUrl = URL(url_data, plugin_file) updateActivity( 'Downloading plugin \'%s\' for configuration section %s' % (objUrl.getSource(), section_name)) rc, plugin_file = objUrl.download() if rc != 0: logger.error( 'Failed to download plugin \'%s\' for configuration section %s.' % (objUrl.getSource(), section_name)) return plugin_file elif plugin_data.get('name') is not None: name = plugin_data.get('name') except (TypeError, ValueError, OSError, IOError) as e: logger.error( 'Exception [%s] encountered while determining plugin for configuration section %s.' % (str(e), section_name)) return None elif plugin_data is not None and isString(plugin_data): name = plugin_data elif plugin_data is not None: logger.error( 'Invalid plugin data type used for configuration section %s.' % section_name) return None # plugin name is not provided in section data, use section name as plugin name if name is None: res = re.split("^[0-9]+-", section_name, maxsplit=1) if len(res) > 1: name = res[1] else: name = res[0] logger.debug( 'ZTP provided plugin %s is being used for configuration section %s.' % (name, section_name)) if os.path.isfile(getCfg('plugins-dir') + '/' + name) is True: return getCfg('plugins-dir') + '/' + name return None