Supports fetching API endpoints from mDNS
This change enables IPA to receive API endpoints and configuration via multicast DNS. Story: #2005393 Task: #30382 Change-Id: Ibbf07052bea8f5c0305dda098b2879bcbc2fece5
This commit is contained in:
parent
4c89cf4cf3
commit
5c5328ccaa
@ -21,6 +21,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from wsgiref import simple_server
|
from wsgiref import simple_server
|
||||||
|
|
||||||
|
from ironic_lib import exception as lib_exc
|
||||||
|
from ironic_lib import mdns
|
||||||
import netaddr
|
import netaddr
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@ -31,6 +33,7 @@ from six.moves.urllib import parse as urlparse
|
|||||||
from stevedore import extension
|
from stevedore import extension
|
||||||
|
|
||||||
from ironic_python_agent.api import app
|
from ironic_python_agent.api import app
|
||||||
|
from ironic_python_agent import config
|
||||||
from ironic_python_agent import encoding
|
from ironic_python_agent import encoding
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent.extensions import base
|
from ironic_python_agent.extensions import base
|
||||||
@ -176,6 +179,21 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
|||||||
invoke_kwds={'agent': self},
|
invoke_kwds={'agent': self},
|
||||||
)
|
)
|
||||||
self.api_url = api_url
|
self.api_url = api_url
|
||||||
|
if not self.api_url or self.api_url == 'mdns':
|
||||||
|
try:
|
||||||
|
self.api_url, params = mdns.get_endpoint('baremetal')
|
||||||
|
except lib_exc.ServiceLookupFailure:
|
||||||
|
if self.api_url:
|
||||||
|
# mDNS explicitly requested, report failure.
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# implicit fallback to mDNS, do not fail (maybe we're only
|
||||||
|
# running inspection).
|
||||||
|
LOG.warning('Could not get baremetal endpoint from mDNS, '
|
||||||
|
'will not heartbeat')
|
||||||
|
else:
|
||||||
|
config.override(params)
|
||||||
|
|
||||||
if self.api_url:
|
if self.api_url:
|
||||||
self.api_client = ironic_api_client.APIClient(self.api_url)
|
self.api_client = ironic_api_client.APIClient(self.api_url)
|
||||||
self.heartbeater = IronicPythonAgentHeartbeater(self)
|
self.heartbeater = IronicPythonAgentHeartbeater(self)
|
||||||
|
@ -13,23 +13,29 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from ironic_python_agent import inspector
|
|
||||||
from ironic_python_agent import netutils
|
from ironic_python_agent import netutils
|
||||||
from ironic_python_agent import utils
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
APARAMS = utils.get_agent_params()
|
APARAMS = utils.get_agent_params()
|
||||||
|
|
||||||
|
INSPECTION_DEFAULT_COLLECTOR = 'default'
|
||||||
|
INSPECTION_DEFAULT_DHCP_WAIT_TIMEOUT = 60
|
||||||
|
|
||||||
cli_opts = [
|
cli_opts = [
|
||||||
cfg.StrOpt('api_url',
|
cfg.StrOpt('api_url',
|
||||||
default=APARAMS.get('ipa-api-url'),
|
default=APARAMS.get('ipa-api-url'),
|
||||||
deprecated_name='api-url',
|
deprecated_name='api-url',
|
||||||
regex='^http(s?):\\/\\/.+',
|
regex='^(mdns|http(s?):\\/\\/.+)',
|
||||||
help='URL of the Ironic API. '
|
help='URL of the Ironic API. '
|
||||||
'Can be supplied as "ipa-api-url" kernel parameter.'
|
'Can be supplied as "ipa-api-url" kernel parameter.'
|
||||||
'The value must start with either http:// or https://.'),
|
'The value must start with either http:// or https://. '
|
||||||
|
'A special value "mdns" can be specified to fetch the '
|
||||||
|
'URL using multicast DNS service discovery.'),
|
||||||
|
|
||||||
cfg.StrOpt('listen_host',
|
cfg.StrOpt('listen_host',
|
||||||
default=APARAMS.get('ipa-listen-host',
|
default=APARAMS.get('ipa-listen-host',
|
||||||
@ -133,12 +139,14 @@ cli_opts = [
|
|||||||
help='Endpoint of ironic-inspector. If set, hardware inventory '
|
help='Endpoint of ironic-inspector. If set, hardware inventory '
|
||||||
'will be collected and sent to ironic-inspector '
|
'will be collected and sent to ironic-inspector '
|
||||||
'on start up. '
|
'on start up. '
|
||||||
|
'A special value "mdns" can be specified to fetch the '
|
||||||
|
'URL using multicast DNS service discovery. '
|
||||||
'Can be supplied as "ipa-inspection-callback-url" '
|
'Can be supplied as "ipa-inspection-callback-url" '
|
||||||
'kernel parameter.'),
|
'kernel parameter.'),
|
||||||
|
|
||||||
cfg.StrOpt('inspection_collectors',
|
cfg.StrOpt('inspection_collectors',
|
||||||
default=APARAMS.get('ipa-inspection-collectors',
|
default=APARAMS.get('ipa-inspection-collectors',
|
||||||
inspector.DEFAULT_COLLECTOR),
|
INSPECTION_DEFAULT_COLLECTOR),
|
||||||
help='Comma-separated list of plugins providing additional '
|
help='Comma-separated list of plugins providing additional '
|
||||||
'hardware data for inspection, empty value gives '
|
'hardware data for inspection, empty value gives '
|
||||||
'a minimum required set of plugins. '
|
'a minimum required set of plugins. '
|
||||||
@ -148,7 +156,7 @@ cli_opts = [
|
|||||||
cfg.IntOpt('inspection_dhcp_wait_timeout',
|
cfg.IntOpt('inspection_dhcp_wait_timeout',
|
||||||
min=0,
|
min=0,
|
||||||
default=APARAMS.get('ipa-inspection-dhcp-wait-timeout',
|
default=APARAMS.get('ipa-inspection-dhcp-wait-timeout',
|
||||||
inspector.DEFAULT_DHCP_WAIT_TIMEOUT),
|
INSPECTION_DEFAULT_DHCP_WAIT_TIMEOUT),
|
||||||
help='Maximum time (in seconds) to wait for the PXE NIC '
|
help='Maximum time (in seconds) to wait for the PXE NIC '
|
||||||
'(or all NICs if inspection_dhcp_all_interfaces is True) '
|
'(or all NICs if inspection_dhcp_all_interfaces is True) '
|
||||||
'to get its IP address via DHCP before inspection. '
|
'to get its IP address via DHCP before inspection. '
|
||||||
@ -216,3 +224,29 @@ CONF.register_cli_opts(cli_opts)
|
|||||||
|
|
||||||
def list_opts():
|
def list_opts():
|
||||||
return [('DEFAULT', cli_opts)]
|
return [('DEFAULT', cli_opts)]
|
||||||
|
|
||||||
|
|
||||||
|
def override(params):
|
||||||
|
"""Override configuration with values from a dictionary.
|
||||||
|
|
||||||
|
This is used for configuration overrides from mDNS.
|
||||||
|
|
||||||
|
:param params: new configuration parameters as a dict.
|
||||||
|
"""
|
||||||
|
if not params:
|
||||||
|
return
|
||||||
|
|
||||||
|
LOG.debug('Overriding configuration with %s', params)
|
||||||
|
for key, value in params.items():
|
||||||
|
if key.startswith('ipa_'):
|
||||||
|
key = key[4:]
|
||||||
|
else:
|
||||||
|
LOG.warning('Skipping unknown configuration option %s', key)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
CONF.set_override(key, value)
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.warning('Unable to override configuration option %(key)s '
|
||||||
|
'with %(value)r: %(exc)s',
|
||||||
|
{'key': key, 'value': value, 'exc': exc})
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from ironic_lib import mdns
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@ -24,6 +25,7 @@ from oslo_utils import excutils
|
|||||||
import requests
|
import requests
|
||||||
import stevedore
|
import stevedore
|
||||||
|
|
||||||
|
from ironic_python_agent import config
|
||||||
from ironic_python_agent import encoding
|
from ironic_python_agent import encoding
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
@ -32,8 +34,6 @@ from ironic_python_agent import utils
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
DEFAULT_COLLECTOR = 'default'
|
|
||||||
DEFAULT_DHCP_WAIT_TIMEOUT = 60
|
|
||||||
|
|
||||||
_DHCP_RETRY_INTERVAL = 2
|
_DHCP_RETRY_INTERVAL = 2
|
||||||
_COLLECTOR_NS = 'ironic_python_agent.inspector.collectors'
|
_COLLECTOR_NS = 'ironic_python_agent.inspector.collectors'
|
||||||
@ -63,6 +63,15 @@ def inspect():
|
|||||||
if not CONF.inspection_callback_url:
|
if not CONF.inspection_callback_url:
|
||||||
LOG.info('Inspection is disabled, skipping')
|
LOG.info('Inspection is disabled, skipping')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if CONF.inspection_callback_url == 'mdns':
|
||||||
|
LOG.debug('Fetching the inspection URL from mDNS')
|
||||||
|
url, params = mdns.get_endpoint('baremetal-introspection')
|
||||||
|
# We expect a proper catalog URL, which doesn't include any path.
|
||||||
|
CONF.set_override('inspection_callback_url',
|
||||||
|
url.rstrip('/') + '/v1/continue')
|
||||||
|
config.override(params)
|
||||||
|
|
||||||
collector_names = [x.strip() for x in CONF.inspection_collectors.split(',')
|
collector_names = [x.strip() for x in CONF.inspection_collectors.split(',')
|
||||||
if x.strip()]
|
if x.strip()]
|
||||||
LOG.info('inspection is enabled with collectors %s', collector_names)
|
LOG.info('inspection is enabled with collectors %s', collector_names)
|
||||||
|
@ -16,6 +16,7 @@ import socket
|
|||||||
import time
|
import time
|
||||||
from wsgiref import simple_server
|
from wsgiref import simple_server
|
||||||
|
|
||||||
|
from ironic_lib import exception as lib_exc
|
||||||
import mock
|
import mock
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@ -217,6 +218,126 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_dispatch.call_args_list)
|
mock_dispatch.call_args_list)
|
||||||
self.agent.heartbeater.start.assert_called_once_with()
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
||||||
|
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
||||||
|
@mock.patch(
|
||||||
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
|
mock.Mock())
|
||||||
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
|
@mock.patch.object(agent.IronicPythonAgent,
|
||||||
|
'_wait_for_interface', autospec=True)
|
||||||
|
@mock.patch('wsgiref.simple_server.WSGIServer', autospec=True)
|
||||||
|
@mock.patch.object(hardware, 'load_managers', autospec=True)
|
||||||
|
def test_url_from_mdns_by_default(self, mock_load_managers, mock_wsgi,
|
||||||
|
mock_wait, mock_dispatch, mock_mdns):
|
||||||
|
CONF.set_override('inspection_callback_url', '')
|
||||||
|
mock_mdns.return_value = 'https://example.com', {}
|
||||||
|
|
||||||
|
wsgi_server = mock_wsgi.return_value
|
||||||
|
|
||||||
|
self.agent = agent.IronicPythonAgent(None,
|
||||||
|
agent.Host('203.0.113.1', 9990),
|
||||||
|
agent.Host('192.0.2.1', 9999),
|
||||||
|
3,
|
||||||
|
10,
|
||||||
|
'eth0',
|
||||||
|
300,
|
||||||
|
1,
|
||||||
|
False)
|
||||||
|
|
||||||
|
def set_serve_api():
|
||||||
|
self.agent.serve_api = False
|
||||||
|
|
||||||
|
wsgi_server.handle_request.side_effect = set_serve_api
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
'config': {
|
||||||
|
'heartbeat_timeout': 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.agent.run()
|
||||||
|
|
||||||
|
listen_addr = agent.Host('192.0.2.1', 9999)
|
||||||
|
mock_wsgi.assert_called_once_with(
|
||||||
|
(listen_addr.hostname,
|
||||||
|
listen_addr.port),
|
||||||
|
simple_server.WSGIRequestHandler)
|
||||||
|
wsgi_server.set_app.assert_called_once_with(self.agent.api)
|
||||||
|
self.assertTrue(wsgi_server.handle_request.called)
|
||||||
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
|
mock.call('wait_for_disks')],
|
||||||
|
mock_dispatch.call_args_list)
|
||||||
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
||||||
|
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
||||||
|
@mock.patch(
|
||||||
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
|
mock.Mock())
|
||||||
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
|
@mock.patch.object(agent.IronicPythonAgent,
|
||||||
|
'_wait_for_interface', autospec=True)
|
||||||
|
@mock.patch('wsgiref.simple_server.WSGIServer', autospec=True)
|
||||||
|
@mock.patch.object(hardware, 'load_managers', autospec=True)
|
||||||
|
def test_url_from_mdns_explicitly(self, mock_load_managers, mock_wsgi,
|
||||||
|
mock_wait, mock_dispatch, mock_mdns):
|
||||||
|
CONF.set_override('inspection_callback_url', '')
|
||||||
|
CONF.set_override('disk_wait_attempts', 0)
|
||||||
|
mock_mdns.return_value = 'https://example.com', {
|
||||||
|
# configuration via mdns
|
||||||
|
'ipa_disk_wait_attempts': '42',
|
||||||
|
}
|
||||||
|
|
||||||
|
wsgi_server = mock_wsgi.return_value
|
||||||
|
|
||||||
|
self.agent = agent.IronicPythonAgent('mdns',
|
||||||
|
agent.Host('203.0.113.1', 9990),
|
||||||
|
agent.Host('192.0.2.1', 9999),
|
||||||
|
3,
|
||||||
|
10,
|
||||||
|
'eth0',
|
||||||
|
300,
|
||||||
|
1,
|
||||||
|
False)
|
||||||
|
|
||||||
|
def set_serve_api():
|
||||||
|
self.agent.serve_api = False
|
||||||
|
|
||||||
|
wsgi_server.handle_request.side_effect = set_serve_api
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
'config': {
|
||||||
|
'heartbeat_timeout': 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.agent.run()
|
||||||
|
|
||||||
|
listen_addr = agent.Host('192.0.2.1', 9999)
|
||||||
|
mock_wsgi.assert_called_once_with(
|
||||||
|
(listen_addr.hostname,
|
||||||
|
listen_addr.port),
|
||||||
|
simple_server.WSGIRequestHandler)
|
||||||
|
wsgi_server.set_app.assert_called_once_with(self.agent.api)
|
||||||
|
self.assertTrue(wsgi_server.handle_request.called)
|
||||||
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
|
mock.call('wait_for_disks')],
|
||||||
|
mock_dispatch.call_args_list)
|
||||||
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
# changed via mdns
|
||||||
|
self.assertEqual(42, CONF.disk_wait_attempts)
|
||||||
|
|
||||||
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
@ -314,6 +435,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_dispatch.call_args_list)
|
mock_dispatch.call_args_list)
|
||||||
self.agent.heartbeater.start.assert_called_once_with()
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
||||||
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
@ -330,7 +452,9 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_wsgi,
|
mock_wsgi,
|
||||||
mock_dispatch,
|
mock_dispatch,
|
||||||
mock_inspector,
|
mock_inspector,
|
||||||
mock_wait):
|
mock_wait,
|
||||||
|
mock_mdns):
|
||||||
|
mock_mdns.side_effect = lib_exc.ServiceLookupFailure()
|
||||||
# If inspection_callback_url is configured and api_url is not when the
|
# If inspection_callback_url is configured and api_url is not when the
|
||||||
# agent starts, ensure that the inspection will be called and wsgi
|
# agent starts, ensure that the inspection will be called and wsgi
|
||||||
# server will work as usual. Also, make sure api_client and heartbeater
|
# server will work as usual. Also, make sure api_client and heartbeater
|
||||||
@ -369,6 +493,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
self.assertFalse(mock_wait.called)
|
self.assertFalse(mock_wait.called)
|
||||||
self.assertFalse(mock_dispatch.called)
|
self.assertFalse(mock_dispatch.called)
|
||||||
|
|
||||||
|
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
||||||
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
@mock.patch.object(hardware, '_check_for_iscsi', mock.Mock())
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
@ -385,7 +510,9 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_wsgi,
|
mock_wsgi,
|
||||||
mock_dispatch,
|
mock_dispatch,
|
||||||
mock_inspector,
|
mock_inspector,
|
||||||
mock_wait):
|
mock_wait,
|
||||||
|
mock_mdns):
|
||||||
|
mock_mdns.side_effect = lib_exc.ServiceLookupFailure()
|
||||||
# If both api_url and inspection_callback_url are not configured when
|
# If both api_url and inspection_callback_url are not configured when
|
||||||
# the agent starts, ensure that the inspection will be skipped and wsgi
|
# the agent starts, ensure that the inspection will be skipped and wsgi
|
||||||
# server will work as usual. Also, make sure api_client and heartbeater
|
# server will work as usual. Also, make sure api_client and heartbeater
|
||||||
|
@ -24,6 +24,7 @@ from oslo_config import cfg
|
|||||||
import requests
|
import requests
|
||||||
import stevedore
|
import stevedore
|
||||||
|
|
||||||
|
from ironic_python_agent import config
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
from ironic_python_agent import inspector
|
from ironic_python_agent import inspector
|
||||||
@ -48,8 +49,9 @@ class AcceptingFailure(mock.Mock):
|
|||||||
|
|
||||||
class TestMisc(base.IronicAgentTest):
|
class TestMisc(base.IronicAgentTest):
|
||||||
def test_default_collector_loadable(self):
|
def test_default_collector_loadable(self):
|
||||||
ext = inspector.extension_manager([inspector.DEFAULT_COLLECTOR])
|
ext = inspector.extension_manager(
|
||||||
self.assertIs(ext[inspector.DEFAULT_COLLECTOR].plugin,
|
[config.INSPECTION_DEFAULT_COLLECTOR])
|
||||||
|
self.assertIs(ext[config.INSPECTION_DEFAULT_COLLECTOR].plugin,
|
||||||
inspector.collect_default)
|
inspector.collect_default)
|
||||||
|
|
||||||
def test_raise_on_wrong_collector(self):
|
def test_raise_on_wrong_collector(self):
|
||||||
@ -80,6 +82,26 @@ class TestInspect(base.IronicAgentTest):
|
|||||||
mock_call.assert_called_with_failure()
|
mock_call.assert_called_with_failure()
|
||||||
self.assertEqual('uuid1', result)
|
self.assertEqual('uuid1', result)
|
||||||
|
|
||||||
|
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
||||||
|
def test_mdns(self, mock_mdns, mock_ext_mgr, mock_call):
|
||||||
|
CONF.set_override('inspection_callback_url', 'mdns')
|
||||||
|
mock_mdns.return_value = 'http://example', {
|
||||||
|
'ipa_inspection_collectors': 'one,two'
|
||||||
|
}
|
||||||
|
mock_ext_mgr.return_value = [self.mock_ext]
|
||||||
|
mock_call.return_value = {'uuid': 'uuid1'}
|
||||||
|
|
||||||
|
result = inspector.inspect()
|
||||||
|
|
||||||
|
self.mock_collect.assert_called_with_failure()
|
||||||
|
mock_call.assert_called_with_failure()
|
||||||
|
self.assertEqual('uuid1', result)
|
||||||
|
|
||||||
|
self.assertEqual('http://example/v1/continue',
|
||||||
|
CONF.inspection_callback_url)
|
||||||
|
self.assertEqual('one,two', CONF.inspection_collectors)
|
||||||
|
self.assertEqual(['one', 'two'], mock_ext_mgr.call_args[1]['names'])
|
||||||
|
|
||||||
def test_collectors_option(self, mock_ext_mgr, mock_call):
|
def test_collectors_option(self, mock_ext_mgr, mock_call):
|
||||||
CONF.set_override('inspection_collectors', 'foo,bar')
|
CONF.set_override('inspection_collectors', 'foo,bar')
|
||||||
mock_ext_mgr.return_value = [
|
mock_ext_mgr.return_value = [
|
||||||
@ -362,7 +384,7 @@ class TestWaitForDhcp(base.IronicAgentTest):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestWaitForDhcp, self).setUp()
|
super(TestWaitForDhcp, self).setUp()
|
||||||
CONF.set_override('inspection_dhcp_wait_timeout',
|
CONF.set_override('inspection_dhcp_wait_timeout',
|
||||||
inspector.DEFAULT_DHCP_WAIT_TIMEOUT)
|
config.INSPECTION_DEFAULT_DHCP_WAIT_TIMEOUT)
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', autospec=True)
|
@mock.patch.object(time, 'sleep', autospec=True)
|
||||||
def test_all(self, mocked_sleep, mocked_dispatch):
|
def test_all(self, mocked_sleep, mocked_dispatch):
|
||||||
|
@ -24,7 +24,7 @@ greenlet==0.4.13
|
|||||||
hacking==1.0.0
|
hacking==1.0.0
|
||||||
idna==2.6
|
idna==2.6
|
||||||
imagesize==1.0.0
|
imagesize==1.0.0
|
||||||
ironic-lib==2.16.0
|
ironic-lib==2.17.0
|
||||||
iso8601==0.1.11
|
iso8601==0.1.11
|
||||||
Jinja2==2.10
|
Jinja2==2.10
|
||||||
keystoneauth1==3.4.0
|
keystoneauth1==3.4.0
|
||||||
|
12
releasenotes/notes/mdns-e020484e64d76edb.yaml
Normal file
12
releasenotes/notes/mdns-e020484e64d76edb.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Supports fetching baremetal and baremetal introspection endpoints from
|
||||||
|
mDNS instead of providing them via kernel parameters or a configuration
|
||||||
|
file. See `story 2005393
|
||||||
|
<https://storyboard.openstack.org/#!/story/2005393>`_ for more details.
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
When no baremetal API URL is provided (e.g. via the ``ipa-api-url`` kernel
|
||||||
|
parameter), ironic-python-agent now tries to get the URL using mDNS service
|
||||||
|
discovery.
|
@ -21,4 +21,4 @@ rtslib-fb>=2.1.65 # Apache-2.0
|
|||||||
six>=1.10.0 # MIT
|
six>=1.10.0 # MIT
|
||||||
stevedore>=1.20.0 # Apache-2.0
|
stevedore>=1.20.0 # Apache-2.0
|
||||||
WSME>=0.8.0 # MIT
|
WSME>=0.8.0 # MIT
|
||||||
ironic-lib>=2.16.0 # Apache-2.0
|
ironic-lib>=2.17.0 # Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user