diff --git a/teeth_agent/agent.py b/teeth_agent/agent.py index b14863941..f67c52e1b 100644 --- a/teeth_agent/agent.py +++ b/teeth_agent/agent.py @@ -15,6 +15,7 @@ limitations under the License. """ import random +import socket import threading import time @@ -87,8 +88,9 @@ class TeethAgentHeartbeater(threading.Thread): try: deadline = self.api.heartbeat( hardware_info=self.hardware.list_hardware_info(), + mode=self.agent.get_mode_name(), version=self.agent.version, - mode=self.agent.get_mode_name()) + uuid=self.agent.get_node_uuid()) self.error_delay = self.initial_delay self.log.info('heartbeat successful') except Exception as e: @@ -109,6 +111,7 @@ class TeethAgentHeartbeater(threading.Thread): class TeethAgent(object): def __init__(self, api_url, listen_address): self.api_url = api_url + self.api_client = overlord_agent_api.APIClient(self.api_url) self.listen_address = listen_address self.mode_implementation = None self.version = pkg_resources.get_distribution('teeth-agent').version @@ -119,6 +122,8 @@ class TeethAgent(object): self.command_lock = threading.Lock() self.log = structlog.get_logger() self.started_at = None + self.configuration = None + self.content = None def get_mode_name(self): if self.mode_implementation: @@ -137,6 +142,12 @@ class TeethAgent(object): def get_agent_mac_addr(self): return self.hardware.get_primary_mac_address() + def get_all_mac_addrs(self): + return self.hardware.list_hardware_info() + + def get_node_uuid(self): + return self.content['node']['uuid'] + def list_command_results(self): return self.command_results.values() @@ -195,6 +206,9 @@ class TeethAgent(object): def run(self): """Run the Teeth Agent.""" self.started_at = time.time() + # Get the UUID so we can heartbeat to Ironic + mac_addresses = self.get_all_mac_addrs() + self.configuration = self.api_client.get_configuration(mac_addresses) self.heartbeater.start() server = wsgiserver.CherryPyWSGIServer(self.listen_address, self.api) diff --git a/teeth_agent/errors.py b/teeth_agent/errors.py index 3d0f870b5..c8731e2f6 100644 --- a/teeth_agent/errors.py +++ b/teeth_agent/errors.py @@ -71,6 +71,15 @@ class HeartbeatError(OverlordAPIError): super(HeartbeatError, self).__init__(details) +class ConfigurationError(OverlordAPIError): + """Error raised when the configuration lookup to the agent API fails.""" + + message = 'Error getting configuration from agent API.' + + def __init__(self, details): + super(ConfigurationError, self).__init__(details) + + class ImageDownloadError(errors.RESTError): """Error raised when an image cannot be downloaded.""" diff --git a/teeth_agent/overlord_agent_api.py b/teeth_agent/overlord_agent_api.py index 1ef250a32..30bbd3a0d 100644 --- a/teeth_agent/overlord_agent_api.py +++ b/teeth_agent/overlord_agent_api.py @@ -30,6 +30,7 @@ class APIClient(object): self.session = requests.Session() self.encoder = encoding.RESTJSONEncoder( encoding.SerializationViews.PUBLIC) + self.uuid = None def _request(self, method, path, data=None): request_url = '{api_url}{path}'.format(api_url=self.api_url, path=path) @@ -47,13 +48,16 @@ class APIClient(object): headers=request_headers, data=data) - def heartbeat(self, hardware_info, mode, version): - path = '/{api_version}/agents'.format(api_version=self.api_version) - + def heartbeat(self, hardware_info, mode, version, uuid): + path = '/{api_version}/nodes/{uuid}/vendor_passthru/heartbeat'.format( + api_version=self.api_version, + uuid=uuid + ) data = { 'hardware': hardware_info, 'mode': mode, 'version': version, + 'agent_url': self.api_url } try: @@ -73,17 +77,27 @@ class APIClient(object): raise errors.HeartbeatError('Invalid Heartbeat-Before header') def get_configuration(self, mac_addr): - path = '/{api_version}/agents/{mac_addr}/configuration'.format( - api_version=self.api_version, - mac_addr=mac_addr) + path = '/{api_version}/drivers/teeth/lookup'.format( + api_version=self.api_version) + data = { + 'mac_addresses': [mac_addr] + } - response = self._request('GET', path) + try: + response = self._request('POST', path, data=data) + except Exception as e: + raise errors.ConfigurationError(str(e)) if response.status_code != requests.codes.OK: msg = 'Invalid status code: {0}'.format(response.status_code) raise errors.OverlordAPIError(msg) try: - return json.loads(response.content) + content = json.loads(response.content) except Exception as e: raise errors.OverlordAPIError('Error decoding response: ' + str(e)) + + if 'node' not in content or 'uuid' not in content['node']: + raise errors.OverlordAPIError('Got invalid data from the API: ' + + content) + return content \ No newline at end of file diff --git a/teeth_agent/tests/agent.py b/teeth_agent/tests/agent.py index f1d4ed20c..1a40299bb 100644 --- a/teeth_agent/tests/agent.py +++ b/teeth_agent/tests/agent.py @@ -162,6 +162,7 @@ class TestBaseAgent(unittest.TestCase): wsgi_server.start.side_effect = KeyboardInterrupt() self.agent.heartbeater = mock.Mock() + self.agent.api_client.get_configuration = mock.Mock() self.agent.run() listen_addr = ('localhost', 9999) diff --git a/teeth_agent/tests/overlord_agent_api.py b/teeth_agent/tests/overlord_agent_api.py index 3abbc9675..74ce06e80 100644 --- a/teeth_agent/tests/overlord_agent_api.py +++ b/teeth_agent/tests/overlord_agent_api.py @@ -49,13 +49,15 @@ class TestBaseTeethAgent(unittest.TestCase): heartbeat_before = self.api_client.heartbeat( hardware_info=self.hardware_info, version='15', - mode='STANDBY') + mode='STANDBY', + uuid='fake-uuid') self.assertEqual(heartbeat_before, expected_heartbeat_before) request_args = self.api_client.session.request.call_args[0] self.assertEqual(request_args[0], 'PUT') - self.assertEqual(request_args[1], API_URL + 'v1/agents') + self.assertEqual(request_args[1], API_URL + 'v1/nodes/fake-uuid/vendor' + '_passthru/heartbeat') data = self.api_client.session.request.call_args[1]['data'] content = json.loads(data) @@ -71,6 +73,7 @@ class TestBaseTeethAgent(unittest.TestCase): 'id': '0:1:2:3', }, ]) + self.assertEqual(content['agent_url'], API_URL.rstrip('/')) def test_heartbeat_requests_exception(self): self.api_client.session.request = mock.Mock() @@ -80,7 +83,8 @@ class TestBaseTeethAgent(unittest.TestCase): self.api_client.heartbeat, hardware_info=self.hardware_info, version='15', - mode='STANDBY') + mode='STANDBY', + uuid='fake-uuid') def test_heartbeat_invalid_status_code(self): response = httmock.response(status_code=404) @@ -91,7 +95,8 @@ class TestBaseTeethAgent(unittest.TestCase): self.api_client.heartbeat, hardware_info=self.hardware_info, version='15', - mode='STANDBY') + mode='STANDBY', + uuid='fake-uuid') def test_heartbeat_missing_heartbeat_before_header(self): response = httmock.response(status_code=204) @@ -102,7 +107,8 @@ class TestBaseTeethAgent(unittest.TestCase): self.api_client.heartbeat, hardware_info=self.hardware_info, version='15', - mode='STANDBY') + mode='STANDBY', + uuid='fake-uuid') def test_heartbeat_invalid_heartbeat_before_header(self): response = httmock.response(status_code=204, headers={ @@ -115,4 +121,5 @@ class TestBaseTeethAgent(unittest.TestCase): self.api_client.heartbeat, hardware_info=self.hardware_info, version='15', - mode='STANDBY') + mode='STANDBY', + uuid='fake-uuid')