Allow setting image_download_source per node
Allows certain flexibility when it comes to low RAM vs high RAM nodes, and large vs small images. Also deploy_interface is settable per node, so this feature makes it easier to migrate from the iscsi deploy. Story: #2008075 Task: #40766 Change-Id: Idf3bbc6d24042ce1d9a895095b5cb0979dd3183d
This commit is contained in:
parent
9ac5c02770
commit
840ce16668
@ -71,6 +71,15 @@ This configuration affects *glance* and ``file://`` images. If you want
|
|||||||
[agent]
|
[agent]
|
||||||
image_download_source = local
|
image_download_source = local
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This option can also be set per node in ``driver_info``::
|
||||||
|
|
||||||
|
openstack baremetal node set <node> --driver-info image_download_source=local
|
||||||
|
|
||||||
|
or per instance in ``instance_info``::
|
||||||
|
|
||||||
|
openstack baremetal node set <node> --instance-info image_download_source=local
|
||||||
|
|
||||||
You need to set up a workable HTTP server at each conductor node which with
|
You need to set up a workable HTTP server at each conductor node which with
|
||||||
``direct`` deploy interface enabled, and check http related options in the
|
``direct`` deploy interface enabled, and check http related options in the
|
||||||
ironic configuration file to match the HTTP server configurations.
|
ironic configuration file to match the HTTP server configurations.
|
||||||
|
@ -228,6 +228,13 @@ Populating instance_info
|
|||||||
--instance-info image_source=$IMG \
|
--instance-info image_source=$IMG \
|
||||||
--instance-info image_checksum=$MD5HASH
|
--instance-info image_checksum=$MD5HASH
|
||||||
|
|
||||||
|
#. When using low RAM nodes with ``http://`` images that are not in the RAW
|
||||||
|
format, you may want them cached locally, converted to raw and served from
|
||||||
|
the conductor's HTTP server::
|
||||||
|
|
||||||
|
openstack baremetal node set $NODE_UUID \
|
||||||
|
--instance-info image_download_source=local
|
||||||
|
|
||||||
#. :ref:`Boot mode <boot_mode_support>` can be specified per instance::
|
#. :ref:`Boot mode <boot_mode_support>` can be specified per instance::
|
||||||
|
|
||||||
openstack baremetal node set $NODE_UUID \
|
openstack baremetal node set $NODE_UUID \
|
||||||
|
@ -57,6 +57,12 @@ OPTIONAL_PROPERTIES = {
|
|||||||
'a dot to prefix the domain name. This value will be '
|
'a dot to prefix the domain name. This value will be '
|
||||||
'ignored if ``image_http_proxy`` and '
|
'ignored if ``image_http_proxy`` and '
|
||||||
'``image_https_proxy`` are not specified. Optional.'),
|
'``image_https_proxy`` are not specified. Optional.'),
|
||||||
|
'image_download_source': _('Specifies whether direct deploy interface '
|
||||||
|
'should try to use the image source directly '
|
||||||
|
'or if ironic should cache the image on the '
|
||||||
|
'conductor and serve it from ironic\'s own '
|
||||||
|
'HTTP server. Accepted values are "swift", '
|
||||||
|
'"http" and "local". Optional.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
_RAID_APPLY_CONFIGURATION_ARGSINFO = {
|
_RAID_APPLY_CONFIGURATION_ARGSINFO = {
|
||||||
@ -168,13 +174,19 @@ def validate_http_provisioning_configuration(node):
|
|||||||
:raises: MissingParameterValue if required option(s) is not set.
|
:raises: MissingParameterValue if required option(s) is not set.
|
||||||
"""
|
"""
|
||||||
image_source = node.instance_info.get('image_source')
|
image_source = node.instance_info.get('image_source')
|
||||||
|
image_download_source = deploy_utils.get_image_download_source(node)
|
||||||
|
if image_download_source not in ('swift', 'http', 'local'):
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
_('Invalid value for image_download_source: "%s". Valid values '
|
||||||
|
'are swift, http or local.') % image_download_source)
|
||||||
|
|
||||||
# NOTE(dtantsur): local HTTP configuration is required in two cases:
|
# NOTE(dtantsur): local HTTP configuration is required in two cases:
|
||||||
# 1. Glance images with image_download_source == http
|
# 1. Glance images with image_download_source == http
|
||||||
# 2. File images (since we need to serve them to IPA)
|
# 2. File images (since we need to serve them to IPA)
|
||||||
if (not image_source.startswith('file://')
|
if (not image_source.startswith('file://')
|
||||||
and CONF.agent.image_download_source != 'local'
|
and image_download_source != 'local'
|
||||||
and (not service_utils.is_glance_image(image_source)
|
and (not service_utils.is_glance_image(image_source)
|
||||||
or CONF.agent.image_download_source == 'swift')):
|
or image_download_source == 'swift')):
|
||||||
return
|
return
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
@ -1046,6 +1046,13 @@ def _cache_and_convert_image(task, instance_info, image_info=None):
|
|||||||
instance_info['image_url'] = http_image_url
|
instance_info['image_url'] = http_image_url
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_download_source(node):
|
||||||
|
"""Get the effective value of image_download_source for the node."""
|
||||||
|
return (node.instance_info.get('image_download_source')
|
||||||
|
or node.driver_info.get('image_download_source')
|
||||||
|
or CONF.agent.image_download_source)
|
||||||
|
|
||||||
|
|
||||||
@METRICS.timer('build_instance_info_for_deploy')
|
@METRICS.timer('build_instance_info_for_deploy')
|
||||||
def build_instance_info_for_deploy(task):
|
def build_instance_info_for_deploy(task):
|
||||||
"""Build instance_info necessary for deploying to a node.
|
"""Build instance_info necessary for deploying to a node.
|
||||||
@ -1060,13 +1067,14 @@ def build_instance_info_for_deploy(task):
|
|||||||
instance_info = node.instance_info
|
instance_info = node.instance_info
|
||||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||||
image_source = instance_info['image_source']
|
image_source = instance_info['image_source']
|
||||||
|
image_download_source = get_image_download_source(node)
|
||||||
|
|
||||||
if service_utils.is_glance_image(image_source):
|
if service_utils.is_glance_image(image_source):
|
||||||
glance = image_service.GlanceImageService(context=task.context)
|
glance = image_service.GlanceImageService(context=task.context)
|
||||||
image_info = glance.show(image_source)
|
image_info = glance.show(image_source)
|
||||||
LOG.debug('Got image info: %(info)s for node %(node)s.',
|
LOG.debug('Got image info: %(info)s for node %(node)s.',
|
||||||
{'info': image_info, 'node': node.uuid})
|
{'info': image_info, 'node': node.uuid})
|
||||||
if CONF.agent.image_download_source == 'swift':
|
if image_download_source == 'swift':
|
||||||
swift_temp_url = glance.swift_temp_url(image_info)
|
swift_temp_url = glance.swift_temp_url(image_info)
|
||||||
_validate_image_url(node, swift_temp_url, secret=True)
|
_validate_image_url(node, swift_temp_url, secret=True)
|
||||||
instance_info['image_url'] = swift_temp_url
|
instance_info['image_url'] = swift_temp_url
|
||||||
@ -1086,7 +1094,7 @@ def build_instance_info_for_deploy(task):
|
|||||||
instance_info['kernel'] = image_info['properties']['kernel_id']
|
instance_info['kernel'] = image_info['properties']['kernel_id']
|
||||||
instance_info['ramdisk'] = image_info['properties']['ramdisk_id']
|
instance_info['ramdisk'] = image_info['properties']['ramdisk_id']
|
||||||
elif (image_source.startswith('file://')
|
elif (image_source.startswith('file://')
|
||||||
or CONF.agent.image_download_source == 'local'):
|
or image_download_source == 'local'):
|
||||||
_cache_and_convert_image(task, instance_info)
|
_cache_and_convert_image(task, instance_info)
|
||||||
else:
|
else:
|
||||||
_validate_image_url(node, image_source)
|
_validate_image_url(node, image_source)
|
||||||
|
@ -213,6 +213,33 @@ class TestAgentMethods(db_base.DbTestCase):
|
|||||||
agent.validate_http_provisioning_configuration,
|
agent.validate_http_provisioning_configuration,
|
||||||
self.node)
|
self.node)
|
||||||
|
|
||||||
|
def test_validate_http_provisioning_missing_args_local_via_node(self):
|
||||||
|
CONF.set_override('http_url', None, group='deploy')
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
i_info['image_source'] = 'http://image-ref'
|
||||||
|
i_info['image_download_source'] = 'local'
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
self.assertRaisesRegex(exception.MissingParameterValue,
|
||||||
|
'failed to validate http provisoning',
|
||||||
|
agent.validate_http_provisioning_configuration,
|
||||||
|
self.node)
|
||||||
|
|
||||||
|
def test_validate_http_provisioning_invalid_image_download_source(self):
|
||||||
|
CONF.set_override('http_url', None, group='deploy')
|
||||||
|
self.node.instance_info['image_source'] = 'http://image-ref'
|
||||||
|
self.node.instance_info['image_download_source'] = 'fridge'
|
||||||
|
self.assertRaisesRegex(exception.InvalidParameterValue, 'fridge',
|
||||||
|
agent.validate_http_provisioning_configuration,
|
||||||
|
self.node)
|
||||||
|
|
||||||
|
def test_validate_http_provisioning_invalid_image_download_source2(self):
|
||||||
|
CONF.set_override('http_url', None, group='deploy')
|
||||||
|
self.node.instance_info['image_source'] = 'http://image-ref'
|
||||||
|
self.node.driver_info['image_download_source'] = 'fridge'
|
||||||
|
self.assertRaisesRegex(exception.InvalidParameterValue, 'fridge',
|
||||||
|
agent.validate_http_provisioning_configuration,
|
||||||
|
self.node)
|
||||||
|
|
||||||
|
|
||||||
class TestAgentDeploy(db_base.DbTestCase):
|
class TestAgentDeploy(db_base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -2106,6 +2106,78 @@ class TestBuildInstanceInfoForHttpProvisioning(db_base.DbTestCase):
|
|||||||
validate_href_mock.assert_called_once_with(
|
validate_href_mock.assert_called_once_with(
|
||||||
mock.ANY, expected_url, False)
|
mock.ANY, expected_url, False)
|
||||||
|
|
||||||
|
@mock.patch.object(image_service.HttpImageService, 'validate_href',
|
||||||
|
autospec=True)
|
||||||
|
def test_build_instance_info_local_image_via_iinfo(self,
|
||||||
|
validate_href_mock):
|
||||||
|
cfg.CONF.set_override('image_download_source', 'http', group='agent')
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
driver_internal_info = self.node.driver_internal_info
|
||||||
|
i_info['image_source'] = 'http://image-ref'
|
||||||
|
i_info['image_checksum'] = 'aa'
|
||||||
|
i_info['root_gb'] = 10
|
||||||
|
i_info['image_checksum'] = 'aa'
|
||||||
|
i_info['image_download_source'] = 'local'
|
||||||
|
driver_internal_info['is_whole_disk_image'] = True
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
self.node.driver_internal_info = driver_internal_info
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
expected_url = (
|
||||||
|
'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)
|
||||||
|
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=False) as task:
|
||||||
|
|
||||||
|
info = utils.build_instance_info_for_deploy(task)
|
||||||
|
|
||||||
|
self.assertEqual(expected_url, info['image_url'])
|
||||||
|
self.assertEqual('sha256', info['image_os_hash_algo'])
|
||||||
|
self.assertEqual('fake-checksum', info['image_os_hash_value'])
|
||||||
|
self.cache_image_mock.assert_called_once_with(
|
||||||
|
task.context, task.node, force_raw=True)
|
||||||
|
self.checksum_mock.assert_called_once_with(
|
||||||
|
self.fake_path, algorithm='sha256')
|
||||||
|
validate_href_mock.assert_called_once_with(
|
||||||
|
mock.ANY, expected_url, False)
|
||||||
|
|
||||||
|
@mock.patch.object(image_service.HttpImageService, 'validate_href',
|
||||||
|
autospec=True)
|
||||||
|
def test_build_instance_info_local_image_via_dinfo(self,
|
||||||
|
validate_href_mock):
|
||||||
|
cfg.CONF.set_override('image_download_source', 'http', group='agent')
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
d_info = self.node.driver_info
|
||||||
|
driver_internal_info = self.node.driver_internal_info
|
||||||
|
i_info['image_source'] = 'http://image-ref'
|
||||||
|
i_info['image_checksum'] = 'aa'
|
||||||
|
i_info['root_gb'] = 10
|
||||||
|
i_info['image_checksum'] = 'aa'
|
||||||
|
d_info['image_download_source'] = 'local'
|
||||||
|
driver_internal_info['is_whole_disk_image'] = True
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
self.node.driver_info = d_info
|
||||||
|
self.node.driver_internal_info = driver_internal_info
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
expected_url = (
|
||||||
|
'http://172.172.24.10:8080/agent_images/%s' % self.node.uuid)
|
||||||
|
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=False) as task:
|
||||||
|
|
||||||
|
info = utils.build_instance_info_for_deploy(task)
|
||||||
|
|
||||||
|
self.assertEqual(expected_url, info['image_url'])
|
||||||
|
self.assertEqual('sha256', info['image_os_hash_algo'])
|
||||||
|
self.assertEqual('fake-checksum', info['image_os_hash_value'])
|
||||||
|
self.cache_image_mock.assert_called_once_with(
|
||||||
|
task.context, task.node, force_raw=True)
|
||||||
|
self.checksum_mock.assert_called_once_with(
|
||||||
|
self.fake_path, algorithm='sha256')
|
||||||
|
validate_href_mock.assert_called_once_with(
|
||||||
|
mock.ANY, expected_url, False)
|
||||||
|
|
||||||
|
|
||||||
class TestStorageInterfaceUtils(db_base.DbTestCase):
|
class TestStorageInterfaceUtils(db_base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The ``image_download_source`` configuration option can now also be set
|
||||||
|
per node in the ``instance_info`` or ``driver_info`` (the former having
|
||||||
|
the highest priority).
|
Loading…
Reference in New Issue
Block a user