Merge "Refactor publishing images into a new module"

This commit is contained in:
Zuul 2023-10-11 09:53:49 +00:00 committed by Gerrit Code Review
commit 51aaa37b72
4 changed files with 390 additions and 296 deletions

View File

@ -0,0 +1,187 @@
# 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.
import abc
import os.path
import shutil
from urllib import parse as urlparse
from ironic_lib import utils as ironic_utils
from oslo_log import log
from ironic.common import exception
from ironic.common import swift
from ironic.common import utils
from ironic.conf import CONF
LOG = log.getLogger(__name__)
class AbstractPublisher(metaclass=abc.ABCMeta):
"""Abstract base class for publishing images via HTTP."""
@abc.abstractmethod
def publish(self, source_path, file_name=None):
"""Publish an image.
:param source_path: Path to the source file.
:param file_name: Destination file name. If None, the file component
of source_path is used.
:return: The HTTP URL of the published image.
"""
@abc.abstractmethod
def unpublish(self, file_name):
"""Unpublish the image.
:param file_name: File name to unpublish.
"""
class LocalPublisher(AbstractPublisher):
"""Image publisher using a local web server."""
def __init__(self, image_subdir=None, file_permission=0o644,
dir_permission=0o755, root_url=None):
"""Create a local publisher.
:param image_subdir: A subdirectory to put the image to.
Using an empty directory may cause name conflicts.
:param file_permission: Permissions for copied files.
:param dir_permission: Permissions for created directories.
:param root_url: Public URL of the web server. If empty, determined
from the configuration.
"""
self.image_subdir = image_subdir
self.root_url = (root_url or CONF.deploy.external_http_url
or CONF.deploy.http_url)
self.file_permission = file_permission
self.dir_permission = dir_permission
def publish(self, source_path, file_name=None):
if not file_name:
file_name = os.path.basename(source_path)
if self.image_subdir:
public_dir = os.path.join(CONF.deploy.http_root, self.image_subdir)
else:
public_dir = CONF.deploy.http_root
if not os.path.exists(public_dir):
os.mkdir(public_dir, self.dir_permission)
published_file = os.path.join(public_dir, file_name)
try:
os.link(source_path, published_file)
os.chmod(source_path, 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(
"Could not hardlink image file %(image)s to public "
"location %(public)s (will copy it over): "
"%(error)s", {'image': source_path,
'public': published_file,
'error': exc})
shutil.copyfile(source_path, published_file)
os.chmod(published_file, self.file_permission)
if self.image_subdir:
return os.path.join(self.root_url, self.image_subdir, file_name)
else:
return os.path.join(self.root_url, file_name)
def unpublish(self, file_name):
published_file = os.path.join(
CONF.deploy.http_root, self.image_subdir, file_name)
ironic_utils.unlink_without_raise(published_file)
class SwiftPublisher(AbstractPublisher):
"""Image publisher using OpenStack Swift."""
def __init__(self, container, delete_after):
"""Create a Swift publisher.
:param container: Swift container to use.
:param delete_after: Number of seconds after which the link will
no longer be valid.
"""
self.container = container
self.delete_after = delete_after
def _append_filename_param(self, url, filename):
"""Append 'filename=<file>' parameter to given URL.
Some BMCs seem to validate boot image URL requiring the URL to end
with something resembling ISO image file name.
This function tries to add, hopefully, meaningless 'filename'
parameter to URL's query string in hope to make the entire boot image
URL looking more convincing to the BMC.
However, `url` with fragments might not get cured by this hack.
:param url: a URL to work on
:param filename: name of the file to append to the URL
:returns: original URL with 'filename' parameter appended
"""
parsed_url = urlparse.urlparse(url)
parsed_qs = urlparse.parse_qsl(parsed_url.query)
has_filename = [x for x in parsed_qs if x[0].lower() == 'filename']
if has_filename:
return url
parsed_qs.append(('filename', filename))
parsed_url = list(parsed_url)
parsed_url[4] = urlparse.urlencode(parsed_qs)
return urlparse.urlunparse(parsed_url)
def publish(self, source_path, file_name=None):
api = swift.SwiftAPI()
if not file_name:
file_name = os.path.basename(source_path)
object_headers = {'X-Delete-After': str(self.delete_after)}
api.create_object(self.container, file_name, source_path,
object_headers=object_headers)
image_url = api.get_temp_url(self.container, file_name,
self.delete_after)
return self._append_filename_param(
image_url, os.path.basename(source_path))
def unpublish(self, file_name):
api = swift.SwiftAPI()
LOG.debug("Cleaning up image %(name)s from Swift container "
"%(container)s", {'name': file_name,
'container': self.container})
try:
api.delete_object(self.container, file_name)
except exception.SwiftOperationError as exc:
LOG.warning("Failed to clean up image %(image)s. Error: "
"%(error)s.", {'image': file_name, 'error': exc})

View File

@ -22,15 +22,14 @@ import shutil
import tempfile
from urllib import parse as urlparse
from ironic_lib import utils as ironic_utils
from oslo_log import log
from ironic.common import exception
from ironic.common.glance_service import service_utils
from ironic.common.i18n import _
from ironic.common import image_publisher
from ironic.common import images
from ironic.common import states
from ironic.common import swift
from ironic.common import utils
from ironic.conf import CONF
from ironic.drivers.modules import boot_mode_utils
@ -82,12 +81,15 @@ class ImageHandler(object):
},
}
self._driver = driver
self.swift_enabled = _SWIFT_MAP[driver].get("swift_enabled")
self._container = _SWIFT_MAP[driver].get("container")
self._timeout = _SWIFT_MAP[driver].get("timeout")
self._image_subdir = _SWIFT_MAP[driver].get("image_subdir")
self._file_permission = _SWIFT_MAP[driver].get("file_permission")
if _SWIFT_MAP[driver].get("swift_enabled"):
self._publisher = image_publisher.SwiftPublisher(
container=_SWIFT_MAP[driver].get("container"),
delete_after=_SWIFT_MAP[driver].get("timeout"))
else:
self._publisher = image_publisher.LocalPublisher(
image_subdir=_SWIFT_MAP[driver].get("image_subdir"),
file_permission=_SWIFT_MAP[driver].get("file_permission"))
# To get the kernel parameters
self.kernel_params = _SWIFT_MAP[driver].get("kernel_params")
@ -100,28 +102,7 @@ class ImageHandler(object):
:param object_name: name of the published file (optional)
"""
if self.swift_enabled:
container = self._container
swift_api = swift.SwiftAPI()
LOG.debug("Cleaning up image %(name)s from Swift container "
"%(container)s", {'name': object_name,
'container': container})
try:
swift_api.delete_object(container, object_name)
except exception.SwiftOperationError as exc:
LOG.warning("Failed to clean up image %(image)s. Error: "
"%(error)s.", {'image': object_name,
'error': exc})
else:
published_file = os.path.join(
CONF.deploy.http_root, self._image_subdir, object_name)
ironic_utils.unlink_without_raise(published_file)
self._publisher.unpublish(object_name)
@classmethod
def unpublish_image_for_node(cls, node, prefix='', suffix=''):
@ -140,35 +121,6 @@ class ImageHandler(object):
LOG.debug('Removed image %(name)s for node %(node)s',
{'node': node.uuid, 'name': name})
def _append_filename_param(self, url, filename):
"""Append 'filename=<file>' parameter to given URL.
Some BMCs seem to validate boot image URL requiring the URL to end
with something resembling ISO image file name.
This function tries to add, hopefully, meaningless 'filename'
parameter to URL's query string in hope to make the entire boot image
URL looking more convincing to the BMC.
However, `url` with fragments might not get cured by this hack.
:param url: a URL to work on
:param filename: name of the file to append to the URL
:returns: original URL with 'filename' parameter appended
"""
parsed_url = urlparse.urlparse(url)
parsed_qs = urlparse.parse_qsl(parsed_url.query)
has_filename = [x for x in parsed_qs if x[0].lower() == 'filename']
if has_filename:
return url
parsed_qs.append(('filename', filename))
parsed_url = list(parsed_url)
parsed_url[4] = urlparse.urlencode(parsed_qs)
return urlparse.urlunparse(parsed_url)
def publish_image(self, image_file, object_name, node_http_url=None):
"""Make image file downloadable.
@ -183,61 +135,9 @@ class ImageHandler(object):
from CONF.deploy won't be used.
:return: a URL to download published file
"""
if self.swift_enabled:
container = self._container
timeout = self._timeout
object_headers = {'X-Delete-After': str(timeout)}
swift_api = swift.SwiftAPI()
swift_api.create_object(container, object_name, image_file,
object_headers=object_headers)
image_url = swift_api.get_temp_url(container, object_name, timeout)
image_url = self._append_filename_param(
image_url, os.path.basename(image_file))
else:
public_dir = os.path.join(CONF.deploy.http_root,
self._image_subdir)
if not os.path.exists(public_dir):
os.mkdir(public_dir, 0o755)
published_file = os.path.join(public_dir, object_name)
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(
"Could not hardlink image file %(image)s to public "
"location %(public)s (will copy it over): "
"%(error)s", {'image': image_file,
'public': published_file,
'error': exc})
shutil.copyfile(image_file, published_file)
os.chmod(published_file, self._file_permission)
http_url = (node_http_url or CONF.deploy.external_http_url
or CONF.deploy.http_url)
image_url = os.path.join(http_url, self._image_subdir, object_name)
return image_url
if node_http_url:
self._publisher.root_url = node_http_url
return self._publisher.publish(image_file, object_name)
@image_cache.cleanup(priority=75)

View File

@ -0,0 +1,184 @@
# 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.
import os
import shutil
from unittest import mock
from ironic_lib import utils as ironic_utils
from ironic.common import image_publisher
from ironic.common import utils
from ironic.tests.unit.db import base as db_base
class SwiftPublisherTestCase(db_base.DbTestCase):
container = "test"
publisher = image_publisher.SwiftPublisher(container, 42)
def test__append_filename_param_without_qs(self):
res = self.publisher._append_filename_param(
'http://a.b/c', 'b.img')
expected = 'http://a.b/c?filename=b.img'
self.assertEqual(expected, res)
def test__append_filename_param_with_qs(self):
res = self.publisher._append_filename_param(
'http://a.b/c?d=e&f=g', 'b.img')
expected = 'http://a.b/c?d=e&f=g&filename=b.img'
self.assertEqual(expected, res)
def test__append_filename_param_with_filename(self):
res = self.publisher._append_filename_param(
'http://a.b/c?filename=bootme.img', 'b.img')
expected = 'http://a.b/c?filename=bootme.img'
self.assertEqual(expected, res)
@mock.patch.object(image_publisher, 'swift', autospec=True)
def test_publish(self, mock_swift):
mock_swift_api = mock_swift.SwiftAPI.return_value
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
url = self.publisher.publish('file.iso', 'boot.iso')
self.assertEqual(
'https://a.b/c.f?e=f&filename=file.iso', url)
mock_swift.SwiftAPI.assert_called_once_with()
mock_swift_api.create_object.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY, mock.ANY)
mock_swift_api.get_temp_url.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY)
@mock.patch.object(image_publisher, 'swift', autospec=True)
def test_unpublish(self, mock_swift):
object_name = 'boot.iso'
self.publisher.unpublish(object_name)
mock_swift.SwiftAPI.assert_called_once_with()
mock_swift_api = mock_swift.SwiftAPI.return_value
mock_swift_api.delete_object.assert_called_once_with(
self.container, object_name)
class LocalPublisherTestCase(db_base.DbTestCase):
def setUp(self):
super().setUp()
self.config(http_url='http://localhost', group='deploy')
self.publisher = image_publisher.LocalPublisher('redfish')
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(os, 'chmod', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_local_link(
self, mock_mkdir, mock_link, mock_chmod, mock_execute):
url = self.publisher.publish('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(shutil, 'copyfile', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_local_link_no_restorecon(
self, mock_mkdir, mock_link, mock_copyfile, mock_chmod,
mock_execute):
url = self.publisher.publish('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_copyfile.assert_not_called()
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(os, 'chmod', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_external_ip(
self, mock_mkdir, mock_link, mock_chmod, mock_execute):
self.config(external_http_url='http://non-local.host', group='deploy')
self.publisher = image_publisher.LocalPublisher(image_subdir='redfish')
url = self.publisher.publish('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(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_external_ip_node_override(
self, mock_mkdir, mock_link, mock_chmod, mock_execute):
self.config(external_http_url='http://non-local.host', group='deploy')
override_url = "http://node.override.url"
self.publisher = image_publisher.LocalPublisher(
image_subdir='redfish', root_url=override_url)
url = self.publisher.publish('file.iso', 'boot.iso')
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(shutil, 'copyfile', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_local_copy(self, mock_mkdir, mock_link,
mock_copyfile, mock_chmod):
mock_link.side_effect = OSError()
url = self.publisher.publish('file.iso', 'boot.iso')
self.assertEqual(
'http://localhost/redfish/boot.iso', url)
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
mock_copyfile.assert_called_once_with(
'file.iso', '/httpboot/redfish/boot.iso')
mock_chmod.assert_called_once_with('/httpboot/redfish/boot.iso',
0o644)
@mock.patch.object(ironic_utils, 'unlink_without_raise', autospec=True)
def test_unpublish_local(self, mock_unlink):
object_name = 'boot.iso'
expected_file = '/httpboot/redfish/' + object_name
self.publisher.unpublish(object_name)
mock_unlink.assert_called_once_with(expected_file)

View File

@ -50,190 +50,13 @@ class RedfishImageHandlerTestCase(db_base.DbTestCase):
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
def test__append_filename_param_without_qs(self):
def test_redfish_kernel_param_config(self):
self.config(kernel_append_params="console=ttyS1", group='redfish')
img_handler_obj = image_utils.ImageHandler(self.node.driver)
res = img_handler_obj._append_filename_param(
'http://a.b/c', 'b.img')
expected = 'http://a.b/c?filename=b.img'
self.assertEqual(expected, res)
actual_k_param = img_handler_obj.kernel_params
expected_k_param = "console=ttyS1"
def test__append_filename_param_with_qs(self):
img_handler_obj = image_utils.ImageHandler(self.node.driver)
res = img_handler_obj._append_filename_param(
'http://a.b/c?d=e&f=g', 'b.img')
expected = 'http://a.b/c?d=e&f=g&filename=b.img'
self.assertEqual(expected, res)
def test__append_filename_param_with_filename(self):
img_handler_obj = image_utils.ImageHandler(self.node.driver)
res = img_handler_obj._append_filename_param(
'http://a.b/c?filename=bootme.img', 'b.img')
expected = 'http://a.b/c?filename=bootme.img'
self.assertEqual(expected, res)
@mock.patch.object(image_utils, 'swift', autospec=True)
def test_publish_image_swift(self, mock_swift):
img_handler_obj = image_utils.ImageHandler(self.node.driver)
mock_swift_api = mock_swift.SwiftAPI.return_value
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
url = img_handler_obj.publish_image('file.iso', 'boot.iso')
self.assertEqual(
'https://a.b/c.f?e=f&filename=file.iso', url)
mock_swift.SwiftAPI.assert_called_once_with()
mock_swift_api.create_object.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY, mock.ANY)
mock_swift_api.get_temp_url.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY)
@mock.patch.object(image_utils, 'swift', autospec=True)
def test_unpublish_image_swift(self, mock_swift):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
img_handler_obj = image_utils.ImageHandler(self.node.driver)
object_name = 'image-%s' % task.node.uuid
img_handler_obj.unpublish_image(object_name)
mock_swift.SwiftAPI.assert_called_once_with()
mock_swift_api = mock_swift.SwiftAPI.return_value
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,
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,
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,
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)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(os, 'mkdir', autospec=True)
def test_publish_image_local_copy(self, mock_mkdir, mock_link,
mock_shutil, mock_chmod):
self.config(use_swift=False, group='redfish')
self.config(http_url='http://localhost', group='deploy')
img_handler_obj = image_utils.ImageHandler(self.node.driver)
mock_link.side_effect = OSError()
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_shutil.copyfile.assert_called_once_with(
'file.iso', '/httpboot/redfish/boot.iso')
mock_chmod.assert_called_once_with('/httpboot/redfish/boot.iso',
0o644)
@mock.patch.object(image_utils, 'ironic_utils', autospec=True)
def test_unpublish_image_local(self, mock_ironic_utils):
self.config(use_swift=False, group='redfish')
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
img_handler_obj = image_utils.ImageHandler(self.node.driver)
object_name = 'image-%s' % task.node.uuid
expected_file = '/httpboot/redfish/' + object_name
img_handler_obj.unpublish_image(object_name)
mock_ironic_utils.unlink_without_raise.assert_called_once_with(
expected_file)
self.assertEqual(expected_k_param, actual_k_param)
class IloImageHandlerTestCase(db_base.DbTestCase):