def run(self): """Run the Ironic Python Agent.""" LOG.info('Starting ironic-python-agent version: %s', self.version) # Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError # if there is an issue (uncaught, restart agent) self.started_at = _time() # Attempt to sync the software clock utils.sync_clock(ignore_errors=True) # Cached hw managers at runtime, not load time. See bug 1490008. hardware.get_managers() # Operator-settable delay before hardware actually comes up. # Helps with slow RAID drivers - see bug 1582797. if self.hardware_initialization_delay > 0: LOG.info('Waiting %d seconds before proceeding', self.hardware_initialization_delay) time.sleep(self.hardware_initialization_delay) if not self.standalone: # Inspection should be started before call to lookup, otherwise # lookup will fail due to unknown MAC. uuid = None # We can't try to inspect or heartbeat until we have valid # interfaces to perform those actions over. self._wait_for_interface() if cfg.CONF.inspection_callback_url: try: # Attempt inspection. This may fail, and previously # an error would be logged. uuid = inspector.inspect() except errors.InspectionError as e: LOG.error('Failed to perform inspection: %s', e) if self.api_url: content = self.api_client.lookup_node( hardware_info=hardware.list_hardware_info(use_cache=True), timeout=self.lookup_timeout, starting_interval=self.lookup_interval, node_uuid=uuid) LOG.debug('Received lookup results: %s', content) self.process_lookup_data(content) # Save the API url in case we need it later. hardware.save_api_client(self.api_client, self.lookup_timeout, self.lookup_interval) elif cfg.CONF.inspection_callback_url: LOG.info('No ipa-api-url configured, Heartbeat and lookup ' 'skipped for inspector.') else: # NOTE(TheJulia): Once communication flow capability is # able to be driven solely from the conductor, this is no # longer a major issue. LOG.error('Neither ipa-api-url nor inspection_callback_url' 'found, please check your pxe append parameters.') self.serve_ipa_api() if not self.standalone and self.api_url: self.heartbeater.stop()
def test_sync_clock_chrony(self, mock_time_method, mock_execute): self.config(ntp_server='192.168.1.1') mock_time_method.return_value = 'chronyd' utils.sync_clock() mock_execute.assert_has_calls([ mock.call('chronyd', check_exit_code=[0, 1]), mock.call('chronyc', 'add', 'server', '192.168.1.1'), mock.call('chronyc', 'makestep'), ])
def test_sync_clock_chrony_already_present(self, mock_time_method, mock_execute): self.config(ntp_server='192.168.1.1') mock_time_method.return_value = 'chronyd' mock_execute.side_effect = [ ('', ''), processutils.ProcessExecutionError( stderr='Source already present'), ('', ''), ] utils.sync_clock() mock_execute.assert_has_calls([ mock.call('chronyd', check_exit_code=[0, 1]), mock.call('chronyc', 'add', 'server', '192.168.1.1'), mock.call('chronyc', 'makestep'), ])
def _sync_clock(self, ignore_errors=False): """Sync the clock to a configured NTP server. :param ignore_errors: Boolean option to indicate if the errors should be fatal. This option does not override the fail_if_clock_not_set configuration option. :raises: ClockSyncError if a failure is encountered and errors are not ignored. """ try: utils.sync_clock(ignore_errors=ignore_errors) # Sync the system hardware clock from the software clock, # as they are independent and the HW clock can still drift # with long running ramdisks. utils.execute('hwclock', '-v', '--systohc') except (processutils.ProcessExecutionError, errors.CommandExecutionError) as e: msg = 'Failed to sync hardware clock: %s' % e LOG.error(msg) if CONF.fail_if_clock_not_set or not ignore_errors: raise errors.ClockSyncError(msg)
def run(self): """Run the Ironic Python Agent.""" LOG.info('Starting ironic-python-agent version: %s', self.version) # Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError # if there is an issue (uncaught, restart agent) self.started_at = _time() # Attempt to sync the software clock utils.sync_clock(ignore_errors=True) # Cached hw managers at runtime, not load time. See bug 1490008. hardware.get_managers() # Operator-settable delay before hardware actually comes up. # Helps with slow RAID drivers - see bug 1582797. if self.hardware_initialization_delay > 0: LOG.info('Waiting %d seconds before proceeding', self.hardware_initialization_delay) time.sleep(self.hardware_initialization_delay) if not self.standalone: # Inspection should be started before call to lookup, otherwise # lookup will fail due to unknown MAC. uuid = None if cfg.CONF.inspection_callback_url: try: # Attempt inspection. This may fail, and previously # an error would be logged. uuid = inspector.inspect() except errors.InspectionError as e: LOG.error('Failed to perform inspection: %s', e) if self.api_url: self._wait_for_interface() content = self.api_client.lookup_node( hardware_info=hardware.dispatch_to_managers( 'list_hardware_info'), timeout=self.lookup_timeout, starting_interval=self.lookup_interval, node_uuid=uuid) LOG.debug('Received lookup results: %s', content) self.node = content['node'] LOG.info('Lookup succeeded, node UUID is %s', self.node['uuid']) hardware.cache_node(self.node) self.heartbeat_timeout = content['config']['heartbeat_timeout'] # Update config with values from Ironic config = content.get('config', {}) if config.get('metrics'): for opt, val in config.items(): setattr(cfg.CONF.metrics, opt, val) if config.get('metrics_statsd'): for opt, val in config.items(): setattr(cfg.CONF.metrics_statsd, opt, val) if config.get('agent_token_required'): self.agent_token_required = True token = config.get('agent_token') if token: if len(token) >= 32: LOG.debug('Agent token recorded as designated by ' 'the ironic installation.') self.agent_token = token # set with-in the API client. self.api_client.agent_token = token elif token == '******': LOG.warning('The agent token has already been ' 'retrieved. IPA may not operate as ' 'intended and the deployment may fail ' 'depending on settings in the ironic ' 'deployment.') if not self.agent_token and self.agent_token_required: LOG.error('Ironic is signaling that agent tokens ' 'are required, however we do not have ' 'a token on file. ' 'This is likely **FATAL**.') else: LOG.info('An invalid token was received.') if self.agent_token: # Explicitly set the token in our API client before # starting heartbeat operations. self.api_client.agent_token = self.agent_token elif cfg.CONF.inspection_callback_url: LOG.info('No ipa-api-url configured, Heartbeat and lookup ' 'skipped for inspector.') else: LOG.error('Neither ipa-api-url nor inspection_callback_url' 'found, please check your pxe append parameters.') self.serve_ipa_api() if not self.standalone and self.api_url: self.heartbeater.stop()
def test_sync_clock_ntp_server_is_none(self, mock_time_method, mock_execute): self.config(ntp_server=None) mock_time_method.return_value = None utils.sync_clock() self.assertEqual(0, mock_execute.call_count)
def test_sync_clock_none(self, mock_time_method, mock_execute): self.config(ntp_server='192.168.1.1') mock_time_method.return_value = None utils.sync_clock(ignore_errors=True) self.assertEqual(0, mock_execute.call_count)
def test_sync_clock_ntp(self, mock_time_method, mock_execute): self.config(ntp_server='192.168.1.1') mock_time_method.return_value = 'ntpdate' utils.sync_clock() mock_execute.assert_has_calls([mock.call('ntpdate', '192.168.1.1')])