Merge "Serve virtual media boot images from ironic conductor"
This commit is contained in:
commit
b7eb4a181c
@ -44,13 +44,22 @@ opts = [
|
|||||||
'fall back to basic HTTP authentication'))],
|
'fall back to basic HTTP authentication'))],
|
||||||
default='auto',
|
default='auto',
|
||||||
help=_('Redfish HTTP client authentication method.')),
|
help=_('Redfish HTTP client authentication method.')),
|
||||||
|
cfg.BoolOpt('use_swift',
|
||||||
|
default=True,
|
||||||
|
help=_('Upload generated ISO images for virtual media boot to '
|
||||||
|
'Swift, then pass temporary URL to BMC for booting the '
|
||||||
|
'node. If set to false, images are are placed on the '
|
||||||
|
'ironic-conductor node and served over its '
|
||||||
|
'local HTTP server.')),
|
||||||
cfg.StrOpt('swift_container',
|
cfg.StrOpt('swift_container',
|
||||||
default='ironic_redfish_container',
|
default='ironic_redfish_container',
|
||||||
help=_('The Swift container to store Redfish driver data.')),
|
help=_('The Swift container to store Redfish driver data. '
|
||||||
|
'Applies only when `use_swift` is enabled.')),
|
||||||
cfg.IntOpt('swift_object_expiry_timeout',
|
cfg.IntOpt('swift_object_expiry_timeout',
|
||||||
default=900,
|
default=900,
|
||||||
help=_('Amount of time in seconds for Swift objects to '
|
help=_('Amount of time in seconds for Swift objects to '
|
||||||
'auto-expire.')),
|
'auto-expire. Applies only when `use_swift` is '
|
||||||
|
'enabled.')),
|
||||||
cfg.StrOpt('kernel_append_params',
|
cfg.StrOpt('kernel_append_params',
|
||||||
default='nofb nomodeset vga=normal',
|
default='nofb nomodeset vga=normal',
|
||||||
help=_('Additional kernel parameters for baremetal '
|
help=_('Additional kernel parameters for baremetal '
|
||||||
|
@ -12,8 +12,12 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
from ironic_lib import utils as ironic_utils
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
from six.moves.urllib import parse as urlparse
|
from six.moves.urllib import parse as urlparse
|
||||||
@ -103,6 +107,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
`[instance_info]image_source` node property.
|
`[instance_info]image_source` node property.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
IMAGE_SUBDIR = 'redfish'
|
||||||
|
|
||||||
capabilities = ['iscsi_volume_boot', 'ramdisk_boot']
|
capabilities = ['iscsi_volume_boot', 'ramdisk_boot']
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -200,25 +206,6 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
|
|
||||||
return deploy_info
|
return deploy_info
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _delete_from_swift(task, container, object_name):
|
|
||||||
LOG.debug("Cleaning up image %(name)s from Swift container "
|
|
||||||
"%(container)s for node "
|
|
||||||
"%(node)s", {'node': task.node.uuid,
|
|
||||||
'name': object_name,
|
|
||||||
'container': container})
|
|
||||||
|
|
||||||
swift_api = swift.SwiftAPI()
|
|
||||||
|
|
||||||
try:
|
|
||||||
swift_api.delete_object(container, object_name)
|
|
||||||
|
|
||||||
except exception.SwiftOperationError as e:
|
|
||||||
LOG.warning("Failed to clean up image %(image)s for node "
|
|
||||||
"%(node)s. Error: %(error)s.",
|
|
||||||
{'node': task.node.uuid, 'image': object_name,
|
|
||||||
'error': e})
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _append_filename_param(url, filename):
|
def _append_filename_param(url, filename):
|
||||||
"""Append 'filename=<file>' parameter to given URL.
|
"""Append 'filename=<file>' parameter to given URL.
|
||||||
@ -249,6 +236,94 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
|
|
||||||
return urlparse.urlunparse(parsed_url)
|
return urlparse.urlunparse(parsed_url)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _publish_image(cls, image_file, object_name):
|
||||||
|
"""Make image file downloadable.
|
||||||
|
|
||||||
|
Depending on ironic settings, pushes given file into Swift or copies
|
||||||
|
it over to local HTTP server's document root and returns publicly
|
||||||
|
accessible URL leading to the given file.
|
||||||
|
|
||||||
|
:param image_file: path to file to publish
|
||||||
|
:param object_name: name of the published file
|
||||||
|
:return: a URL to download published file
|
||||||
|
"""
|
||||||
|
|
||||||
|
if CONF.redfish.use_swift:
|
||||||
|
container = CONF.redfish.swift_container
|
||||||
|
timeout = CONF.redfish.swift_object_expiry_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)
|
||||||
|
|
||||||
|
else:
|
||||||
|
public_dir = os.path.join(CONF.deploy.http_root, cls.IMAGE_SUBDIR)
|
||||||
|
|
||||||
|
if not os.path.exists(public_dir):
|
||||||
|
os.mkdir(public_dir, 0x755)
|
||||||
|
|
||||||
|
published_file = os.path.join(public_dir, object_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.link(image_file, published_file)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
image_url = urlparse.urljoin(
|
||||||
|
CONF.deploy.http_url, cls.IMAGE_SUBDIR, object_name)
|
||||||
|
|
||||||
|
image_url = cls._append_filename_param(
|
||||||
|
image_url, os.path.basename(image_file))
|
||||||
|
|
||||||
|
return image_url
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _unpublish_image(cls, object_name):
|
||||||
|
"""Withdraw the image previously made downloadable.
|
||||||
|
|
||||||
|
Depending on ironic settings, removes previously published file
|
||||||
|
from where it has been published - Swift or local HTTP server's
|
||||||
|
document root.
|
||||||
|
|
||||||
|
:param object_name: name of the published file (optional)
|
||||||
|
"""
|
||||||
|
if CONF.redfish.use_swift:
|
||||||
|
container = CONF.redfish.swift_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, cls.IMAGE_SUBDIR, object_name)
|
||||||
|
|
||||||
|
ironic_utils.unlink_without_raise(published_file)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_floppy_image_name(node):
|
def _get_floppy_image_name(node):
|
||||||
"""Returns the floppy image name for a given node.
|
"""Returns the floppy image name for a given node.
|
||||||
@ -265,8 +340,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
"""
|
"""
|
||||||
floppy_object_name = cls._get_floppy_image_name(task.node)
|
floppy_object_name = cls._get_floppy_image_name(task.node)
|
||||||
|
|
||||||
cls._delete_from_swift(
|
cls._unpublish_image(floppy_object_name)
|
||||||
task, CONF.redfish.swift_container, floppy_object_name)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _prepare_floppy_image(cls, task, params=None):
|
def _prepare_floppy_image(cls, task, params=None):
|
||||||
@ -290,12 +364,6 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
"""
|
"""
|
||||||
object_name = cls._get_floppy_image_name(task.node)
|
object_name = cls._get_floppy_image_name(task.node)
|
||||||
|
|
||||||
container = CONF.redfish.swift_container
|
|
||||||
timeout = CONF.redfish.swift_object_expiry_timeout
|
|
||||||
|
|
||||||
object_headers = {'X-Delete-After': str(timeout)}
|
|
||||||
swift_api = swift.SwiftAPI()
|
|
||||||
|
|
||||||
LOG.debug("Trying to create floppy image for node "
|
LOG.debug("Trying to create floppy image for node "
|
||||||
"%(node)s", {'node': task.node.uuid})
|
"%(node)s", {'node': task.node.uuid})
|
||||||
|
|
||||||
@ -305,12 +373,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
vfat_image_tmpfile = vfat_image_tmpfile_obj.name
|
vfat_image_tmpfile = vfat_image_tmpfile_obj.name
|
||||||
images.create_vfat_image(vfat_image_tmpfile, parameters=params)
|
images.create_vfat_image(vfat_image_tmpfile, parameters=params)
|
||||||
|
|
||||||
swift_api.create_object(container, object_name, vfat_image_tmpfile,
|
image_url = cls._publish_image(vfat_image_tmpfile, object_name)
|
||||||
object_headers=object_headers)
|
|
||||||
|
|
||||||
image_url = swift_api.get_temp_url(container, object_name, timeout)
|
|
||||||
|
|
||||||
image_url = cls._append_filename_param(image_url, 'bootme.img')
|
|
||||||
|
|
||||||
LOG.debug("Created floppy image %(name)s in Swift for node %(node)s, "
|
LOG.debug("Created floppy image %(name)s in Swift for node %(node)s, "
|
||||||
"exposed as temporary URL "
|
"exposed as temporary URL "
|
||||||
@ -336,8 +399,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
"""
|
"""
|
||||||
iso_object_name = cls._get_iso_image_name(task.node)
|
iso_object_name = cls._get_iso_image_name(task.node)
|
||||||
|
|
||||||
cls._delete_from_swift(
|
cls._unpublish_image(iso_object_name)
|
||||||
task, CONF.redfish.swift_container, iso_object_name)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _prepare_iso_image(cls, task, kernel_href, ramdisk_href,
|
def _prepare_iso_image(cls, task, kernel_href, ramdisk_href,
|
||||||
@ -406,30 +468,14 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
|
|
||||||
iso_object_name = cls._get_iso_image_name(task.node)
|
iso_object_name = cls._get_iso_image_name(task.node)
|
||||||
|
|
||||||
container = CONF.redfish.swift_container
|
image_url = cls._publish_image(boot_iso_tmp_file, iso_object_name)
|
||||||
|
|
||||||
timeout = CONF.redfish.swift_object_expiry_timeout
|
|
||||||
|
|
||||||
object_headers = {'X-Delete-After': str(timeout)}
|
|
||||||
|
|
||||||
swift_api = swift.SwiftAPI()
|
|
||||||
|
|
||||||
swift_api.create_object(container, iso_object_name,
|
|
||||||
boot_iso_tmp_file,
|
|
||||||
object_headers=object_headers)
|
|
||||||
|
|
||||||
boot_iso_url = swift_api.get_temp_url(
|
|
||||||
container, iso_object_name, timeout)
|
|
||||||
|
|
||||||
boot_iso_url = cls._append_filename_param(
|
|
||||||
boot_iso_url, 'bootme.iso')
|
|
||||||
|
|
||||||
LOG.debug("Created ISO %(name)s in Swift for node %(node)s, exposed "
|
LOG.debug("Created ISO %(name)s in Swift for node %(node)s, exposed "
|
||||||
"as temporary URL %(url)s", {'node': task.node.uuid,
|
"as temporary URL %(url)s", {'node': task.node.uuid,
|
||||||
'name': iso_object_name,
|
'name': iso_object_name,
|
||||||
'url': boot_iso_url})
|
'url': image_url})
|
||||||
|
|
||||||
return boot_iso_url
|
return image_url
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _prepare_deploy_iso(cls, task, params, mode):
|
def _prepare_deploy_iso(cls, task, params, mode):
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
@ -200,35 +202,19 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
self.assertEqual(expected, res)
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
@mock.patch.object(redfish_boot, 'swift', autospec=True)
|
@mock.patch.object(redfish_boot, 'swift', autospec=True)
|
||||||
def test__cleanup_floppy_image(self, mock_swift):
|
def test__publish_image_swift(self, mock_swift):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
task.driver.boot._cleanup_floppy_image(task)
|
|
||||||
|
|
||||||
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', 'image-%s' % task.node.uuid
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch.object(redfish_boot, 'swift', autospec=True)
|
|
||||||
@mock.patch.object(images, 'create_vfat_image', autospec=True)
|
|
||||||
def test__prepare_floppy_image(self, mock_create_vfat_image, mock_swift):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
mock_swift_api = mock_swift.SwiftAPI.return_value
|
||||||
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
|
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
|
||||||
|
|
||||||
url = task.driver.boot._prepare_floppy_image(task)
|
url = task.driver.boot._publish_image('file.iso', 'boot.iso')
|
||||||
|
|
||||||
self.assertIn('filename=bootme.img', url)
|
self.assertEqual(
|
||||||
|
'https://a.b/c.f?e=f&filename=file.iso', url)
|
||||||
|
|
||||||
mock_swift.SwiftAPI.assert_called_once_with()
|
mock_swift.SwiftAPI.assert_called_once_with()
|
||||||
|
|
||||||
mock_create_vfat_image.assert_called_once_with(
|
|
||||||
mock.ANY, parameters=mock.ANY)
|
|
||||||
|
|
||||||
mock_swift_api.create_object.assert_called_once_with(
|
mock_swift_api.create_object.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, mock.ANY, mock.ANY)
|
mock.ANY, mock.ANY, mock.ANY, mock.ANY)
|
||||||
|
|
||||||
@ -236,33 +222,143 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
mock.ANY, mock.ANY, mock.ANY)
|
mock.ANY, mock.ANY, mock.ANY)
|
||||||
|
|
||||||
@mock.patch.object(redfish_boot, 'swift', autospec=True)
|
@mock.patch.object(redfish_boot, 'swift', autospec=True)
|
||||||
def test__cleanup_iso_image(self, mock_swift):
|
def test__unpublish_image_swift(self, mock_swift):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
task.driver.boot._cleanup_iso_image(task)
|
object_name = 'image-%s' % task.node.uuid
|
||||||
|
|
||||||
|
task.driver.boot._unpublish_image(object_name)
|
||||||
|
|
||||||
mock_swift.SwiftAPI.assert_called_once_with()
|
mock_swift.SwiftAPI.assert_called_once_with()
|
||||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
mock_swift_api = mock_swift.SwiftAPI.return_value
|
||||||
|
|
||||||
mock_swift_api.delete_object.assert_called_once_with(
|
mock_swift_api.delete_object.assert_called_once_with(
|
||||||
'ironic_redfish_container', 'boot-%s' % task.node.uuid
|
'ironic_redfish_container', object_name)
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch.object(redfish_boot, 'swift', autospec=True)
|
@mock.patch.object(redfish_boot, '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):
|
||||||
|
self.config(use_swift=False, group='redfish')
|
||||||
|
self.config(http_url='http://localhost', group='deploy')
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
|
||||||
|
url = task.driver.boot._publish_image('file.iso', 'boot.iso')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
'http://localhost/redfish?filename=file.iso', url)
|
||||||
|
|
||||||
|
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0x755)
|
||||||
|
mock_link.assert_called_once_with(
|
||||||
|
'file.iso', '/httpboot/redfish/boot.iso')
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_boot, '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):
|
||||||
|
self.config(use_swift=False, group='redfish')
|
||||||
|
self.config(http_url='http://localhost', group='deploy')
|
||||||
|
|
||||||
|
mock_link.side_effect = OSError()
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
|
||||||
|
url = task.driver.boot._publish_image('file.iso', 'boot.iso')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
'http://localhost/redfish?filename=file.iso', url)
|
||||||
|
|
||||||
|
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0x755)
|
||||||
|
|
||||||
|
mock_shutil.copyfile.assert_called_once_with(
|
||||||
|
'file.iso', '/httpboot/redfish/boot.iso')
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_boot, '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:
|
||||||
|
object_name = 'image-%s' % task.node.uuid
|
||||||
|
|
||||||
|
expected_file = '/httpboot/redfish/' + object_name
|
||||||
|
|
||||||
|
task.driver.boot._unpublish_image(object_name)
|
||||||
|
|
||||||
|
mock_ironic_utils.unlink_without_raise.assert_called_once_with(
|
||||||
|
expected_file)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
|
'_unpublish_image', autospec=True)
|
||||||
|
def test__cleanup_floppy_image(self, mock_unpublish):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.boot._cleanup_floppy_image(task)
|
||||||
|
|
||||||
|
object_name = 'image-%s' % task.node.uuid
|
||||||
|
|
||||||
|
mock_unpublish.assert_called_once_with(object_name)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
|
'_publish_image', autospec=True)
|
||||||
|
@mock.patch.object(images, 'create_vfat_image', autospec=True)
|
||||||
|
def test__prepare_floppy_image(
|
||||||
|
self, mock_create_vfat_image, mock__publish_image):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
expected_url = 'https://a.b/c.f?e=f'
|
||||||
|
|
||||||
|
mock__publish_image.return_value = expected_url
|
||||||
|
|
||||||
|
url = task.driver.boot._prepare_floppy_image(task)
|
||||||
|
|
||||||
|
object_name = 'image-%s' % task.node.uuid
|
||||||
|
|
||||||
|
mock__publish_image.assert_called_once_with(
|
||||||
|
mock.ANY, object_name)
|
||||||
|
|
||||||
|
mock_create_vfat_image.assert_called_once_with(
|
||||||
|
mock.ANY, parameters=mock.ANY)
|
||||||
|
|
||||||
|
self.assertEqual(expected_url, url)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
|
'_unpublish_image', autospec=True)
|
||||||
|
def test__cleanup_iso_image(self, mock_unpublish):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.boot._cleanup_iso_image(task)
|
||||||
|
|
||||||
|
object_name = 'boot-%s' % task.node.uuid
|
||||||
|
|
||||||
|
mock_unpublish.assert_called_once_with(object_name)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
|
'_publish_image', autospec=True)
|
||||||
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
||||||
def test__prepare_iso_image_uefi(self, mock_create_boot_iso, mock_swift):
|
def test__prepare_iso_image_uefi(
|
||||||
|
self, mock_create_boot_iso, mock__publish_image):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
task.node.instance_info.update(deploy_boot_mode='uefi')
|
task.node.instance_info.update(deploy_boot_mode='uefi')
|
||||||
|
|
||||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
expected_url = 'https://a.b/c.f?e=f'
|
||||||
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
|
|
||||||
|
mock__publish_image.return_value = expected_url
|
||||||
|
|
||||||
url = task.driver.boot._prepare_iso_image(
|
url = task.driver.boot._prepare_iso_image(
|
||||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||||
'http://bootloader/img', root_uuid=task.node.uuid)
|
'http://bootloader/img', root_uuid=task.node.uuid)
|
||||||
|
|
||||||
self.assertIn('filename=bootme.iso', url)
|
object_name = 'boot-%s' % task.node.uuid
|
||||||
|
|
||||||
|
mock__publish_image.assert_called_once_with(
|
||||||
|
mock.ANY, object_name)
|
||||||
|
|
||||||
mock_create_boot_iso.assert_called_once_with(
|
mock_create_boot_iso.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
||||||
@ -270,28 +366,28 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
kernel_params='nofb nomodeset vga=normal',
|
kernel_params='nofb nomodeset vga=normal',
|
||||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
|
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
|
||||||
|
|
||||||
mock_swift.SwiftAPI.assert_called_once_with()
|
self.assertEqual(expected_url, url)
|
||||||
|
|
||||||
mock_swift_api.create_object.assert_called_once_with(
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
mock.ANY, mock.ANY, mock.ANY, mock.ANY)
|
'_publish_image', autospec=True)
|
||||||
|
|
||||||
mock_swift_api.get_temp_url.assert_called_once_with(
|
|
||||||
mock.ANY, mock.ANY, mock.ANY)
|
|
||||||
|
|
||||||
@mock.patch.object(redfish_boot, 'swift', autospec=True)
|
|
||||||
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
||||||
def test__prepare_iso_image_bios(self, mock_create_boot_iso, mock_swift):
|
def test__prepare_iso_image_bios(
|
||||||
|
self, mock_create_boot_iso, mock__publish_image):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
|
|
||||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
expected_url = 'https://a.b/c.f?e=f'
|
||||||
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
|
|
||||||
|
mock__publish_image.return_value = expected_url
|
||||||
|
|
||||||
url = task.driver.boot._prepare_iso_image(
|
url = task.driver.boot._prepare_iso_image(
|
||||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||||
bootloader_href=None, root_uuid=task.node.uuid)
|
bootloader_href=None, root_uuid=task.node.uuid)
|
||||||
|
|
||||||
self.assertIn('filename=bootme.iso', url)
|
object_name = 'boot-%s' % task.node.uuid
|
||||||
|
|
||||||
|
mock__publish_image.assert_called_once_with(
|
||||||
|
mock.ANY, object_name)
|
||||||
|
|
||||||
mock_create_boot_iso.assert_called_once_with(
|
mock_create_boot_iso.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
||||||
@ -299,13 +395,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
kernel_params='nofb nomodeset vga=normal',
|
kernel_params='nofb nomodeset vga=normal',
|
||||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
|
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
|
||||||
|
|
||||||
mock_swift.SwiftAPI.assert_called_once_with()
|
self.assertEqual(expected_url, url)
|
||||||
|
|
||||||
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(redfish_boot.RedfishVirtualMediaBoot,
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
'_prepare_iso_image', autospec=True)
|
'_prepare_iso_image', autospec=True)
|
||||||
|
@ -7,4 +7,7 @@ features:
|
|||||||
``redfish-virtual-media`` boot interface can additionally require EFI
|
``redfish-virtual-media`` boot interface can additionally require EFI
|
||||||
system partition image (ESP) when performing UEFI boot. New configuration
|
system partition image (ESP) when performing UEFI boot. New configuration
|
||||||
option ``bootloader`` or ``[driver_info]/bootloader`` property can be used
|
option ``bootloader`` or ``[driver_info]/bootloader`` property can be used
|
||||||
to convey ESP location to ironic.
|
to convey ESP location to ironic. Bootable ISO images can be served to
|
||||||
|
BMC either from Swift or from a HTTP server running on ironic conductor
|
||||||
|
machine. This is controlled by the ``[redfish]use_swift`` ironic
|
||||||
|
configuration option.
|
||||||
|
Loading…
Reference in New Issue
Block a user