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,
|
def __init__(self, api_url, advertise_address, listen_address,
|
||||||
ip_lookup_attempts, ip_lookup_sleep, network_interface,
|
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__()
|
super(IronicPythonAgent, self).__init__()
|
||||||
self.ext_mgr = extension.ExtensionManager(
|
self.ext_mgr = extension.ExtensionManager(
|
||||||
namespace='ironic_python_agent.extensions',
|
namespace='ironic_python_agent.extensions',
|
||||||
@@ -166,6 +166,7 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
|||||||
self.ip_lookup_attempts = ip_lookup_attempts
|
self.ip_lookup_attempts = ip_lookup_attempts
|
||||||
self.ip_lookup_sleep = ip_lookup_sleep
|
self.ip_lookup_sleep = ip_lookup_sleep
|
||||||
self.network_interface = network_interface
|
self.network_interface = network_interface
|
||||||
|
self.standalone = standalone
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
"""Retrieve a serializable status.
|
"""Retrieve a serializable status.
|
||||||
@@ -267,20 +268,22 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
|||||||
result_id)
|
result_id)
|
||||||
|
|
||||||
def force_heartbeat(self):
|
def force_heartbeat(self):
|
||||||
self.heartbeater.force_heartbeat()
|
if not self.standalone:
|
||||||
|
self.heartbeater.force_heartbeat()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Run the Ironic Python Agent."""
|
"""Run the Ironic Python Agent."""
|
||||||
# Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
|
# Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
|
||||||
# if there is an issue (uncaught, restart agent)
|
# if there is an issue (uncaught, restart agent)
|
||||||
self.started_at = _time()
|
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(),
|
hardware_info=self.hardware.list_hardware_info(),
|
||||||
timeout=self.lookup_timeout,
|
timeout=self.lookup_timeout,
|
||||||
starting_interval=self.lookup_interval)
|
starting_interval=self.lookup_interval)
|
||||||
|
|
||||||
self.node = content['node']
|
self.node = content['node']
|
||||||
self.heartbeat_timeout = content['heartbeat_timeout']
|
self.heartbeat_timeout = content['heartbeat_timeout']
|
||||||
|
|
||||||
wsgi = simple_server.make_server(
|
wsgi = simple_server.make_server(
|
||||||
self.listen_address[0],
|
self.listen_address[0],
|
||||||
@@ -288,12 +291,14 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
|||||||
self.api,
|
self.api,
|
||||||
server_class=simple_server.WSGIServer)
|
server_class=simple_server.WSGIServer)
|
||||||
|
|
||||||
# Don't start heartbeating until the server is listening
|
if not self.standalone:
|
||||||
self.heartbeater.start()
|
# Don't start heartbeating until the server is listening
|
||||||
|
self.heartbeater.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wsgi.serve_forever()
|
wsgi.serve_forever()
|
||||||
except BaseException:
|
except BaseException:
|
||||||
self.log.exception('shutting down')
|
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 = [
|
cli_opts = [
|
||||||
cfg.StrOpt('api_url',
|
cfg.StrOpt('api_url',
|
||||||
required=('ipa-api-url' not in APARAMS),
|
default=APARAMS.get('ipa-api-url', 'http://127.0.0.1:6835'),
|
||||||
default=APARAMS.get('ipa-api-url'),
|
|
||||||
deprecated_name='api-url',
|
deprecated_name='api-url',
|
||||||
help='URL of the Ironic API'),
|
help='URL of the Ironic API'),
|
||||||
|
|
||||||
@@ -195,7 +194,12 @@ cli_opts = [
|
|||||||
|
|
||||||
cfg.FloatOpt('lldp_timeout',
|
cfg.FloatOpt('lldp_timeout',
|
||||||
default=APARAMS.get('lldp-timeout', 30.0),
|
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)
|
CONF.register_cli_opts(cli_opts)
|
||||||
@@ -204,7 +208,6 @@ CONF.register_cli_opts(cli_opts)
|
|||||||
def run():
|
def run():
|
||||||
CONF()
|
CONF()
|
||||||
log.setup('ironic-python-agent')
|
log.setup('ironic-python-agent')
|
||||||
|
|
||||||
agent.IronicPythonAgent(CONF.api_url,
|
agent.IronicPythonAgent(CONF.api_url,
|
||||||
(CONF.advertise_host, CONF.advertise_port),
|
(CONF.advertise_host, CONF.advertise_port),
|
||||||
(CONF.listen_host, CONF.listen_port),
|
(CONF.listen_host, CONF.listen_port),
|
||||||
@@ -213,4 +216,5 @@ def run():
|
|||||||
CONF.network_interface,
|
CONF.network_interface,
|
||||||
CONF.lookup_timeout,
|
CONF.lookup_timeout,
|
||||||
CONF.lookup_interval,
|
CONF.lookup_interval,
|
||||||
CONF.driver_name).run()
|
CONF.driver_name,
|
||||||
|
CONF.standalone).run()
|
||||||
|
@@ -146,7 +146,8 @@ class TestBaseAgent(test_base.BaseTestCase):
|
|||||||
'eth0',
|
'eth0',
|
||||||
300,
|
300,
|
||||||
1,
|
1,
|
||||||
'agent_ipmitool')
|
'agent_ipmitool',
|
||||||
|
False)
|
||||||
self.agent.ext_mgr = extension.ExtensionManager.\
|
self.agent.ext_mgr = extension.ExtensionManager.\
|
||||||
make_test_instance([extension.Extension('fake', None,
|
make_test_instance([extension.Extension('fake', None,
|
||||||
FakeExtension,
|
FakeExtension,
|
||||||
@@ -210,7 +211,8 @@ class TestBaseAgent(test_base.BaseTestCase):
|
|||||||
None,
|
None,
|
||||||
300,
|
300,
|
||||||
1,
|
1,
|
||||||
'agent_ipmitool')
|
'agent_ipmitool',
|
||||||
|
False)
|
||||||
|
|
||||||
homeless_agent.hardware = mock.Mock()
|
homeless_agent.hardware = mock.Mock()
|
||||||
mock_list_net = homeless_agent.hardware.list_network_interfaces
|
mock_list_net = homeless_agent.hardware.list_network_interfaces
|
||||||
@@ -303,6 +305,50 @@ class TestBaseAgent(test_base.BaseTestCase):
|
|||||||
self.agent.get_node_uuid)
|
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):
|
class TestAgentCmd(test_base.BaseTestCase):
|
||||||
@mock.patch('ironic_python_agent.openstack.common.log.getLogger')
|
@mock.patch('ironic_python_agent.openstack.common.log.getLogger')
|
||||||
@mock.patch(OPEN_FUNCTION_NAME)
|
@mock.patch(OPEN_FUNCTION_NAME)
|
||||||
|
Reference in New Issue
Block a user