Using Ironic vendor passthru for heartbeat/config

This commit is contained in:
Josh Gachnang 2014-03-13 16:52:45 -07:00
parent 6e127030ef
commit fe4113fb5a
5 changed files with 60 additions and 15 deletions

@ -15,6 +15,7 @@ limitations under the License.
""" """
import random import random
import socket
import threading import threading
import time import time
@ -87,8 +88,9 @@ class TeethAgentHeartbeater(threading.Thread):
try: try:
deadline = self.api.heartbeat( deadline = self.api.heartbeat(
hardware_info=self.hardware.list_hardware_info(), hardware_info=self.hardware.list_hardware_info(),
mode=self.agent.get_mode_name(),
version=self.agent.version, version=self.agent.version,
mode=self.agent.get_mode_name()) uuid=self.agent.get_node_uuid())
self.error_delay = self.initial_delay self.error_delay = self.initial_delay
self.log.info('heartbeat successful') self.log.info('heartbeat successful')
except Exception as e: except Exception as e:
@ -109,6 +111,7 @@ class TeethAgentHeartbeater(threading.Thread):
class TeethAgent(object): class TeethAgent(object):
def __init__(self, api_url, listen_address): def __init__(self, api_url, listen_address):
self.api_url = api_url self.api_url = api_url
self.api_client = overlord_agent_api.APIClient(self.api_url)
self.listen_address = listen_address self.listen_address = listen_address
self.mode_implementation = None self.mode_implementation = None
self.version = pkg_resources.get_distribution('teeth-agent').version self.version = pkg_resources.get_distribution('teeth-agent').version
@ -119,6 +122,8 @@ class TeethAgent(object):
self.command_lock = threading.Lock() self.command_lock = threading.Lock()
self.log = structlog.get_logger() self.log = structlog.get_logger()
self.started_at = None self.started_at = None
self.configuration = None
self.content = None
def get_mode_name(self): def get_mode_name(self):
if self.mode_implementation: if self.mode_implementation:
@ -137,6 +142,12 @@ class TeethAgent(object):
def get_agent_mac_addr(self): def get_agent_mac_addr(self):
return self.hardware.get_primary_mac_address() 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): def list_command_results(self):
return self.command_results.values() return self.command_results.values()
@ -195,6 +206,9 @@ class TeethAgent(object):
def run(self): def run(self):
"""Run the Teeth Agent.""" """Run the Teeth Agent."""
self.started_at = time.time() 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() self.heartbeater.start()
server = wsgiserver.CherryPyWSGIServer(self.listen_address, self.api) server = wsgiserver.CherryPyWSGIServer(self.listen_address, self.api)

@ -71,6 +71,15 @@ class HeartbeatError(OverlordAPIError):
super(HeartbeatError, self).__init__(details) 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): class ImageDownloadError(errors.RESTError):
"""Error raised when an image cannot be downloaded.""" """Error raised when an image cannot be downloaded."""

@ -30,6 +30,7 @@ class APIClient(object):
self.session = requests.Session() self.session = requests.Session()
self.encoder = encoding.RESTJSONEncoder( self.encoder = encoding.RESTJSONEncoder(
encoding.SerializationViews.PUBLIC) encoding.SerializationViews.PUBLIC)
self.uuid = None
def _request(self, method, path, data=None): def _request(self, method, path, data=None):
request_url = '{api_url}{path}'.format(api_url=self.api_url, path=path) request_url = '{api_url}{path}'.format(api_url=self.api_url, path=path)
@ -47,13 +48,16 @@ class APIClient(object):
headers=request_headers, headers=request_headers,
data=data) data=data)
def heartbeat(self, hardware_info, mode, version): def heartbeat(self, hardware_info, mode, version, uuid):
path = '/{api_version}/agents'.format(api_version=self.api_version) path = '/{api_version}/nodes/{uuid}/vendor_passthru/heartbeat'.format(
api_version=self.api_version,
uuid=uuid
)
data = { data = {
'hardware': hardware_info, 'hardware': hardware_info,
'mode': mode, 'mode': mode,
'version': version, 'version': version,
'agent_url': self.api_url
} }
try: try:
@ -73,17 +77,27 @@ class APIClient(object):
raise errors.HeartbeatError('Invalid Heartbeat-Before header') raise errors.HeartbeatError('Invalid Heartbeat-Before header')
def get_configuration(self, mac_addr): def get_configuration(self, mac_addr):
path = '/{api_version}/agents/{mac_addr}/configuration'.format( path = '/{api_version}/drivers/teeth/lookup'.format(
api_version=self.api_version, api_version=self.api_version)
mac_addr=mac_addr) 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: if response.status_code != requests.codes.OK:
msg = 'Invalid status code: {0}'.format(response.status_code) msg = 'Invalid status code: {0}'.format(response.status_code)
raise errors.OverlordAPIError(msg) raise errors.OverlordAPIError(msg)
try: try:
return json.loads(response.content) content = json.loads(response.content)
except Exception as e: except Exception as e:
raise errors.OverlordAPIError('Error decoding response: ' + str(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

@ -162,6 +162,7 @@ class TestBaseAgent(unittest.TestCase):
wsgi_server.start.side_effect = KeyboardInterrupt() wsgi_server.start.side_effect = KeyboardInterrupt()
self.agent.heartbeater = mock.Mock() self.agent.heartbeater = mock.Mock()
self.agent.api_client.get_configuration = mock.Mock()
self.agent.run() self.agent.run()
listen_addr = ('localhost', 9999) listen_addr = ('localhost', 9999)

@ -49,13 +49,15 @@ class TestBaseTeethAgent(unittest.TestCase):
heartbeat_before = self.api_client.heartbeat( heartbeat_before = self.api_client.heartbeat(
hardware_info=self.hardware_info, hardware_info=self.hardware_info,
version='15', version='15',
mode='STANDBY') mode='STANDBY',
uuid='fake-uuid')
self.assertEqual(heartbeat_before, expected_heartbeat_before) self.assertEqual(heartbeat_before, expected_heartbeat_before)
request_args = self.api_client.session.request.call_args[0] request_args = self.api_client.session.request.call_args[0]
self.assertEqual(request_args[0], 'PUT') 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'] data = self.api_client.session.request.call_args[1]['data']
content = json.loads(data) content = json.loads(data)
@ -71,6 +73,7 @@ class TestBaseTeethAgent(unittest.TestCase):
'id': '0:1:2:3', 'id': '0:1:2:3',
}, },
]) ])
self.assertEqual(content['agent_url'], API_URL.rstrip('/'))
def test_heartbeat_requests_exception(self): def test_heartbeat_requests_exception(self):
self.api_client.session.request = mock.Mock() self.api_client.session.request = mock.Mock()
@ -80,7 +83,8 @@ class TestBaseTeethAgent(unittest.TestCase):
self.api_client.heartbeat, self.api_client.heartbeat,
hardware_info=self.hardware_info, hardware_info=self.hardware_info,
version='15', version='15',
mode='STANDBY') mode='STANDBY',
uuid='fake-uuid')
def test_heartbeat_invalid_status_code(self): def test_heartbeat_invalid_status_code(self):
response = httmock.response(status_code=404) response = httmock.response(status_code=404)
@ -91,7 +95,8 @@ class TestBaseTeethAgent(unittest.TestCase):
self.api_client.heartbeat, self.api_client.heartbeat,
hardware_info=self.hardware_info, hardware_info=self.hardware_info,
version='15', version='15',
mode='STANDBY') mode='STANDBY',
uuid='fake-uuid')
def test_heartbeat_missing_heartbeat_before_header(self): def test_heartbeat_missing_heartbeat_before_header(self):
response = httmock.response(status_code=204) response = httmock.response(status_code=204)
@ -102,7 +107,8 @@ class TestBaseTeethAgent(unittest.TestCase):
self.api_client.heartbeat, self.api_client.heartbeat,
hardware_info=self.hardware_info, hardware_info=self.hardware_info,
version='15', version='15',
mode='STANDBY') mode='STANDBY',
uuid='fake-uuid')
def test_heartbeat_invalid_heartbeat_before_header(self): def test_heartbeat_invalid_heartbeat_before_header(self):
response = httmock.response(status_code=204, headers={ response = httmock.response(status_code=204, headers={
@ -115,4 +121,5 @@ class TestBaseTeethAgent(unittest.TestCase):
self.api_client.heartbeat, self.api_client.heartbeat,
hardware_info=self.hardware_info, hardware_info=self.hardware_info,
version='15', version='15',
mode='STANDBY') mode='STANDBY',
uuid='fake-uuid')