Пример #1
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)
Пример #2
0
    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)
Пример #3
0
    def test_json_config_valid_load(self, tmpdir):
        '''!
        Test case when the new json configuration file to apply is syntaxically valid.
        Verify that the new configuration is correctly applied using 'config load'.
        Verify that the plugin does not return with a non zero exit code.
        '''

        d = tmpdir.mkdir("valid")
        fh_before = d.join("config-before.json")
        cmd = 'config save -y ' + str(fh_before)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)

        data = self.__read_file(str(fh_before))
        fh_after = d.join("config-after.json")
        self.__write_file(str(fh_after), data)

        cmd = "/bin/sed -i -e 's/\"hostname\": \".*\"/\"hostname\": \"something\"/' " + str(
            fh_after)
        rc = runCommand(cmd)

        fh = d.join("input.json")
        fh.write("""
        {
            "configdb-json": {
                "clear-config": false,
                "halt-on-failure": false,
                "ignore-result": false,
                "reboot-on-failure": false,
                "reboot-on-success": false,
                "status": "BOOT",
                "timestamp": "2019-05-01 19:49:25",
                "url": {
                    "destination": "/etc/sonic/config_db.json",
                    "source": "file://%s"
                }
            }
        }
        """ % (str(fh_after)))
        configdb_json = ConfigDBJson(str(fh))
        configdb_json.main()

        fh_after = d.join("config-after.json")
        cmd = 'config save -y ' + str(fh_after)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)

        # Collect the differences between the two configurations
        cmd = "/usr/bin/diff --changed-group-format='%>' --unchanged-group-format='' " + str(
            fh_before) + ' ' + str(fh_after)
        (rc2, cmd_stdout, cmd_stderr) = runCommand(cmd)

        # Restore initial configuration
        cmd = 'config load -y ' + str(fh_before)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)
Пример #4
0
    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
Пример #5
0
 def test_runCommand(self):
     '''!
     Test the runCommand function
     '''
     with pytest.raises(TypeError):
         (rc, cmd_stdout, cmd_stderr) = runCommand([12])
     (rc, cmd_stdout, cmd_stderr) = runCommand('/abc/xyz/foo')
     assert ((rc, cmd_stdout, cmd_stderr) == (1, None, None))
     (rc, cmd_stdout, cmd_stderr) = runCommand('ps FOO')
     assert (rc == 1)
     (rc, cmd_stdout, cmd_stderr) = runCommand('[12]')
Пример #6
0
    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
Пример #7
0
    def __read_sys_eeprom(self, option):
        '''!
        Helper function to read a specific section from the eeprom.

        @param option (str) Option given to decode_syseeprom command line utility
        '''
        cmd = 'decode-syseeprom ' + option
        (rc, cmd_stdout, cmd_stderr) = runCommand(cmd)
        if not rc == 0 or len(cmd_stdout) != 1:
            return 'N.A'
        else:
            return printable(cmd_stdout[0].rstrip())
Пример #8
0
    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
Пример #9
0
    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
Пример #10
0
    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)
Пример #11
0
    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,
         cmd_stderr4) = runCommand(['/bin/cat', '/proc/devices'],
                                   use_shell=True)
        assert ((rc1 == rc2) and (rc2 == rc3) and (rc3 == rc4))
        assert ((cmd_stdout1 == cmd_stdout2) and (cmd_stdout2 == cmd_stdout3)
                and (cmd_stdout3 == cmd_stdout4))
        assert ((cmd_stderr1 == cmd_stderr2) and (cmd_stderr2 == cmd_stderr3)
                and (cmd_stderr3 == cmd_stderr4))

        (rc1, cmd_stdout1, cmd_stderr1) = runCommand('ps hjk')
        (rc2, cmd_stdout2, cmd_stderr2) = runCommand('ps hjk', use_shell=True)
        (rc3, cmd_stdout3, cmd_stderr3) = runCommand(['ps', 'hjk'])
        (rc4, cmd_stdout4, cmd_stderr4) = runCommand(['ps', 'hjk'],
                                                     use_shell=True)
        assert ((rc1 == rc2) and (rc2 == rc3) and (rc3 == rc4))
        assert ((cmd_stdout1 == cmd_stdout2) and (cmd_stdout2 == cmd_stdout3)
                and (cmd_stdout3 == cmd_stdout4))
        assert ((cmd_stderr1 == cmd_stderr2) and (cmd_stderr2 == cmd_stderr3)
                and (cmd_stderr3 == cmd_stderr4))

        d = tmpdir.mkdir("valid")
        fh = d.join("test.sh")
        fh.write("""#!/bin/sh

        echo $1
        echo $2 > /dev/stderr
        exit $#
        """)
        os.chmod(str(fh), stat.S_IRWXU)
        (rc1, cmd_stdout1, cmd_stderr1) = runCommand(str(fh) + ' ABC XYZ')
        (rc2, cmd_stdout2, cmd_stderr2) = runCommand(str(fh) + ' ABC XYZ',
                                                     use_shell=True)
        (rc3, cmd_stdout3, cmd_stderr3) = runCommand([str(fh), 'ABC', 'XYZ'])
        (rc4, cmd_stdout4, cmd_stderr4) = runCommand([str(fh), 'ABC', 'XYZ'],
                                                     use_shell=True)
        assert ((rc1 == rc2) and (rc2 == rc3) and (rc3 == rc4))
        assert ((cmd_stdout1 == cmd_stdout2) and (cmd_stdout2 == cmd_stdout3)
                and (cmd_stdout3 == cmd_stdout4))
        assert ((cmd_stderr1 == cmd_stderr2) and (cmd_stderr2 == cmd_stderr3)
                and (cmd_stderr3 == cmd_stderr4))
Пример #12
0
    def executeLoop(self, test_mode=False):
        '''!
         ZTP service loop which peforms provisioning data discovery and initiates processing.
        '''

        updateActivity('Initializing')

        # Set testing mode
        self.test_mode = test_mode

        # Check if ZTP is disabled administratively, bail out if disabled
        if getCfg('admin-mode') is False:
            logger.info('ZTP is administratively disabled.')
            self.__removeZTPProfile()
            return

        # Check if ZTP data restart flag is set
        if os.path.isfile(getCfg('ztp-restart-flag')):
            self.__ztp_restart = True
            os.remove(getCfg('ztp-restart-flag'))

        if self.test_mode:
            logger.warning(
                'ZTP service started in test mode with restricted functionality.'
            )
        else:
            logger.info('ZTP service started.')

        self.__ztp_engine_start_time = getTimestamp()
        _start_time = None
        self.ztp_mode = 'DISCOVERY'
        # Main provisioning data discovery loop
        while self.ztp_mode == 'DISCOVERY':
            updateActivity('Discovering provisioning data', overwrite=False)
            try:
                result = self.__discover()
            except Exception as e:
                logger.error(
                    "Exception [%s] encountered while running the discovery logic."
                    % (str(e)))
                _exc_type, _exc_value, _exc_traceback = sys.exc_info()
                __tb = traceback.extract_tb(_exc_traceback)
                for l in __tb:
                    logger.debug('  File ' + l[0] + ', line ' + str(l[1]) +
                                 ', in ' + str(l[2]))
                    logger.debug('    ' + str(l[3]))
                self.__forceRestartDiscovery(
                    "Invalid provisioning data received")
                continue

            if result:
                if self.ztp_mode == 'MANUAL_CONFIG':
                    logger.info(
                        "Configuration file '%s' detected. Shutting down ZTP service."
                        % (getCfg('config-db-json')))
                    break
                elif self.ztp_mode != 'DISCOVERY':
                    (rv, msg) = self.__processZTPJson()
                    if rv == "retry":
                        self.ztp_mode = 'DISCOVERY'
                    elif rv == "restart":
                        self.__forceRestartDiscovery(msg)
                    else:
                        break

            # Initialize in-band interfaces to establish connectivity if not done already
            self.__loadZTPProfile("discovery")
            logger.debug('Provisioning data not found.')

            # Scan for inband interfaces to link up and restart interface connectivity
            if self.__link_scan():
                updateActivity('Restarting network discovery after link scan')
                logger.info('Restarting network discovery after link scan.')
                runCommand('systemctl restart interfaces-config',
                           capture_stdout=False)
                logger.info('Restarted network discovery after link scan.')
                _start_time = time.time()
                continue

            # Start keeping time of last time restart networking was done
            if _start_time is None:
                _start_time = time.time()

            # Check if we have to restart networking
            if (time.time() - _start_time > getCfg('restart-ztp-interval')):
                updateActivity('Restarting network discovery')
                if self.test_mode is False:
                    # Remove existing leases to source new provisioning data
                    self.__cleanup_dhcp_leases()
                    logger.info('Restarting network discovery.')
                    runCommand('systemctl restart interfaces-config',
                               capture_stdout=False)
                    logger.info('Restarted network discovery.')
                _start_time = time.time()
                continue

            # Try after sometime
            time.sleep(getCfg('discovery-interval'))

        # Cleanup installed ZTP configuration profile
        self.__removeZTPProfile()
        if self.reboot_on_completion and self.test_mode == False:
            updateActivity('System reboot requested')
            systemReboot()
        updateActivity('Exiting ZTP server')
Пример #13
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)
Пример #14
0
    def test_json_config_valid_reload(self, tmpdir):
        '''!
        Test case when the new json configuration file to apply is syntaxically valid.
        Verify that the new configuration is correctly applied using 'config reload'.
        Verify that the plugin does not return with a non zero exit code.
        '''

        d = tmpdir.mkdir("valid")
        fh_before = d.join("config-before.json")
        cmd = 'config save -y ' + str(fh_before)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)
        cmd = 'sonic-cfggen -d -H -v DEVICE_METADATA.localhost.platform'
        (rc, cmd_stdout, cmd_stderr) = runCommand(cmd)
        assert (rc == 0)
        platform = cmd_stdout[0]
        cmd = 'sonic-cfggen -d -H -v DEVICE_METADATA.localhost.hwsku'
        (rc, cmd_stdout, cmd_stderr) = runCommand(cmd)
        assert (rc == 0)
        hwsku = cmd_stdout[0]
        sku_dir = '/usr/share/sonic/device/{}/{}'.format(platform, hwsku)
        os.system('rm -rf {}'.format(sku_dir + '_dup'))
        os.system('cp -R {} {}'.format(sku_dir, sku_dir + '_dup'))

        data = self.__read_file(str(fh_before))
        fh_after = d.join("config-after.json")
        self.__write_file(str(fh_after), data)

        cfgDict = {}
        with open(str(fh_after)) as fp:
            cfgDict = json.load(fp)
            cfgDict['DEVICE_METADATA']['localhost']['hostname'] = "something"
            cfgDict['DEVICE_METADATA']['localhost']['platform'] = "invalid1"
            cfgDict['DEVICE_METADATA']['localhost']['mac'] = "invalid2"
            cfgDict['DEVICE_METADATA']['localhost']['hwsku'] = hwsku + "_dup"
        with open(str(fh_after), "w") as fp:
            json.dump(cfgDict, fp, indent=4)

        fh = d.join("input.json")
        fh.write("""
        {
            "configdb-json": {
                "clear-config": true,
                "halt-on-failure": false,
                "ignore-result": false,
                "reboot-on-failure": false,
                "reboot-on-success": false,
                "status": "BOOT",
                "timestamp": "2019-05-01 19:49:25",
                "url": {
                    "destination": "/etc/sonic/config_db.json",
                    "source": "file://%s"
                }
            }
        }
        """ % (str(fh_after)))
        configdb_json = ConfigDBJson(str(fh))
        configdb_json.main()

        fh_after = d.join("config-after.json")
        cmd = 'config save -y ' + str(fh_after)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)

        with open(str(fh_after)) as json_file:
            json_dict = json.load(json_file)
            assert (json_dict.get('DEVICE_METADATA').get('localhost').get(
                'platform') != 'invalid1')
            assert (
                json_dict.get('DEVICE_METADATA').get('localhost').get('mac') !=
                'invalid2')
            assert (json_dict.get('DEVICE_METADATA').get('localhost').get(
                'hwsku') == hwsku + "_dup")

        # Restore initial configuration
        cmd = 'config reload -y -f ' + str(fh_before)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)
        rc = runCommand('config save -y', capture_stdout=False)
Пример #15
0
    def test_json_config_valid_load(self, tmpdir):
        '''!
        Test case when the new json configuration file to apply is syntaxically valid.
        Verify that the new configuration is correctly applied using 'config load'.
        Verify that the plugin does not return with a non zero exit code.
        '''

        d = tmpdir.mkdir("valid")
        fh_before = d.join("config-before.json")
        cmd = 'config save -y ' + str(fh_before)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)

        data = self.__read_file(str(fh_before))
        fh_after = d.join("config-after.json")
        self.__write_file(str(fh_after), data)

        cfgDict = {}
        with open(str(fh_after)) as fp:
            cfgDict = json.load(fp)
            cfgDict['DEVICE_METADATA']['localhost']['hostname'] = "something"
            cfgDict['DEVICE_METADATA']['localhost']['platform'] = "invalid1"
            cfgDict['DEVICE_METADATA']['localhost']['mac'] = "invalid2"
            cfgDict['DEVICE_METADATA']['localhost']['hwsku'] = "invalid3"
        with open(str(fh_after), "w") as fp:
            json.dump(cfgDict, fp, indent=4)

        fh = d.join("input.json")
        fh.write("""
        {
            "configdb-json": {
                "clear-config": false,
                "halt-on-failure": false,
                "ignore-result": false,
                "reboot-on-failure": false,
                "reboot-on-success": false,
                "status": "BOOT",
                "timestamp": "2019-05-01 19:49:25",
                "url": {
                    "destination": "/etc/sonic/config_db.json",
                    "source": "file://%s"
                }
            }
        }
        """ % (str(fh_after)))
        configdb_json = ConfigDBJson(str(fh))
        configdb_json.main()

        fh_after = d.join("config-after.json")
        cmd = 'config save -y ' + str(fh_after)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)

        with open(str(fh_after)) as json_file:
            json_dict = json.load(json_file)
            assert (json_dict.get('DEVICE_METADATA').get('localhost').get(
                'platform') != 'invalid1')
            assert (
                json_dict.get('DEVICE_METADATA').get('localhost').get('mac') !=
                'invalid2')
            assert (
                json_dict.get('DEVICE_METADATA').get('localhost').get('hwsku')
                != 'invalid3')

        # Collect the differences between the two configurations
        cmd = "/usr/bin/diff --changed-group-format='%>' --unchanged-group-format='' " + str(
            fh_before) + ' ' + str(fh_after)
        (rc2, cmd_stdout, cmd_stderr) = runCommand(cmd)

        # Restore initial configuration
        cmd = 'config load -y ' + str(fh_before)
        rc = runCommand(cmd, capture_stdout=False)
        assert (rc == 0)