Add standalone mode for IPA
This allows a developer to run IPA without an Ironic API. This can be useful for testing (especially functional testing) or testing integration of things like hardware managers. Change-Id: I2dc49fbe306430bf5b05a36fe56de5275fc128b2
This commit is contained in:
@@ -137,7 +137,7 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
||||
|
||||
def __init__(self, api_url, advertise_address, listen_address,
|
||||
ip_lookup_attempts, ip_lookup_sleep, network_interface,
|
||||
lookup_timeout, lookup_interval, driver_name):
|
||||
lookup_timeout, lookup_interval, driver_name, standalone):
|
||||
super(IronicPythonAgent, self).__init__()
|
||||
self.ext_mgr = extension.ExtensionManager(
|
||||
namespace='ironic_python_agent.extensions',
|
||||
@@ -166,6 +166,7 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
||||
self.ip_lookup_attempts = ip_lookup_attempts
|
||||
self.ip_lookup_sleep = ip_lookup_sleep
|
||||
self.network_interface = network_interface
|
||||
self.standalone = standalone
|
||||
|
||||
def get_status(self):
|
||||
"""Retrieve a serializable status.
|
||||
@@ -267,20 +268,22 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
||||
result_id)
|
||||
|
||||
def force_heartbeat(self):
|
||||
self.heartbeater.force_heartbeat()
|
||||
if not self.standalone:
|
||||
self.heartbeater.force_heartbeat()
|
||||
|
||||
def run(self):
|
||||
"""Run the Ironic Python Agent."""
|
||||
# Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
|
||||
# if there is an issue (uncaught, restart agent)
|
||||
self.started_at = _time()
|
||||
content = self.api_client.lookup_node(
|
||||
if not self.standalone:
|
||||
content = self.api_client.lookup_node(
|
||||
hardware_info=self.hardware.list_hardware_info(),
|
||||
timeout=self.lookup_timeout,
|
||||
starting_interval=self.lookup_interval)
|
||||
|
||||
self.node = content['node']
|
||||
self.heartbeat_timeout = content['heartbeat_timeout']
|
||||
self.node = content['node']
|
||||
self.heartbeat_timeout = content['heartbeat_timeout']
|
||||
|
||||
wsgi = simple_server.make_server(
|
||||
self.listen_address[0],
|
||||
@@ -288,12 +291,14 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
||||
self.api,
|
||||
server_class=simple_server.WSGIServer)
|
||||
|
||||
# Don't start heartbeating until the server is listening
|
||||
self.heartbeater.start()
|
||||
if not self.standalone:
|
||||
# Don't start heartbeating until the server is listening
|
||||
self.heartbeater.start()
|
||||
|
||||
try:
|
||||
wsgi.serve_forever()
|
||||
except BaseException:
|
||||
self.log.exception('shutting down')
|
||||
|
||||
self.heartbeater.stop()
|
||||
if not self.standalone:
|
||||
self.heartbeater.stop()
|
||||
|
@@ -128,8 +128,7 @@ APARAMS = _get_agent_params()
|
||||
|
||||
cli_opts = [
|
||||
cfg.StrOpt('api_url',
|
||||
required=('ipa-api-url' not in APARAMS),
|
||||
default=APARAMS.get('ipa-api-url'),
|
||||
default=APARAMS.get('ipa-api-url', 'http://127.0.0.1:6835'),
|
||||
deprecated_name='api-url',
|
||||
help='URL of the Ironic API'),
|
||||
|
||||
@@ -195,7 +194,12 @@ cli_opts = [
|
||||
|
||||
cfg.FloatOpt('lldp_timeout',
|
||||
default=APARAMS.get('lldp-timeout', 30.0),
|
||||
help='The amount of seconds to wait for LLDP packets.')
|
||||
help='The amount of seconds to wait for LLDP packets.'),
|
||||
|
||||
cfg.BoolOpt('standalone',
|
||||
default=False,
|
||||
help='Note: for debugging only. Start the Agent but suppress '
|
||||
'any calls to Ironic API.'),
|
||||
]
|
||||
|
||||
CONF.register_cli_opts(cli_opts)
|
||||
@@ -204,7 +208,6 @@ CONF.register_cli_opts(cli_opts)
|
||||
def run():
|
||||
CONF()
|
||||
log.setup('ironic-python-agent')
|
||||
|
||||
agent.IronicPythonAgent(CONF.api_url,
|
||||
(CONF.advertise_host, CONF.advertise_port),
|
||||
(CONF.listen_host, CONF.listen_port),
|
||||
@@ -213,4 +216,5 @@ def run():
|
||||
CONF.network_interface,
|
||||
CONF.lookup_timeout,
|
||||
CONF.lookup_interval,
|
||||
CONF.driver_name).run()
|
||||
CONF.driver_name,
|
||||
CONF.standalone).run()
|
||||
|
@@ -146,7 +146,8 @@ class TestBaseAgent(test_base.BaseTestCase):
|
||||
'eth0',
|
||||
300,
|
||||
1,
|
||||
'agent_ipmitool')
|
||||
'agent_ipmitool',
|
||||
False)
|
||||
self.agent.ext_mgr = extension.ExtensionManager.\
|
||||
make_test_instance([extension.Extension('fake', None,
|
||||
FakeExtension,
|
||||
@@ -210,7 +211,8 @@ class TestBaseAgent(test_base.BaseTestCase):
|
||||
None,
|
||||
300,
|
||||
1,
|
||||
'agent_ipmitool')
|
||||
'agent_ipmitool',
|
||||
False)
|
||||
|
||||
homeless_agent.hardware = mock.Mock()
|
||||
mock_list_net = homeless_agent.hardware.list_network_interfaces
|
||||
@@ -303,6 +305,50 @@ class TestBaseAgent(test_base.BaseTestCase):
|
||||
self.agent.get_node_uuid)
|
||||
|
||||
|
||||
class TestAgentStandalone(test_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAgentStandalone, self).setUp()
|
||||
self.agent = agent.IronicPythonAgent('https://fake_api.example.'
|
||||
'org:8081/',
|
||||
('203.0.113.1', 9990),
|
||||
('192.0.2.1', 9999),
|
||||
3,
|
||||
10,
|
||||
'eth0',
|
||||
300,
|
||||
1,
|
||||
'agent_ipmitool',
|
||||
True)
|
||||
|
||||
@mock.patch('wsgiref.simple_server.make_server', autospec=True)
|
||||
@mock.patch.object(hardware.HardwareManager, 'list_hardware_info')
|
||||
def test_run(self, mocked_list_hardware, wsgi_server_cls):
|
||||
wsgi_server = wsgi_server_cls.return_value
|
||||
wsgi_server.start.side_effect = KeyboardInterrupt()
|
||||
|
||||
self.agent.heartbeater = mock.Mock()
|
||||
self.agent.api_client.lookup_node = mock.Mock()
|
||||
self.agent.api_client.lookup_node.return_value = {
|
||||
'node': {
|
||||
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
||||
},
|
||||
'heartbeat_timeout': 300
|
||||
}
|
||||
self.agent.run()
|
||||
|
||||
listen_addr = ('192.0.2.1', 9999)
|
||||
wsgi_server_cls.assert_called_once_with(
|
||||
listen_addr[0],
|
||||
listen_addr[1],
|
||||
self.agent.api,
|
||||
server_class=simple_server.WSGIServer)
|
||||
wsgi_server.serve_forever.assert_called_once()
|
||||
|
||||
self.assertFalse(self.agent.heartbeater.called)
|
||||
self.assertFalse(self.agent.api_client.lookup_node.called)
|
||||
|
||||
|
||||
class TestAgentCmd(test_base.BaseTestCase):
|
||||
@mock.patch('ironic_python_agent.openstack.common.log.getLogger')
|
||||
@mock.patch(OPEN_FUNCTION_NAME)
|
||||
|
Reference in New Issue
Block a user