Using Ironic vendor passthru for heartbeat/config
This commit is contained in:
parent
6e127030ef
commit
fe4113fb5a
@ -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')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user