Skip to content

Commit 417bf08

Browse files
author
Josh Gachnang
committed
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
1 parent 86d4b41 commit 417bf08

File tree

3 files changed

+70
-15
lines changed

3 files changed

+70
-15
lines changed

ironic_python_agent/agent.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
137137

138138
def __init__(self, api_url, advertise_address, listen_address,
139139
ip_lookup_attempts, ip_lookup_sleep, network_interface,
140-
lookup_timeout, lookup_interval, driver_name):
140+
lookup_timeout, lookup_interval, driver_name, standalone):
141141
super(IronicPythonAgent, self).__init__()
142142
self.ext_mgr = extension.ExtensionManager(
143143
namespace='ironic_python_agent.extensions',
@@ -166,6 +166,7 @@ def __init__(self, api_url, advertise_address, listen_address,
166166
self.ip_lookup_attempts = ip_lookup_attempts
167167
self.ip_lookup_sleep = ip_lookup_sleep
168168
self.network_interface = network_interface
169+
self.standalone = standalone
169170

170171
def get_status(self):
171172
"""Retrieve a serializable status.
@@ -267,33 +268,37 @@ def get_command_result(self, result_id):
267268
result_id)
268269

269270
def force_heartbeat(self):
270-
self.heartbeater.force_heartbeat()
271+
if not self.standalone:
272+
self.heartbeater.force_heartbeat()
271273

272274
def run(self):
273275
"""Run the Ironic Python Agent."""
274276
# Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
275277
# if there is an issue (uncaught, restart agent)
276278
self.started_at = _time()
277-
content = self.api_client.lookup_node(
279+
if not self.standalone:
280+
content = self.api_client.lookup_node(
278281
hardware_info=self.hardware.list_hardware_info(),
279282
timeout=self.lookup_timeout,
280283
starting_interval=self.lookup_interval)
281284

282-
self.node = content['node']
283-
self.heartbeat_timeout = content['heartbeat_timeout']
285+
self.node = content['node']
286+
self.heartbeat_timeout = content['heartbeat_timeout']
284287

285288
wsgi = simple_server.make_server(
286289
self.listen_address[0],
287290
self.listen_address[1],
288291
self.api,
289292
server_class=simple_server.WSGIServer)
290293

291-
# Don't start heartbeating until the server is listening
292-
self.heartbeater.start()
294+
if not self.standalone:
295+
# Don't start heartbeating until the server is listening
296+
self.heartbeater.start()
293297

294298
try:
295299
wsgi.serve_forever()
296300
except BaseException:
297301
self.log.exception('shutting down')
298302

299-
self.heartbeater.stop()
303+
if not self.standalone:
304+
self.heartbeater.stop()

ironic_python_agent/cmd/agent.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,7 @@ def _get_vmedia_params():
128128

129129
cli_opts = [
130130
cfg.StrOpt('api_url',
131-
required=('ipa-api-url' not in APARAMS),
132-
default=APARAMS.get('ipa-api-url'),
131+
default=APARAMS.get('ipa-api-url', 'http://127.0.0.1:6835'),
133132
deprecated_name='api-url',
134133
help='URL of the Ironic API'),
135134

@@ -195,7 +194,12 @@ def _get_vmedia_params():
195194

196195
cfg.FloatOpt('lldp_timeout',
197196
default=APARAMS.get('lldp-timeout', 30.0),
198-
help='The amount of seconds to wait for LLDP packets.')
197+
help='The amount of seconds to wait for LLDP packets.'),
198+
199+
cfg.BoolOpt('standalone',
200+
default=False,
201+
help='Note: for debugging only. Start the Agent but suppress '
202+
'any calls to Ironic API.'),
199203
]
200204

201205
CONF.register_cli_opts(cli_opts)
@@ -204,7 +208,6 @@ def _get_vmedia_params():
204208
def run():
205209
CONF()
206210
log.setup('ironic-python-agent')
207-
208211
agent.IronicPythonAgent(CONF.api_url,
209212
(CONF.advertise_host, CONF.advertise_port),
210213
(CONF.listen_host, CONF.listen_port),
@@ -213,4 +216,5 @@ def run():
213216
CONF.network_interface,
214217
CONF.lookup_timeout,
215218
CONF.lookup_interval,
216-
CONF.driver_name).run()
219+
CONF.driver_name,
220+
CONF.standalone).run()

ironic_python_agent/tests/agent.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ def setUp(self):
146146
'eth0',
147147
300,
148148
1,
149-
'agent_ipmitool')
149+
'agent_ipmitool',
150+
False)
150151
self.agent.ext_mgr = extension.ExtensionManager.\
151152
make_test_instance([extension.Extension('fake', None,
152153
FakeExtension,
@@ -210,7 +211,8 @@ def test_ipv4_lookup(self, mock_time_sleep, mock_poll, mock_read):
210211
None,
211212
300,
212213
1,
213-
'agent_ipmitool')
214+
'agent_ipmitool',
215+
False)
214216

215217
homeless_agent.hardware = mock.Mock()
216218
mock_list_net = homeless_agent.hardware.list_network_interfaces
@@ -303,6 +305,50 @@ def test_get_node_uuid_invalid_node(self):
303305
self.agent.get_node_uuid)
304306

305307

308+
class TestAgentStandalone(test_base.BaseTestCase):
309+
310+
def setUp(self):
311+
super(TestAgentStandalone, self).setUp()
312+
self.agent = agent.IronicPythonAgent('https://fake_api.example.'
313+
'org:8081/',
314+
('203.0.113.1', 9990),
315+
('192.0.2.1', 9999),
316+
3,
317+
10,
318+
'eth0',
319+
300,
320+
1,
321+
'agent_ipmitool',
322+
True)
323+
324+
@mock.patch('wsgiref.simple_server.make_server', autospec=True)
325+
@mock.patch.object(hardware.HardwareManager, 'list_hardware_info')
326+
def test_run(self, mocked_list_hardware, wsgi_server_cls):
327+
wsgi_server = wsgi_server_cls.return_value
328+
wsgi_server.start.side_effect = KeyboardInterrupt()
329+
330+
self.agent.heartbeater = mock.Mock()
331+
self.agent.api_client.lookup_node = mock.Mock()
332+
self.agent.api_client.lookup_node.return_value = {
333+
'node': {
334+
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
335+
},
336+
'heartbeat_timeout': 300
337+
}
338+
self.agent.run()
339+
340+
listen_addr = ('192.0.2.1', 9999)
341+
wsgi_server_cls.assert_called_once_with(
342+
listen_addr[0],
343+
listen_addr[1],
344+
self.agent.api,
345+
server_class=simple_server.WSGIServer)
346+
wsgi_server.serve_forever.assert_called_once()
347+
348+
self.assertFalse(self.agent.heartbeater.called)
349+
self.assertFalse(self.agent.api_client.lookup_node.called)
350+
351+
306352
class TestAgentCmd(test_base.BaseTestCase):
307353
@mock.patch('ironic_python_agent.openstack.common.log.getLogger')
308354
@mock.patch(OPEN_FUNCTION_NAME)

0 commit comments

Comments
 (0)