2014-01-07 17:55:02 -08:00
|
|
|
"""
|
|
|
|
Copyright 2013 Rackspace, Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import json
|
|
|
|
import mock
|
|
|
|
import time
|
|
|
|
import unittest
|
|
|
|
|
2014-03-19 16:19:52 -07:00
|
|
|
from ironic_python_agent import errors
|
|
|
|
from ironic_python_agent import hardware
|
|
|
|
from ironic_python_agent import ironic_api_client
|
2014-03-26 10:57:25 -07:00
|
|
|
from ironic_python_agent.openstack.common import loopingcall
|
|
|
|
|
2014-01-07 17:55:02 -08:00
|
|
|
|
2014-03-19 16:19:52 -07:00
|
|
|
API_URL = 'http://agent-api.ironic.example.org/'
|
2014-01-07 17:55:02 -08:00
|
|
|
|
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
class FakeResponse(object):
|
|
|
|
def __init__(self, content=None, status_code=200, headers=None):
|
|
|
|
content = content or {}
|
|
|
|
self.content = json.dumps(content)
|
|
|
|
self.status_code = status_code
|
|
|
|
self.headers = headers or {}
|
|
|
|
|
|
|
|
|
2014-03-19 16:19:52 -07:00
|
|
|
class TestBaseIronicPythonAgent(unittest.TestCase):
|
2014-01-07 17:55:02 -08:00
|
|
|
def setUp(self):
|
2014-03-19 16:19:52 -07:00
|
|
|
self.api_client = ironic_api_client.APIClient(API_URL)
|
2014-01-28 11:25:36 -08:00
|
|
|
self.hardware_info = [
|
|
|
|
hardware.HardwareInfo(hardware.HardwareType.MAC_ADDRESS,
|
2014-03-19 13:55:56 -07:00
|
|
|
'aa:bb:cc:dd:ee:ff'),
|
2014-01-28 11:25:36 -08:00
|
|
|
hardware.HardwareInfo(hardware.HardwareType.MAC_ADDRESS,
|
2014-03-19 13:55:56 -07:00
|
|
|
'ff:ee:dd:cc:bb:aa'),
|
2014-01-28 11:25:36 -08:00
|
|
|
]
|
2014-01-07 17:55:02 -08:00
|
|
|
|
|
|
|
def test_successful_heartbeat(self):
|
|
|
|
expected_heartbeat_before = time.time() + 120
|
2014-03-26 10:57:25 -07:00
|
|
|
response = FakeResponse(status_code=204, headers={
|
2014-01-07 17:55:02 -08:00
|
|
|
'Heartbeat-Before': expected_heartbeat_before,
|
|
|
|
})
|
|
|
|
|
2014-01-08 14:32:44 -08:00
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
2014-01-07 17:55:02 -08:00
|
|
|
|
2014-03-19 11:14:45 -07:00
|
|
|
heartbeat_before = self.api_client.heartbeat(
|
2014-03-19 13:55:56 -07:00
|
|
|
uuid='deadbeef-dabb-ad00-b105-f00d00bab10c',
|
2014-03-19 14:01:49 -07:00
|
|
|
advertise_address=('192.0.2.1', '9999')
|
2014-03-19 11:14:45 -07:00
|
|
|
)
|
2014-01-07 17:55:02 -08:00
|
|
|
|
|
|
|
self.assertEqual(heartbeat_before, expected_heartbeat_before)
|
|
|
|
|
2014-03-19 13:55:56 -07:00
|
|
|
heartbeat_path = 'v1/nodes/deadbeef-dabb-ad00-b105-f00d00bab10c/' \
|
|
|
|
'vendor_passthru/heartbeat'
|
2014-01-08 14:32:44 -08:00
|
|
|
request_args = self.api_client.session.request.call_args[0]
|
2014-03-14 16:40:51 -07:00
|
|
|
self.assertEqual(request_args[0], 'POST')
|
2014-03-19 13:55:56 -07:00
|
|
|
self.assertEqual(request_args[1], API_URL + heartbeat_path)
|
2014-01-07 17:55:02 -08:00
|
|
|
|
2014-01-07 18:11:57 -08:00
|
|
|
def test_heartbeat_requests_exception(self):
|
2014-01-08 14:32:44 -08:00
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.side_effect = Exception('api is down!')
|
2014-01-07 18:11:57 -08:00
|
|
|
|
|
|
|
self.assertRaises(errors.HeartbeatError,
|
|
|
|
self.api_client.heartbeat,
|
2014-03-19 13:55:56 -07:00
|
|
|
uuid='deadbeef-dabb-ad00-b105-f00d00bab10c',
|
2014-03-19 14:01:49 -07:00
|
|
|
advertise_address=('192.0.2.1', '9999'))
|
2014-01-07 18:11:57 -08:00
|
|
|
|
|
|
|
def test_heartbeat_invalid_status_code(self):
|
2014-03-26 10:57:25 -07:00
|
|
|
response = FakeResponse(status_code=404)
|
2014-01-08 14:32:44 -08:00
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
2014-01-07 18:11:57 -08:00
|
|
|
|
|
|
|
self.assertRaises(errors.HeartbeatError,
|
|
|
|
self.api_client.heartbeat,
|
2014-03-19 13:55:56 -07:00
|
|
|
uuid='deadbeef-dabb-ad00-b105-f00d00bab10c',
|
2014-03-19 14:01:49 -07:00
|
|
|
advertise_address=('192.0.2.1', '9999'))
|
2014-01-07 18:11:57 -08:00
|
|
|
|
|
|
|
def test_heartbeat_missing_heartbeat_before_header(self):
|
2014-03-26 10:57:25 -07:00
|
|
|
response = FakeResponse(status_code=204)
|
2014-01-08 14:32:44 -08:00
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
2014-01-07 18:11:57 -08:00
|
|
|
|
|
|
|
self.assertRaises(errors.HeartbeatError,
|
|
|
|
self.api_client.heartbeat,
|
2014-03-19 13:55:56 -07:00
|
|
|
uuid='deadbeef-dabb-ad00-b105-f00d00bab10c',
|
2014-03-19 14:01:49 -07:00
|
|
|
advertise_address=('192.0.2.1', '9999'))
|
2014-01-07 18:11:57 -08:00
|
|
|
|
|
|
|
def test_heartbeat_invalid_heartbeat_before_header(self):
|
2014-03-26 10:57:25 -07:00
|
|
|
response = FakeResponse(status_code=204, headers={
|
2014-01-07 18:11:57 -08:00
|
|
|
'Heartbeat-Before': 'tomorrow',
|
|
|
|
})
|
2014-01-08 14:32:44 -08:00
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
2014-01-07 18:11:57 -08:00
|
|
|
|
|
|
|
self.assertRaises(errors.HeartbeatError,
|
|
|
|
self.api_client.heartbeat,
|
2014-03-19 13:55:56 -07:00
|
|
|
uuid='deadbeef-dabb-ad00-b105-f00d00bab10c',
|
2014-03-19 14:01:49 -07:00
|
|
|
advertise_address=('192.0.2.1', '9999'))
|
2014-03-14 16:40:51 -07:00
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
@mock.patch('eventlet.greenthread.sleep')
|
|
|
|
def test_lookup_node(self, sleep_mock):
|
|
|
|
content = {
|
|
|
|
'node': {
|
|
|
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
|
|
|
},
|
|
|
|
'heartbeat_timeout': 300
|
|
|
|
}
|
|
|
|
response = FakeResponse(status_code=200, content={
|
|
|
|
'node': {
|
|
|
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
|
|
|
},
|
|
|
|
'heartbeat_timeout': 300
|
|
|
|
})
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
|
|
|
returned_content = self.api_client.lookup_node(
|
|
|
|
hardware_info=self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
starting_interval=1)
|
|
|
|
|
|
|
|
self.assertEqual(content, returned_content)
|
|
|
|
|
|
|
|
@mock.patch('eventlet.greenthread.sleep')
|
|
|
|
def test_lookup_node_exception(self, sleep_mock):
|
|
|
|
bad_response = FakeResponse(status_code=500, content={
|
|
|
|
'node': {
|
|
|
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
|
|
|
},
|
|
|
|
'heartbeat_timeout': 300
|
|
|
|
})
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = bad_response
|
|
|
|
self.assertRaises(errors.LookupNodeError,
|
|
|
|
self.api_client.lookup_node,
|
|
|
|
hardware_info=self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
starting_interval=1)
|
|
|
|
|
|
|
|
def test_do_lookup(self):
|
|
|
|
response = FakeResponse(status_code=200, content={
|
2014-03-14 16:40:51 -07:00
|
|
|
'node': {
|
2014-03-19 13:55:56 -07:00
|
|
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
2014-03-20 15:18:48 -07:00
|
|
|
},
|
|
|
|
'heartbeat_timeout': 300
|
2014-03-14 16:40:51 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
self.assertRaises(loopingcall.LoopingCallDone,
|
|
|
|
self.api_client._do_lookup,
|
|
|
|
hardware_info=self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
intervals=[2])
|
2014-03-14 16:40:51 -07:00
|
|
|
|
|
|
|
request_args = self.api_client.session.request.call_args[0]
|
|
|
|
self.assertEqual(request_args[0], 'POST')
|
2014-03-27 12:25:11 -07:00
|
|
|
self.assertEqual(request_args[1],
|
2014-03-26 10:57:25 -07:00
|
|
|
API_URL + 'v1/drivers/teeth/vendor_passthru/lookup')
|
2014-03-14 16:40:51 -07:00
|
|
|
|
|
|
|
data = self.api_client.session.request.call_args[1]['data']
|
|
|
|
content = json.loads(data)
|
|
|
|
self.assertEqual(content['hardware'], [
|
|
|
|
{
|
|
|
|
'type': 'mac_address',
|
2014-03-19 13:55:56 -07:00
|
|
|
'id': 'aa:bb:cc:dd:ee:ff',
|
2014-03-14 16:40:51 -07:00
|
|
|
},
|
|
|
|
{
|
|
|
|
'type': 'mac_address',
|
2014-03-19 13:55:56 -07:00
|
|
|
'id': 'ff:ee:dd:cc:bb:aa',
|
2014-03-14 16:40:51 -07:00
|
|
|
},
|
|
|
|
])
|
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
def test_do_lookup_bad_response_code(self):
|
|
|
|
response = FakeResponse(status_code=400, content={
|
2014-03-14 16:40:51 -07:00
|
|
|
'node': {
|
2014-03-19 13:55:56 -07:00
|
|
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
2014-03-14 16:40:51 -07:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
total_time = [0]
|
|
|
|
interval = self.api_client._do_lookup(self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
total_time=total_time,
|
|
|
|
intervals=[2])
|
|
|
|
self.assertEqual(4, interval)
|
|
|
|
self.assertEqual(4, total_time[0])
|
2014-03-14 16:40:51 -07:00
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
def test_do_lookup_bad_response_data(self):
|
|
|
|
response = FakeResponse(status_code=200, content={
|
2014-03-20 15:18:48 -07:00
|
|
|
'heartbeat_timeout': 300
|
|
|
|
})
|
2014-03-14 16:40:51 -07:00
|
|
|
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
total_time = [0]
|
|
|
|
interval = self.api_client._do_lookup(self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
total_time=total_time,
|
|
|
|
intervals=[2])
|
|
|
|
self.assertEqual(4, interval)
|
|
|
|
self.assertEqual(4, total_time[0])
|
2014-03-20 15:18:48 -07:00
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
def test_do_lookup_no_heartbeat_timeout(self):
|
|
|
|
response = FakeResponse(status_code=200, content={
|
2014-03-20 15:18:48 -07:00
|
|
|
'node': {
|
|
|
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
total_time = [0]
|
|
|
|
interval = self.api_client._do_lookup(self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
total_time=total_time,
|
|
|
|
intervals=[2])
|
|
|
|
self.assertEqual(4, interval)
|
|
|
|
self.assertEqual(4, total_time[0])
|
2014-03-14 16:40:51 -07:00
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
def test_do_lookup_bad_response_body(self):
|
|
|
|
response = FakeResponse(status_code=200, content={
|
2014-03-14 16:40:51 -07:00
|
|
|
'node_node': 'also_not_node'
|
|
|
|
})
|
|
|
|
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = response
|
|
|
|
|
2014-03-26 10:57:25 -07:00
|
|
|
total_time = [0]
|
|
|
|
interval = self.api_client._do_lookup(self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
total_time=total_time,
|
|
|
|
intervals=[2])
|
|
|
|
self.assertEqual(4, interval)
|
|
|
|
self.assertEqual(4, total_time[0])
|
|
|
|
|
|
|
|
@mock.patch('eventlet.greenthread.sleep')
|
|
|
|
def test_lookup_node_exponential_backoff(self, sleep_mock):
|
|
|
|
bad_response = FakeResponse(status_code=500, content={
|
|
|
|
'node': {
|
|
|
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
|
|
|
},
|
|
|
|
'heartbeat_timeout': 300
|
|
|
|
})
|
|
|
|
self.api_client.session.request = mock.Mock()
|
|
|
|
self.api_client.session.request.return_value = bad_response
|
2014-03-18 10:39:47 -07:00
|
|
|
self.assertRaises(errors.LookupNodeError,
|
2014-03-18 10:36:33 -07:00
|
|
|
self.api_client.lookup_node,
|
2014-03-26 10:57:25 -07:00
|
|
|
hardware_info=self.hardware_info,
|
|
|
|
timeout=300,
|
|
|
|
starting_interval=1)
|
|
|
|
# 254 seconds before timeout
|
|
|
|
expected_times = [mock.call(2), mock.call(4), mock.call(8),
|
|
|
|
mock.call(16), mock.call(32), mock.call(64),
|
|
|
|
mock.call(128)]
|
|
|
|
self.assertEqual(expected_times, sleep_mock.call_args_list)
|