summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ironic/common/glance_service/image_service.py3
-rw-r--r--ironic/common/utils.py15
-rw-r--r--ironic/conf/glance.py1
-rw-r--r--ironic/drivers/modules/image_utils.py10
-rw-r--r--ironic/drivers/modules/redfish/raid.py8
-rw-r--r--ironic/tests/unit/common/test_glance_service.py27
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_inspect.py25
-rw-r--r--ironic/tests/unit/drivers/modules/redfish/test_raid.py4
-rw-r--r--ironic/tests/unit/drivers/modules/test_image_utils.py47
-rw-r--r--releasenotes/config.yaml5
-rw-r--r--releasenotes/notes/fix-context-image-hardlink-16f452974abc7327.yaml7
-rw-r--r--releasenotes/notes/fix-nonetype-object-is-not-iterable-0592926d890d6c11.yaml7
-rw-r--r--reno.yaml4
-rw-r--r--tox.ini9
14 files changed, 124 insertions, 48 deletions
diff --git a/ironic/common/glance_service/image_service.py b/ironic/common/glance_service/image_service.py
index 0a32eaf0a..1d9d6d4bc 100644
--- a/ironic/common/glance_service/image_service.py
+++ b/ironic/common/glance_service/image_service.py
@@ -33,6 +33,7 @@ from ironic.common.glance_service import service_utils
from ironic.common.i18n import _
from ironic.common import keystone
from ironic.common import swift
+from ironic.common import utils
from ironic.conf import CONF
TempUrlCacheElement = collections.namedtuple('TempUrlCacheElement',
@@ -114,7 +115,7 @@ class GlanceImageService(object):
@tenacity.retry(
retry=tenacity.retry_if_exception_type(
exception.GlanceConnectionFailed),
- stop=tenacity.stop_after_attempt(CONF.glance.num_retries + 1),
+ stop=utils.stop_after_retries('num_retries', group='glance'),
wait=tenacity.wait_fixed(1),
reraise=True
)
diff --git a/ironic/common/utils.py b/ironic/common/utils.py
index e4d83c9b6..9ae88d4d6 100644
--- a/ironic/common/utils.py
+++ b/ironic/common/utils.py
@@ -681,3 +681,18 @@ def is_fips_enabled():
except Exception:
pass
return False
+
+
+def stop_after_retries(option, group=None):
+ """A tenacity retry helper that stops after retries specified in conf."""
+ # NOTE(dtantsur): fetch the option inside of the nested call, otherwise it
+ # cannot be changed in runtime.
+ def should_stop(retry_state):
+ if group:
+ conf = getattr(CONF, group)
+ else:
+ conf = CONF
+ num_retries = getattr(conf, option)
+ return retry_state.attempt_number >= num_retries + 1
+
+ return should_stop
diff --git a/ironic/conf/glance.py b/ironic/conf/glance.py
index a3286b1eb..317f213bc 100644
--- a/ironic/conf/glance.py
+++ b/ironic/conf/glance.py
@@ -114,6 +114,7 @@ opts = [
'will determine how many containers are created.')),
cfg.IntOpt('num_retries',
default=0,
+ mutable=True,
help=_('Number of retries when downloading an image from '
'glance.')),
]
diff --git a/ironic/drivers/modules/image_utils.py b/ironic/drivers/modules/image_utils.py
index 304c199bf..86607ee25 100644
--- a/ironic/drivers/modules/image_utils.py
+++ b/ironic/drivers/modules/image_utils.py
@@ -211,6 +211,16 @@ class ImageHandler(object):
try:
os.link(image_file, published_file)
os.chmod(image_file, self._file_permission)
+ try:
+ utils.execute(
+ '/usr/sbin/restorecon', '-i', '-R', 'v', public_dir)
+ except FileNotFoundError as exc:
+ LOG.debug(
+ "Could not restore SELinux context on "
+ "%(public_dir)s, restorecon command not found.\n"
+ "Error: %(error)s",
+ {'public_dir': public_dir,
+ 'error': exc})
except OSError as exc:
LOG.debug(
diff --git a/ironic/drivers/modules/redfish/raid.py b/ironic/drivers/modules/redfish/raid.py
index 809ec59c6..154cd53d3 100644
--- a/ironic/drivers/modules/redfish/raid.py
+++ b/ironic/drivers/modules/redfish/raid.py
@@ -1120,7 +1120,9 @@ class RedfishRAID(base.RAIDInterface):
raid_configs['pending'].setdefault(controller, []).append(
logical_disk)
- node.set_driver_internal_info('raid_configs', raid_configs)
+ # Store only when async operation
+ if reboot_required:
+ node.set_driver_internal_info('raid_configs', raid_configs)
return raid_configs, reboot_required
@@ -1182,7 +1184,9 @@ class RedfishRAID(base.RAIDInterface):
response.task_monitor_uri)
reboot_required = True
- node.set_driver_internal_info('raid_configs', raid_configs)
+ # Store only when async operation
+ if reboot_required:
+ node.set_driver_internal_info('raid_configs', raid_configs)
return raid_configs, reboot_required
diff --git a/ironic/tests/unit/common/test_glance_service.py b/ironic/tests/unit/common/test_glance_service.py
index 6be0fccd9..a6dd11d3e 100644
--- a/ironic/tests/unit/common/test_glance_service.py
+++ b/ironic/tests/unit/common/test_glance_service.py
@@ -24,7 +24,6 @@ from glanceclient import exc as glance_exc
from keystoneauth1 import loading as ks_loading
from oslo_config import cfg
from oslo_utils import uuidutils
-import tenacity
import testtools
from ironic.common import context
@@ -204,20 +203,18 @@ class TestGlanceImageService(base.TestCase):
image_id = uuidutils.generate_uuid()
writer = NullWriter()
- with mock.patch.object(tenacity, 'retry', autospec=True) as mock_retry:
- # When retries are disabled, we should get an exception
- self.config(num_retries=0, group='glance')
- self.assertRaises(exception.GlanceConnectionFailed,
- stub_service.download, image_id, writer)
-
- # Now lets enable retries. No exception should happen now.
- self.config(num_retries=1, group='glance')
- importlib.reload(image_service)
- stub_service = image_service.GlanceImageService(stub_client,
- stub_context)
- tries = [0]
- stub_service.download(image_id, writer)
- mock_retry.assert_called_once()
+ # When retries are disabled, we should get an exception
+ self.config(num_retries=0, group='glance')
+ self.assertRaises(exception.GlanceConnectionFailed,
+ stub_service.download, image_id, writer)
+
+ # Now lets enable retries. No exception should happen now.
+ self.config(num_retries=1, group='glance')
+ importlib.reload(image_service)
+ stub_service = image_service.GlanceImageService(stub_client,
+ stub_context)
+ tries = [0]
+ stub_service.download(image_id, writer)
def test_download_no_data(self):
self.client.fake_wrapped = None
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_inspect.py b/ironic/tests/unit/drivers/modules/irmc/test_inspect.py
index 5c66cb96a..da91ec61d 100644
--- a/ironic/tests/unit/drivers/modules/irmc/test_inspect.py
+++ b/ironic/tests/unit/drivers/modules/irmc/test_inspect.py
@@ -204,8 +204,8 @@ class IRMCInspectTestCase(test_common.BaseIRMCTest):
_inspect_hardware_mock.return_value = (inspected_props,
inspected_macs,
new_traits)
- new_port_mock1 = mock.MagicMock(spec=objects.Port)
- new_port_mock2 = mock.MagicMock(spec=objects.Port)
+ new_port_mock1 = objects.Port
+ new_port_mock2 = objects.Port
port_mock.side_effect = [new_port_mock1, new_port_mock2]
@@ -220,11 +220,11 @@ class IRMCInspectTestCase(test_common.BaseIRMCTest):
port_mock.assert_has_calls([
mock.call(task.context, address=inspected_macs[0],
node_id=node_id),
+ mock.call.create(),
mock.call(task.context, address=inspected_macs[1],
- node_id=node_id)
- ])
- new_port_mock1.create.assert_called_once_with()
- new_port_mock2.create.assert_called_once_with()
+ node_id=node_id),
+ mock.call.create()
+ ], any_order=False)
self.assertTrue(info_mock.called)
task.node.refresh()
@@ -259,8 +259,9 @@ class IRMCInspectTestCase(test_common.BaseIRMCTest):
_inspect_hardware_mock.return_value = (inspected_props,
inspected_macs,
new_traits)
- new_port_mock1 = mock.MagicMock(spec=objects.Port)
- new_port_mock2 = mock.MagicMock(spec=objects.Port)
+
+ new_port_mock1 = objects.Port
+ new_port_mock2 = objects.Port
port_mock.side_effect = [new_port_mock1, new_port_mock2]
@@ -276,11 +277,11 @@ class IRMCInspectTestCase(test_common.BaseIRMCTest):
port_mock.assert_has_calls([
mock.call(task.context, address=inspected_macs[0],
node_id=node_id),
+ mock.call.create(),
mock.call(task.context, address=inspected_macs[1],
- node_id=node_id)
- ])
- new_port_mock1.create.assert_called_once_with()
- new_port_mock2.create.assert_called_once_with()
+ node_id=node_id),
+ mock.call.create()
+ ], any_order=False)
self.assertTrue(info_mock.called)
task.node.refresh()
diff --git a/ironic/tests/unit/drivers/modules/redfish/test_raid.py b/ironic/tests/unit/drivers/modules/redfish/test_raid.py
index dfb3c1473..843be735c 100644
--- a/ironic/tests/unit/drivers/modules/redfish/test_raid.py
+++ b/ironic/tests/unit/drivers/modules/redfish/test_raid.py
@@ -336,6 +336,8 @@ class RedfishRAIDTestCase(db_base.DbTestCase):
self.assertEqual(mock_node_power_action.call_count, 0)
self.assertEqual(mock_build_agent_options.call_count, 0)
self.assertEqual(mock_prepare_ramdisk.call_count, 0)
+ self.assertIsNone(
+ task.node.driver_internal_info.get('raid_configs'))
self.assertEqual(
[{'controller': 'RAID controller 1',
'id': '1',
@@ -1066,6 +1068,8 @@ class RedfishRAIDTestCase(db_base.DbTestCase):
self.assertEqual(mock_node_power_action.call_count, 0)
self.assertEqual(mock_build_agent_options.call_count, 0)
self.assertEqual(mock_prepare_ramdisk.call_count, 0)
+ self.assertIsNone(
+ task.node.driver_internal_info.get('raid_configs'))
self.assertEqual([], task.node.raid_config['logical_disks'])
self.assertNotEqual(
last_updated, task.node.raid_config['last_updated'])
diff --git a/ironic/tests/unit/drivers/modules/test_image_utils.py b/ironic/tests/unit/drivers/modules/test_image_utils.py
index 753452f5d..b6c572125 100644
--- a/ironic/tests/unit/drivers/modules/test_image_utils.py
+++ b/ironic/tests/unit/drivers/modules/test_image_utils.py
@@ -105,73 +105,96 @@ class RedfishImageHandlerTestCase(db_base.DbTestCase):
mock_swift_api.delete_object.assert_called_once_with(
'ironic_redfish_container', object_name)
+ @mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(os, 'chmod', autospec=True)
@mock.patch.object(image_utils, 'shutil', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_image_local_link(
- self, mock_mkdir, mock_link, mock_shutil, mock_chmod):
+ self, mock_mkdir, mock_link, mock_shutil, mock_chmod,
+ mock_execute):
self.config(use_swift=False, group='redfish')
self.config(http_url='http://localhost', group='deploy')
img_handler_obj = image_utils.ImageHandler(self.node.driver)
-
url = img_handler_obj.publish_image('file.iso', 'boot.iso')
-
self.assertEqual(
'http://localhost/redfish/boot.iso', url)
+ mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
+ mock_link.assert_called_once_with(
+ 'file.iso', '/httpboot/redfish/boot.iso')
+ mock_chmod.assert_called_once_with('file.iso', 0o644)
+ mock_execute.assert_called_once_with(
+ '/usr/sbin/restorecon', '-i', '-R', 'v', '/httpboot/redfish')
+ @mock.patch.object(utils, 'execute', autospec=True)
+ @mock.patch.object(os, 'chmod', autospec=True)
+ @mock.patch.object(image_utils, 'shutil', autospec=True)
+ @mock.patch.object(os, 'link', autospec=True)
+ @mock.patch.object(os, 'mkdir', autospec=True)
+ def test_publish_image_local_link_no_restorecon(
+ self, mock_mkdir, mock_link, mock_shutil, mock_chmod,
+ mock_execute):
+ self.config(use_swift=False, group='redfish')
+ self.config(http_url='http://localhost', group='deploy')
+ img_handler_obj = image_utils.ImageHandler(self.node.driver)
+ url = img_handler_obj.publish_image('file.iso', 'boot.iso')
+ self.assertEqual(
+ 'http://localhost/redfish/boot.iso', url)
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
mock_link.assert_called_once_with(
'file.iso', '/httpboot/redfish/boot.iso')
mock_chmod.assert_called_once_with('file.iso', 0o644)
+ mock_execute.return_value = FileNotFoundError
+ mock_shutil.assert_not_called()
+ @mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(os, 'chmod', autospec=True)
@mock.patch.object(image_utils, 'shutil', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_image_external_ip(
- self, mock_mkdir, mock_link, mock_shutil, mock_chmod):
+ self, mock_mkdir, mock_link, mock_shutil, mock_chmod,
+ mock_execute):
self.config(use_swift=False, group='redfish')
self.config(http_url='http://localhost',
external_http_url='http://non-local.host',
group='deploy')
img_handler_obj = image_utils.ImageHandler(self.node.driver)
-
url = img_handler_obj.publish_image('file.iso', 'boot.iso')
-
self.assertEqual(
'http://non-local.host/redfish/boot.iso', url)
-
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
mock_link.assert_called_once_with(
'file.iso', '/httpboot/redfish/boot.iso')
mock_chmod.assert_called_once_with('file.iso', 0o644)
+ mock_execute.assert_called_once_with(
+ '/usr/sbin/restorecon', '-i', '-R', 'v', '/httpboot/redfish')
+ @mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(os, 'chmod', autospec=True)
@mock.patch.object(image_utils, 'shutil', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_image_external_ip_node_override(
- self, mock_mkdir, mock_link, mock_shutil, mock_chmod):
+ self, mock_mkdir, mock_link, mock_shutil, mock_chmod,
+ mock_execute):
self.config(use_swift=False, group='redfish')
self.config(http_url='http://localhost',
external_http_url='http://non-local.host',
group='deploy')
img_handler_obj = image_utils.ImageHandler(self.node.driver)
self.node.driver_info["external_http_url"] = "http://node.override.url"
-
override_url = self.node.driver_info.get("external_http_url")
-
url = img_handler_obj.publish_image('file.iso', 'boot.iso',
override_url)
-
self.assertEqual(
'http://node.override.url/redfish/boot.iso', url)
-
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
mock_link.assert_called_once_with(
'file.iso', '/httpboot/redfish/boot.iso')
mock_chmod.assert_called_once_with('file.iso', 0o644)
+ mock_execute.assert_called_once_with(
+ '/usr/sbin/restorecon', '-i', '-R', 'v', '/httpboot/redfish')
@mock.patch.object(os, 'chmod', autospec=True)
@mock.patch.object(image_utils, 'shutil', autospec=True)
diff --git a/releasenotes/config.yaml b/releasenotes/config.yaml
new file mode 100644
index 000000000..26538010e
--- /dev/null
+++ b/releasenotes/config.yaml
@@ -0,0 +1,5 @@
+---
+# Ignore the kilo-eol tag because that branch does not work with reno
+# and contains no release notes.
+# Ignore bugfix tags because their releasenotes are covered under stable
+closed_branch_tag_re: 'r"(?!^(kilo-|bugfix-)).+-eol$"'
diff --git a/releasenotes/notes/fix-context-image-hardlink-16f452974abc7327.yaml b/releasenotes/notes/fix-context-image-hardlink-16f452974abc7327.yaml
new file mode 100644
index 000000000..90d38d5cc
--- /dev/null
+++ b/releasenotes/notes/fix-context-image-hardlink-16f452974abc7327.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Fixes an issue where if selinux is enabled and enforcing, and
+ the published image is a hardlink, the source selinux context
+ is preserved, causing access denied when retrieving the image
+ using hardlink URL.
diff --git a/releasenotes/notes/fix-nonetype-object-is-not-iterable-0592926d890d6c11.yaml b/releasenotes/notes/fix-nonetype-object-is-not-iterable-0592926d890d6c11.yaml
new file mode 100644
index 000000000..ec9043adb
--- /dev/null
+++ b/releasenotes/notes/fix-nonetype-object-is-not-iterable-0592926d890d6c11.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Fixes ``'NoneType' object is not iterable`` in conductor logs for
+ ``redfish`` and ``idrac-redfish`` RAID clean and deploy steps. The message
+ should no longer appear. For affected nodes re-create the node or delete
+ ``raid_configs`` entry from ``driver_internal_info`` field.
diff --git a/reno.yaml b/reno.yaml
deleted file mode 100644
index dd0aac790..000000000
--- a/reno.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-# Ignore the kilo-eol tag because that branch does not work with reno
-# and contains no release notes.
-closed_branch_tag_re: "(.+)(?<!kilo)-eol"
diff --git a/tox.ini b/tox.ini
index c7c5ceecc..b42a0a8d6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,7 +8,7 @@ ignore_basepython_conflict=true
usedevelop = True
basepython = python3
setenv = VIRTUAL_ENV={envdir}
- PYTHONDONTWRITEBYTECODE = 1
+ PYTHONDONTWRITEBYTECODE=1
LANGUAGE=en_US
LC_ALL=en_US.UTF-8
deps =
@@ -17,7 +17,12 @@ deps =
-r{toxinidir}/test-requirements.txt
commands =
stestr run --slowest {posargs}
-passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
+passenv = http_proxy
+ HTTP_PROXY
+ https_proxy
+ HTTPS_PROXY
+ no_proxy
+ NO_PROXY
[testenv:unit-with-driver-libs]
deps = {[testenv]deps}