예제 #1
0
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)
예제 #2
0
 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()
예제 #3
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()
예제 #4
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()
예제 #5
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')
예제 #6
0
    def __processZTPJson(self):
        '''!
         Process ZTP JSON file downloaded using URL provided by DHCP Option 67, DHCPv6 Option 59 or
         local ZTP JSON file.

        '''
        logger.debug('Starting to process ZTP JSON file %s.' % self.json_src)
        updateActivity('Processing ZTP JSON file %s' % self.json_src)
        try:
            # Read provided ZTP JSON file and load it
            self.objztpJson = ZTPJson(self.json_src, getCfg('ztp-json'))
        except ValueError as e:
            logger.error(
                'Exception [%s] occured while processing ZTP JSON file %s.' %
                (str(e), self.json_src))
            logger.error('ZTP JSON file %s processing failed.' %
                         (self.json_src))
            try:
                os.remove(getCfg('ztp-json'))
                if os.path.isfile(getCfg('ztp-json-shadow')):
                    os.remove(getCfg('ztp-json-shadow'))
            except OSError as v:
                if v.errno != errno.ENOENT:
                    logger.warning(
                        'Exception [%s] encountered while deleting ZTP JSON file %s.'
                        % (str(v), getCfg('ztp-json')))
                    raise
            self.objztpJson = None
            # Restart networking after a wait time to discover new provisioning data
            if getCfg('restart-ztp-on-invalid-data'):
                return ("restart", "Invalid provisioning data processed")
            else:
                return ("stop", "Invalid provisioning data processed")

        if self.objztpJson['ztp-json-source'] is None:
            self.objztpJson['ztp-json-source'] = self.ztp_mode

        # Check if ZTP process has already completed. If not mark start of ZTP.
        if self.objztpJson['status'] == 'BOOT':
            self.objztpJson['status'] = 'IN-PROGRESS'
            if self.objztpJson['start-timestamp'] is None:
                self.objztpJson[
                    'start-timestamp'] = self.__ztp_engine_start_time
                self.objztpJson.objJson.writeJson()
        elif self.objztpJson['status'] != 'IN-PROGRESS':
            # Re-start ZTP if requested
            if getCfg('monitor-startup-config') is True and self.__ztp_restart:
                self.__ztp_restart = False
                # Discover new ZTP data after deleting historic ZTP data
                logger.info(
                    "ZTP restart requested. Deleting previous ZTP session JSON data."
                )
                os.remove(getCfg('ztp-json'))
                if os.path.isfile(getCfg('ztp-json-shadow')):
                    os.remove(getCfg('ztp-json-shadow'))
                self.objztpJson = None
                return ("retry", "ZTP restart requested")
            else:
                # ZTP was successfully completed in previous session. No need to proceed, return and exit service.
                logger.info(
                    "ZTP already completed with result %s at %s." %
                    (self.objztpJson['status'], self.objztpJson['timestamp']))
                return ("stop", "ZTP completed")

        logger.info('Starting ZTP using JSON file %s at %s.' %
                    (self.json_src, self.objztpJson['timestamp']))

        # Initialize connectivity if not done already
        self.__loadZTPProfile("resume")

        # Process available configuration sections in ZTP JSON
        self.__processConfigSections()

        # Determine ZTP result
        self.__evalZTPResult()

        # Check restart ZTP condition
        # ZTP result is failed and restart-ztp-on-failure is set  or
        _restart_ztp_on_failure = (self.objztpJson['status'] == 'FAILED' and \
                        self.objztpJson['restart-ztp-on-failure'] == True)

        # ZTP completed and no startup-config is found, restart-ztp-no-config and config-fallback is not set
        _restart_ztp_missing_config = ( (self.objztpJson['status'] == 'SUCCESS' or self.objztpJson['status'] == 'FAILED') and \
                           self.objztpJson['restart-ztp-no-config'] == True and \
                           self.objztpJson['config-fallback'] == False and
                           os.path.isfile(getCfg('config-db-json')) is False )

        # Mark ZTP for restart
        if _restart_ztp_missing_config or _restart_ztp_on_failure:
            os.remove(getCfg('ztp-json'))
            if os.path.isfile(getCfg('ztp-json-shadow')):
                os.remove(getCfg('ztp-json-shadow'))
            self.objztpJson = None
            # Remove startup-config file to obtain a new one through ZTP
            if getCfg('monitor-startup-config') is True and os.path.isfile(
                    getCfg('config-db-json')):
                os.remove(getCfg('config-db-json'))
            if _restart_ztp_missing_config:
                return (
                    "restart",
                    "ZTP completed but startup configuration '%s' not found" %
                    (getCfg('config-db-json')))
            elif _restart_ztp_on_failure:
                return ("restart", "ZTP completed with FAILED status")

        return ("stop", "ZTP completed")