예제 #1
0
    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()
예제 #2
0
    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)
예제 #3
0
    def __rebootAction(self, section, delayed_reboot=False):
        '''!
         Perform system reboot if reboot-on-success or reboot-on-failure is defined in the
         configuration section data.

         @param section (dict) Configuration section data containing status and reboot-on flags

        '''

        # Obtain section status
        status = section.get('status')

        # Check if flag is set to reboot on SUCCESS and status is SUCCESS as well
        if getField(section, 'reboot-on-success', bool,
                    False) is True and status == 'SUCCESS':
            logger.warning(
                'ZTP is rebooting the device as reboot-on-success flag is set.'
            )
            updateActivity('System reboot requested on success')
            if self.test_mode and delayed_reboot == False:
                sys.exit(0)
            else:
                if delayed_reboot:
                    self.reboot_on_completion = True
                else:
                    systemReboot()

        # Check if flag is set to reboot on FAIL and status is FAILED as well
        if getField(section, 'reboot-on-failure', bool,
                    False) is True and status == 'FAILED':
            logger.warning(
                'ZTP is rebooting the device as reboot-on-failure flag is set.'
            )
            updateActivity('System reboot requested on failure')
            if self.test_mode and delayed_reboot == False:
                sys.exit(0)
            else:
                if delayed_reboot:
                    self.reboot_on_completion = True
                else:
                    systemReboot()
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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))
예제 #7
0
    def test_getField(self):
        data = dict({'key': 'val'})
        assert (getField(data, 'key', str, 'defval') == 'val')

        data = dict({'key': 10})
        assert (getField(data, 'key', str, 'defval') == 'defval')

        data = dict({'key': 'val'})
        assert (getField(data, 'key2', str, 'defval') == 'defval')

        data = dict({'key': True})
        assert (getField(data, 'key', bool, False) == True)

        data = dict({'key': 'TrUe'})
        assert (getField(data, 'key', bool, False) == True)

        data = dict({'key': 'fTrUe'})
        assert (getField(data, 'key', bool, False) == False)

        data = dict({'key': 'FalSe'})
        assert (getField(data, 'key2', bool, True) == True)

        data = dict({'key': 'FalSe'})
        assert (getField(data, 'key', bool, True) == False)

        data = dict({'key': 10})
        assert (getField(data, 'key', int, 20) == 10)

        data = dict({'key': "20"})
        assert (getField(data, 'key', int, 10) == 20)

        data = dict({'key': "abc"})
        assert (getField(data, 'key', int, 10) == 10)

        data = dict({'key': None})
        assert (getField(data, 'key', int, 10) == 10)

        assert (getField(None, 'key', int, 10) == 10)
        assert (getField(None, None, int, 10) == 10)

        data = dict({'key': '10'})
        assert (getField(data, 'key2', int, 20) == 20)

        data = dict({'key': '10'})
        assert (getField(data, 'key', dict, None) == None)

        data = dict({'key': {'subkey': 10}})
        assert (getField(data, 'key', dict, None).get('subkey') == 10)
예제 #8
0
    def __processConfigSections(self):
        '''!
         Process and execute individual configuration sections defined in ZTP JSON. Plugin for each
         configuration section is resolved and executed. Configuration section data is provided as
         command line argument to the plugin. Each and every section is processed before this function
         returns.

        '''

        # Obtain a copy of the list of configuration sections
        section_names = list(self.objztpJson.section_names)

        # set temporary flags
        abort = False
        sort = True

        logger.debug('Processing configuration sections: %s' %
                     ', '.join(section_names))
        # Loop through each sections till all of them are processed
        while section_names and abort is False:
            # Take a fresh sorted list to begin with and if any changes happen to it while processing
            if sort:
                sorted_list = sorted(section_names)
                sort = False
            # Loop through configuration section in a sorted order
            for sec in sorted_list:
                # Retrieve configuration section data
                section = self.objztpJson.ztpDict.get(sec)
                try:
                    # Retrieve individual section's progress
                    sec_status = section.get('status')
                    if sec_status == 'BOOT' or sec_status == 'SUSPEND':
                        # Mark section status as in progress
                        self.objztpJson.updateStatus(section, 'IN-PROGRESS')
                        if section.get('start-timestamp') is None:
                            section['start-timestamp'] = section['timestamp']
                            self.objztpJson.objJson.writeJson()
                        logger.info(
                            'Processing configuration section %s at %s.' %
                            (sec, section['timestamp']))
                    elif sec_status != 'IN-PROGRESS':
                        # Skip completed sections
                        logger.debug(
                            'Removing section %s from list. Status %s.' %
                            (sec, sec_status))
                        section_names.remove(sec)
                        # set flag to sort the configuration sections list again
                        sort = True
                        # Ignore disabled configuration sections
                        if sec_status == 'DISABLED':
                            logger.info(
                                'Configuration section %s skipped as its status is set to DISABLED.'
                                % sec)
                        continue
                    updateActivity('Processing configuration section %s' % sec)
                    # Get the appropriate plugin to be used for this configuration section
                    plugin = self.objztpJson.plugin(sec)
                    # Get the location of this configuration section's input data parsed from the input ZTP JSON file
                    plugin_input = getCfg(
                        'ztp-tmp-persistent') + '/' + sec + '/' + getCfg(
                            'section-input-file')
                    # Initialize result flag to FAILED
                    finalResult = 'FAILED'
                    rc = 1
                    # Check if plugin could not be resolved
                    if plugin is None:
                        logger.error(
                            'Unable to resolve plugin to be used for configuration section %s. Marking it as FAILED.'
                            % sec)
                        section[
                            'error'] = 'Unable to find or download requested plugin'
                    elif os.path.isfile(plugin) and os.path.isfile(
                            plugin_input):
                        plugin_args = self.objztpJson.pluginArgs(sec)
                        plugin_data = section.get('plugin')

                        # Determine if shell has to be used to execute plugin
                        _shell = getField(plugin_data, 'shell', bool, False)

                        # Construct the full plugin command string along with arguments
                        plugin_cmd = plugin
                        if plugin_args is not None:
                            plugin_cmd = plugin_cmd + ' ' + plugin_args

                        # A plugin has been resolved and its input configuration section data as well
                        logger.debug('Executing plugin %s.' % (plugin_cmd))
                        # Execute identified plugin
                        rc = runCommand(plugin_cmd,
                                        capture_stdout=False,
                                        use_shell=_shell)

                        logger.debug('Plugin %s exit code = %d.' %
                                     (plugin_cmd, rc))
                        # Compare plugin exit code
                        if rc == 0:
                            finalResult = 'SUCCESS'
                        elif section.get('suspend-exit-code'
                                         ) is not None and section.get(
                                             'suspend-exit-code') == rc:
                            finalResult = 'SUSPEND'
                        else:
                            finalResult = 'FAILED'
                except Exception as e:
                    logger.debug(
                        'Exception [%s] encountered for configuration section %s.'
                        % (str(e), sec))
                    logger.info(
                        'Exception encountered while processing configuration section %s. Marking it as FAILED.'
                        % sec)
                    section[
                        'error'] = 'Exception [%s] encountered while executing the plugin' % (
                            str(e))
                    finalResult = 'FAILED'

                # Update this configuration section's result in ztp json file
                logger.info(
                    'Processed Configuration section %s with result %s, exit code (%d) at %s.'
                    % (sec, finalResult, rc, section['timestamp']))
                if finalResult == 'FAILED' and section.get('error') is None:
                    section['error'] = 'Plugin failed'
                section['exit-code'] = rc
                self.objztpJson.updateStatus(section, finalResult)

                # Check if abort ZTP on failure flag is set
                if getField(section, 'halt-on-failure', bool,
                            False) is True and finalResult == 'FAILED':
                    logger.info(
                        'Halting ZTP as Configuration section %s FAILED and halt-on-failure flag is set.'
                        % sec)
                    abort = True
                    break

                # Check reboot on result flags
                self.__rebootAction(section)