Files
ironic-python-agent/ironic_python_agent/tests/unit/base.py
Jay Faulkner e303a369dc Inspect non-raw images for safety
When IPA gets a non-raw image, it performs an on-the-fly conversion
using qemu-img convert, as well as running qemu-img frequently to get
basic information about the image before validating it.

Now, we ensure that before any qemu-img calls are made, that we have
inspected the image for safety and pass through the detected format.

If given a disk_format=raw image and image streaming is enabled
(default), we retain the existing behavior of not inspecting it in
any way and streaming it bit-perfect to the device. In this case, we
never use qemu-based tools on the image at all.

If given a disk_format=raw image and image streaming is disabled, this
change fixes a bug where the image may have been converted if it was not
actually raw in the first place. We now stream these bit-perfect to the
device.

Adds two config options:
- [DEFAULT]/disable_deep_image_inspection, which can be set to "True" in
  order to disable all security features. Do not do this.
- [DEFAULT]/permitted_image_formats, default raw,qcow2, for image types
  IPA should accept.

Both of these configuration options are wired up to be set by the lookup
data returned by Ironic at lookup time.

This uses a image format inspection module imported from Nova; this
inspector will eventually live in oslo.utils, at which point we'll
migrate our usage of the inspector to it.

Closes-Bug: #2071740
Change-Id: I5254b80717cb5a7f9084e3eff32a00b968f987b7
2024-09-04 09:11:28 -07:00

92 lines
3.3 KiB
Python

# Copyright 2017 Cisco Systems, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common utilities and classes across all unit tests."""
import subprocess
import ironic_lib
from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_config import fixture as config_fixture
from oslo_log import log
from oslo_service import sslutils
from oslotest import base as test_base
from ironic_python_agent import config
from ironic_python_agent.extensions import base as ext_base
from ironic_python_agent import hardware
CONF = cfg.CONF
class IronicAgentTest(test_base.BaseTestCase):
"""Extends the base test to provide common features across agent tests."""
# By default block execution of utils.execute() and related functions.
block_execute = True
def setUp(self):
super(IronicAgentTest, self).setUp()
config.populate_config()
self._set_config()
# Ban running external processes via 'execute' like functions. If the
# patched function is called, an exception is raised to warn the
# tester.
if self.block_execute:
# NOTE(jlvillal): pyudev.Context() calls ctypes.find_library()
# which calls subprocess.Popen(). So not blocking
# subprocess.Popen()
# NOTE(jlvillal): Intentionally not using mock as if you mock a
# mock it causes things to not work correctly. As doing an
# autospec=True causes strangeness. By using a simple function we
# can then mock it without issue.
self.patch(ironic_lib.utils, 'execute', do_not_call)
self.patch(processutils, 'execute', do_not_call)
self.patch(subprocess, 'call', do_not_call)
self.patch(subprocess, 'check_call', do_not_call)
self.patch(subprocess, 'check_output', do_not_call)
# ironic_python_agent.utils.execute is an alias of ironic_lib one
ext_base._EXT_MANAGER = None
hardware._CACHED_HW_INFO = None
hardware._global_managers = None
def _set_config(self):
self.cfg_fixture = self.useFixture(config_fixture.Config(CONF))
# Register options from libraries that are explicitly used in the code
log.register_options(CONF)
sslutils.register_opts(CONF)
def config(self, **kw):
"""Override config options for a test."""
self.cfg_fixture.config(**kw)
def set_defaults(self, **kw):
"""Set default values of config options."""
group = kw.pop('group', None)
for o, v in kw.items():
self.cfg_fixture.set_default(o, v, group=group)
def do_not_call(*args, **kwargs):
"""Helper function to raise an exception if it is called"""
raise Exception(
"Don't call ironic_lib.utils.execute() / "
"processutils.execute() or similar functions in tests!")