Refactor iscsi_ilo driver to use new boot interface
This commit implements new boot interface IloVirtualMediaBoot and refactors IloVirtualMediaIscsiDeploy to use the same. Implements: blueprint ilo-driver-uses-boot-interface Change-Id: I5481e705892f66cb3184ac16ff56c123f5c69087
This commit is contained in:
parent
36db38d211
commit
39e40ef12b
@ -21,10 +21,12 @@ from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules.ilo import boot
|
||||
from ironic.drivers.modules.ilo import deploy
|
||||
from ironic.drivers.modules.ilo import inspect
|
||||
from ironic.drivers.modules.ilo import management
|
||||
from ironic.drivers.modules.ilo import power
|
||||
from ironic.drivers.modules.ilo import vendor
|
||||
|
||||
|
||||
class IloVirtualMediaIscsiDriver(base.BaseDriver):
|
||||
@ -44,10 +46,11 @@ class IloVirtualMediaIscsiDriver(base.BaseDriver):
|
||||
reason=_("Unable to import proliantutils library"))
|
||||
|
||||
self.power = power.IloPower()
|
||||
self.boot = boot.IloVirtualMediaBoot()
|
||||
self.deploy = deploy.IloVirtualMediaIscsiDeploy()
|
||||
self.console = deploy.IloConsoleInterface()
|
||||
self.management = management.IloManagement()
|
||||
self.vendor = deploy.VendorPassthru()
|
||||
self.vendor = vendor.VendorPassthru()
|
||||
self.inspect = inspect.IloInspect()
|
||||
|
||||
|
||||
@ -71,6 +74,6 @@ class IloVirtualMediaAgentDriver(base.BaseDriver):
|
||||
self.deploy = deploy.IloVirtualMediaAgentDeploy()
|
||||
self.console = deploy.IloConsoleInterface()
|
||||
self.management = management.IloManagement()
|
||||
self.vendor = deploy.IloVirtualMediaAgentVendorInterface()
|
||||
self.vendor = vendor.IloVirtualMediaAgentVendorInterface()
|
||||
self.inspect = inspect.IloInspect()
|
||||
self.raid = agent.AgentRAID()
|
||||
|
413
ironic/drivers/modules/ilo/boot.py
Normal file
413
ironic/drivers/modules/ilo/boot.py
Normal file
@ -0,0 +1,413 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Boot Interface for iLO drivers and its supporting methods.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import service_utils
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LE
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.common import image_service
|
||||
from ironic.common import images
|
||||
from ironic.common import swift
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.ilo import common as ilo_common
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
REQUIRED_PROPERTIES = {
|
||||
'ilo_deploy_iso': _("UUID (from Glance) of the deployment ISO. "
|
||||
"Required.")
|
||||
}
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES
|
||||
|
||||
|
||||
def parse_driver_info(node):
|
||||
"""Gets the driver specific Node deployment info.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver to
|
||||
deploy images to the node.
|
||||
|
||||
:param node: a single Node.
|
||||
:returns: A dict with the driver_info values.
|
||||
:raises: MissingParameterValue, if any of the required parameters are
|
||||
missing.
|
||||
"""
|
||||
info = node.driver_info
|
||||
d_info = {}
|
||||
d_info['ilo_deploy_iso'] = info.get('ilo_deploy_iso')
|
||||
|
||||
error_msg = _("Error validating iLO virtual media deploy. Some parameters"
|
||||
" were missing in node's driver_info")
|
||||
deploy_utils.check_for_missing_params(d_info, error_msg)
|
||||
|
||||
return d_info
|
||||
|
||||
|
||||
def _get_boot_iso_object_name(node):
|
||||
"""Returns the boot iso object name for a given node.
|
||||
|
||||
:param node: the node for which object name is to be provided.
|
||||
"""
|
||||
return "boot-%s" % node.uuid
|
||||
|
||||
|
||||
def _get_boot_iso(task, root_uuid):
|
||||
"""This method returns a boot ISO to boot the node.
|
||||
|
||||
It chooses one of the three options in the order as below:
|
||||
1. Does nothing if 'ilo_boot_iso' is present in node's instance_info and
|
||||
'boot_iso_created_in_web_server' is not set in 'driver_internal_info'.
|
||||
2. Image deployed has a meta-property 'boot_iso' in Glance. This should
|
||||
refer to the UUID of the boot_iso which exists in Glance.
|
||||
3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in
|
||||
the image deployed. It uploads the generated boot ISO to Swift.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param root_uuid: the uuid of the root partition.
|
||||
:returns: boot ISO URL. Should be either of below:
|
||||
* A Swift object - It should be of format 'swift:<object-name>'. It is
|
||||
assumed that the image object is present in
|
||||
CONF.ilo.swift_ilo_container;
|
||||
* A Glance image - It should be format 'glance://<glance-image-uuid>'
|
||||
or just <glance-image-uuid>;
|
||||
* An HTTP URL.
|
||||
On error finding the boot iso, it returns None.
|
||||
:raises: MissingParameterValue, if any of the required parameters are
|
||||
missing in the node's driver_info or instance_info.
|
||||
:raises: InvalidParameterValue, if any of the parameters have invalid
|
||||
value in the node's driver_info or instance_info.
|
||||
:raises: SwiftOperationError, if operation with Swift fails.
|
||||
:raises: ImageCreationFailed, if creation of boot ISO failed.
|
||||
:raises: exception.ImageRefValidationFailed if ilo_boot_iso is not
|
||||
HTTP(S) URL.
|
||||
"""
|
||||
LOG.debug("Trying to get a boot ISO to boot the baremetal node")
|
||||
|
||||
# Option 1 - Check if user has provided ilo_boot_iso in node's
|
||||
# instance_info
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
boot_iso_created_in_web_server = (
|
||||
driver_internal_info.get('boot_iso_created_in_web_server'))
|
||||
|
||||
if (task.node.instance_info.get('ilo_boot_iso')
|
||||
and not boot_iso_created_in_web_server):
|
||||
LOG.debug("Using ilo_boot_iso provided in node's instance_info")
|
||||
boot_iso = task.node.instance_info['ilo_boot_iso']
|
||||
if not service_utils.is_glance_image(boot_iso):
|
||||
try:
|
||||
image_service.HttpImageService().validate_href(boot_iso)
|
||||
except exception.ImageRefValidationFailed:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Virtual media deploy accepts only Glance "
|
||||
"images or HTTP(S) URLs as "
|
||||
"instance_info['ilo_boot_iso']. Either %s "
|
||||
"is not a valid HTTP(S) URL or is "
|
||||
"not reachable."), boot_iso)
|
||||
|
||||
return task.node.instance_info['ilo_boot_iso']
|
||||
|
||||
# Option 2 - Check if user has provided a boot_iso in Glance. If boot_iso
|
||||
# is a supported non-glance href execution will proceed to option 3.
|
||||
deploy_info = _parse_deploy_info(task.node)
|
||||
|
||||
image_href = deploy_info['image_source']
|
||||
image_properties = (
|
||||
images.get_image_properties(
|
||||
task.context, image_href, ['boot_iso', 'kernel_id', 'ramdisk_id']))
|
||||
|
||||
boot_iso_uuid = image_properties.get('boot_iso')
|
||||
kernel_href = (task.node.instance_info.get('kernel') or
|
||||
image_properties.get('kernel_id'))
|
||||
ramdisk_href = (task.node.instance_info.get('ramdisk') or
|
||||
image_properties.get('ramdisk_id'))
|
||||
|
||||
if boot_iso_uuid:
|
||||
LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid)
|
||||
return boot_iso_uuid
|
||||
|
||||
if not kernel_href or not ramdisk_href:
|
||||
LOG.error(_LE("Unable to find kernel or ramdisk for "
|
||||
"image %(image)s to generate boot ISO for %(node)s"),
|
||||
{'image': image_href, 'node': task.node.uuid})
|
||||
return
|
||||
|
||||
# NOTE(rameshg87): Functionality to share the boot ISOs created for
|
||||
# similar instances (instances with same deployed image) is
|
||||
# not implemented as of now. Creation/Deletion of such a shared boot ISO
|
||||
# will require synchronisation across conductor nodes for the shared boot
|
||||
# ISO. Such a synchronisation mechanism doesn't exist in ironic as of now.
|
||||
|
||||
# Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift
|
||||
# or web server and provide its name.
|
||||
deploy_iso_uuid = deploy_info['ilo_deploy_iso']
|
||||
boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node)
|
||||
boot_iso_object_name = _get_boot_iso_object_name(task.node)
|
||||
kernel_params = CONF.pxe.pxe_append_params
|
||||
with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj:
|
||||
boot_iso_tmp_file = fileobj.name
|
||||
images.create_boot_iso(task.context, boot_iso_tmp_file,
|
||||
kernel_href, ramdisk_href,
|
||||
deploy_iso_uuid, root_uuid,
|
||||
kernel_params, boot_mode)
|
||||
|
||||
if CONF.ilo.use_web_server_for_images:
|
||||
boot_iso_url = (
|
||||
ilo_common.copy_image_to_web_server(boot_iso_tmp_file,
|
||||
boot_iso_object_name))
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info['boot_iso_created_in_web_server'] = True
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s",
|
||||
{'boot_iso': boot_iso_url, 'node': task.node.uuid})
|
||||
return boot_iso_url
|
||||
else:
|
||||
container = CONF.ilo.swift_ilo_container
|
||||
swift_api = swift.SwiftAPI()
|
||||
swift_api.create_object(container, boot_iso_object_name,
|
||||
boot_iso_tmp_file)
|
||||
|
||||
LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name)
|
||||
return 'swift:%s' % boot_iso_object_name
|
||||
|
||||
|
||||
def _clean_up_boot_iso_for_instance(node):
|
||||
"""Deletes the boot ISO if it was created for the instance.
|
||||
|
||||
:param node: an ironic node object.
|
||||
"""
|
||||
ilo_boot_iso = node.instance_info.get('ilo_boot_iso')
|
||||
if not ilo_boot_iso:
|
||||
return
|
||||
if ilo_boot_iso.startswith('swift'):
|
||||
swift_api = swift.SwiftAPI()
|
||||
container = CONF.ilo.swift_ilo_container
|
||||
boot_iso_object_name = _get_boot_iso_object_name(node)
|
||||
try:
|
||||
swift_api.delete_object(container, boot_iso_object_name)
|
||||
except exception.SwiftOperationError as e:
|
||||
LOG.exception(_LE("Failed to clean up boot ISO for node "
|
||||
"%(node)s. Error: %(error)s."),
|
||||
{'node': node.uuid, 'error': e})
|
||||
elif CONF.ilo.use_web_server_for_images:
|
||||
result = urlparse.urlparse(ilo_boot_iso)
|
||||
ilo_boot_iso_name = os.path.basename(result.path)
|
||||
boot_iso_path = os.path.join(
|
||||
CONF.deploy.http_root, ilo_boot_iso_name)
|
||||
utils.unlink_without_raise(boot_iso_path)
|
||||
|
||||
|
||||
def _parse_deploy_info(node):
|
||||
"""Gets the instance and driver specific Node deployment info.
|
||||
|
||||
This method validates whether the 'instance_info' and 'driver_info'
|
||||
property of the supplied node contains the required information for
|
||||
this driver to deploy images to the node.
|
||||
|
||||
:param node: a single Node.
|
||||
:returns: A dict with the instance_info and driver_info values.
|
||||
:raises: MissingParameterValue, if any of the required parameters are
|
||||
missing.
|
||||
:raises: InvalidParameterValue, if any of the parameters have invalid
|
||||
value.
|
||||
"""
|
||||
info = {}
|
||||
info.update(deploy_utils.get_image_instance_info(node))
|
||||
info.update(parse_driver_info(node))
|
||||
return info
|
||||
|
||||
|
||||
class IloVirtualMediaBoot(base.BootInterface):
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the deployment information for the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue, if some information is invalid.
|
||||
:raises: MissingParameterValue if 'kernel_id' and 'ramdisk_id' are
|
||||
missing in the Glance image or 'kernel' and 'ramdisk' not provided
|
||||
in instance_info for non-Glance image.
|
||||
"""
|
||||
|
||||
node = task.node
|
||||
|
||||
d_info = _parse_deploy_info(node)
|
||||
|
||||
if node.driver_internal_info.get('is_whole_disk_image'):
|
||||
props = []
|
||||
elif service_utils.is_glance_image(d_info['image_source']):
|
||||
props = ['kernel_id', 'ramdisk_id']
|
||||
else:
|
||||
props = ['kernel', 'ramdisk']
|
||||
deploy_utils.validate_image_properties(task.context, d_info, props)
|
||||
|
||||
def prepare_ramdisk(self, task, ramdisk_params):
|
||||
"""Prepares the boot of deploy ramdisk using virtual media.
|
||||
|
||||
This method prepares the boot of the deploy ramdisk after
|
||||
reading relevant information from the node's driver_info and
|
||||
instance_info.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:param ramdisk_params: the parameters to be passed to the ramdisk.
|
||||
:returns: None
|
||||
:raises: MissingParameterValue, if some information is missing in
|
||||
node's driver_info or instance_info.
|
||||
:raises: InvalidParameterValue, if some information provided is
|
||||
invalid.
|
||||
:raises: IronicException, if some power or set boot boot device
|
||||
operation failed on the node.
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
|
||||
node = task.node
|
||||
|
||||
# Clear ilo_boot_iso if it's a glance image to force recreate
|
||||
# another one again (or use existing one in glance).
|
||||
# This is mainly for rebuild scenario.
|
||||
if service_utils.is_glance_image(
|
||||
node.instance_info.get('image_source')):
|
||||
instance_info = node.instance_info
|
||||
instance_info.pop('ilo_boot_iso', None)
|
||||
node.instance_info = instance_info
|
||||
node.save()
|
||||
|
||||
# Eject all virtual media devices, as we are going to use them
|
||||
# during deploy.
|
||||
ilo_common.eject_vmedia_devices(task)
|
||||
|
||||
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
|
||||
ramdisk_params['BOOTIF'] = deploy_nic_mac
|
||||
deploy_iso = node.driver_info['ilo_deploy_iso']
|
||||
|
||||
ilo_common.setup_vmedia(task, deploy_iso, ramdisk_params)
|
||||
|
||||
def prepare_instance(self, task):
|
||||
"""Prepares the boot of instance.
|
||||
|
||||
This method prepares the boot of the instance after reading
|
||||
relevant information from the node's instance_info.
|
||||
It does the following depending on boot_option for deploy:
|
||||
|
||||
- If the boot_option requested for this deploy is 'local' or image
|
||||
is a whole disk image, then it sets the node to boot from disk.
|
||||
- Otherwise it finds/creates the boot ISO to boot the instance
|
||||
image, attaches the boot ISO to the bare metal and then sets
|
||||
the node to boot from CDROM.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: None
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
|
||||
ilo_common.cleanup_vmedia_boot(task)
|
||||
|
||||
# For iscsi_ilo driver, we boot from disk every time if the image
|
||||
# deployed is a whole disk image.
|
||||
node = task.node
|
||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||
if deploy_utils.get_boot_option(node) == "local" or iwdi:
|
||||
manager_utils.node_set_boot_device(task, boot_devices.DISK,
|
||||
persistent=True)
|
||||
else:
|
||||
drv_int_info = node.driver_internal_info
|
||||
root_uuid_or_disk_id = drv_int_info.get('root_uuid_or_disk_id')
|
||||
if root_uuid_or_disk_id:
|
||||
self._configure_vmedia_boot(task, root_uuid_or_disk_id)
|
||||
else:
|
||||
LOG.warning(_LW("The UUID for the root partition could not "
|
||||
"be found for node %s"), node.uuid)
|
||||
|
||||
def clean_up_instance(self, task):
|
||||
"""Cleans up the boot of instance.
|
||||
|
||||
This method cleans up the environment that was setup for booting
|
||||
the instance. It ejects virtual media
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: None
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
|
||||
_clean_up_boot_iso_for_instance(task.node)
|
||||
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info.pop('boot_iso_created_in_web_server', None)
|
||||
driver_internal_info.pop('root_uuid_or_disk_id', None)
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
|
||||
ilo_common.cleanup_vmedia_boot(task)
|
||||
|
||||
def clean_up_ramdisk(self, task):
|
||||
"""Cleans up the boot of ironic ramdisk.
|
||||
|
||||
This method cleans up virtual media devices setup for the deploy
|
||||
ramdisk.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: None
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
|
||||
ilo_common.cleanup_vmedia_boot(task)
|
||||
|
||||
def _configure_vmedia_boot(self, task, root_uuid):
|
||||
"""Configure vmedia boot for the node.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:param root_uuid: uuid of the root partition
|
||||
:returns: None
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
|
||||
node = task.node
|
||||
boot_iso = _get_boot_iso(task, root_uuid)
|
||||
if not boot_iso:
|
||||
LOG.error(_LE("Cannot get boot ISO for node %s"), node.uuid)
|
||||
return
|
||||
|
||||
# Upon deploy complete, some distros cloud images reboot the system as
|
||||
# part of its configuration. Hence boot device should be persistent and
|
||||
# not one-time.
|
||||
ilo_common.setup_vmedia_for_boot(task, boot_iso)
|
||||
manager_utils.node_set_boot_device(task,
|
||||
boot_devices.CDROM,
|
||||
persistent=True)
|
||||
|
||||
i_info = node.instance_info
|
||||
i_info['ilo_boot_iso'] = boot_iso
|
||||
node.instance_info = i_info
|
||||
node.save()
|
@ -26,6 +26,7 @@ from oslo_utils import importutils
|
||||
import six.moves.urllib.parse as urlparse
|
||||
from six.moves.urllib.parse import urljoin
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import service_utils
|
||||
from ironic.common.i18n import _
|
||||
@ -35,6 +36,7 @@ from ironic.common.i18n import _LW
|
||||
from ironic.common import images
|
||||
from ironic.common import swift
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
|
||||
ilo_client = importutils.try_import('proliantutils.ilo.client')
|
||||
@ -432,6 +434,37 @@ def update_boot_mode(task):
|
||||
node.save()
|
||||
|
||||
|
||||
def setup_vmedia(task, iso, ramdisk_options):
|
||||
"""Attaches virtual media and sets it as boot device.
|
||||
|
||||
This method attaches the given bootable ISO as virtual media, prepares the
|
||||
arguments for ramdisk in virtual media floppy.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param iso: a bootable ISO image href to attach to. Should be either
|
||||
of below:
|
||||
* A Swift object - It should be of format 'swift:<object-name>'.
|
||||
It is assumed that the image object is present in
|
||||
CONF.ilo.swift_ilo_container;
|
||||
* A Glance image - It should be format 'glance://<glance-image-uuid>'
|
||||
or just <glance-image-uuid>;
|
||||
* An HTTP URL.
|
||||
:param ramdisk_options: the options to be passed to the ramdisk in virtual
|
||||
media floppy.
|
||||
:raises: ImageCreationFailed, if it failed while creating the floppy image.
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
setup_vmedia_for_boot(task, iso, ramdisk_options)
|
||||
|
||||
# In UEFI boot mode, upon inserting virtual CDROM, one has to reset the
|
||||
# system to see it as a valid boot device in persistent boot devices.
|
||||
# But virtual CDROM device is always available for one-time boot.
|
||||
# During enable/disable of secure boot settings, iLO internally resets
|
||||
# the server twice. But it retains one time boot settings across internal
|
||||
# resets. Hence no impact of this change for secure boot deploy.
|
||||
manager_utils.node_set_boot_device(task, boot_devices.CDROM)
|
||||
|
||||
|
||||
def setup_vmedia_for_boot(task, boot_iso, parameters=None):
|
||||
"""Sets up the node to boot from the given ISO image.
|
||||
|
||||
@ -590,3 +623,25 @@ def set_secure_boot_mode(task, flag):
|
||||
except ilo_error.IloError as ilo_exception:
|
||||
raise exception.IloOperationError(operation=operation,
|
||||
error=ilo_exception)
|
||||
|
||||
|
||||
def update_secure_boot_mode(task, mode):
|
||||
"""Changes secure boot mode for next boot on the node.
|
||||
|
||||
This method changes secure boot mode on the node for next boot. It changes
|
||||
the secure boot mode setting on node only if the deploy has requested for
|
||||
the secure boot.
|
||||
During deploy, this method is used to enable secure boot on the node by
|
||||
passing 'mode' as 'True'.
|
||||
During teardown, this method is used to disable secure boot on the node by
|
||||
passing 'mode' as 'False'.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param mode: Boolean value requesting the next state for secure boot
|
||||
:raises: IloOperationNotSupported, if operation is not supported on iLO
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
if deploy_utils.is_secure_boot_requested(task.node):
|
||||
set_secure_boot_mode(task, mode)
|
||||
LOG.info(_LI('Changed secure boot to %(mode)s for node %(node)s'),
|
||||
{'mode': mode, 'node': task.node.uuid})
|
||||
|
@ -15,33 +15,21 @@
|
||||
iLO Deploy Driver(s) and supporting methods.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import dhcp_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import service_utils
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LE
|
||||
from ironic.common.i18n import _LI
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.common import image_service
|
||||
from ironic.common import images
|
||||
from ironic.common import states
|
||||
from ironic.common import swift
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import agent_base_vendor
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.ilo import boot as ilo_boot
|
||||
from ironic.drivers.modules.ilo import common as ilo_common
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
@ -58,12 +46,6 @@ clean_opts = [
|
||||
'disabled and will not run during cleaning.'))
|
||||
]
|
||||
|
||||
REQUIRED_PROPERTIES = {
|
||||
'ilo_deploy_iso': _("UUID (from Glance) of the deployment ISO. "
|
||||
"Required.")
|
||||
}
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES
|
||||
|
||||
CONF.import_opt('pxe_append_params', 'ironic.drivers.modules.iscsi_deploy',
|
||||
group='pxe')
|
||||
CONF.import_opt('swift_ilo_container', 'ironic.drivers.modules.ilo.common',
|
||||
@ -71,272 +53,6 @@ CONF.import_opt('swift_ilo_container', 'ironic.drivers.modules.ilo.common',
|
||||
CONF.register_opts(clean_opts, group='ilo')
|
||||
|
||||
|
||||
def _recreate_and_populate_ilo_boot_iso(task):
|
||||
"""Recreate the boot iso for the node.
|
||||
|
||||
Recreates the boot iso for the image and host it
|
||||
on a valid image service and populates the new boot iso
|
||||
in the instance_info of the node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
"""
|
||||
instance_info = task.node.instance_info
|
||||
root_uuid = task.node.driver_internal_info.get('root_uuid_or_disk_id')
|
||||
boot_iso = None
|
||||
if root_uuid:
|
||||
try:
|
||||
# Recreate the boot iso
|
||||
boot_iso = _get_boot_iso(task, root_uuid)
|
||||
except Exception as e:
|
||||
LOG.warning(_LW("Boot iso recreation failed during take over. "
|
||||
"Reason: %(reason)s. The node %(node)s may not "
|
||||
"come up with current boot_iso %(boot_iso)s. "),
|
||||
{'boot_iso': instance_info['ilo_boot_iso'],
|
||||
'reason': e, 'node': task.node.uuid})
|
||||
# populate the new ilo_boot_iso in node.instance_info.
|
||||
if boot_iso:
|
||||
instance_info['ilo_boot_iso'] = boot_iso
|
||||
task.node.instance_info = instance_info
|
||||
task.node.save()
|
||||
else:
|
||||
LOG.warning(_LW("Boot iso recreation failed during take over. "
|
||||
"The node %(node)s may not come up "
|
||||
"with current boot_iso %(boot_iso)s. "),
|
||||
{'boot_iso': instance_info['ilo_boot_iso'],
|
||||
'node': task.node.uuid})
|
||||
else:
|
||||
LOG.warning(_LW("There is not enough information to recreate "
|
||||
"boot iso. The UUID for the root partition "
|
||||
"could not be found. The boot-iso cannot be "
|
||||
"created without root_uuid. The node %(node)s may "
|
||||
"not come up with current boot_iso "
|
||||
"%(boot_iso)s "),
|
||||
{'boot_iso': instance_info['ilo_boot_iso'],
|
||||
'node': task.node.uuid})
|
||||
|
||||
|
||||
def _get_boot_iso_object_name(node):
|
||||
"""Returns the boot iso object name for a given node.
|
||||
|
||||
:param node: the node for which object name is to be provided.
|
||||
"""
|
||||
return "boot-%s" % node.uuid
|
||||
|
||||
|
||||
def _get_boot_iso(task, root_uuid):
|
||||
"""This method returns a boot ISO to boot the node.
|
||||
|
||||
It chooses one of the three options in the order as below:
|
||||
1. Does nothing if 'ilo_boot_iso' is present in node's instance_info.
|
||||
2. Image deployed has a meta-property 'boot_iso' in Glance. This should
|
||||
refer to the UUID of the boot_iso which exists in Glance.
|
||||
3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in
|
||||
the image deployed. It uploads the generated boot ISO to Swift.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param root_uuid: the uuid of the root partition.
|
||||
:returns: boot ISO URL. Should be either of below:
|
||||
* A Swift object - It should be of format 'swift:<object-name>'. It is
|
||||
assumed that the image object is present in
|
||||
CONF.ilo.swift_ilo_container;
|
||||
* A Glance image - It should be format 'glance://<glance-image-uuid>'
|
||||
or just <glance-image-uuid>;
|
||||
* An HTTP URL.
|
||||
On error finding the boot iso, it returns None.
|
||||
:raises: MissingParameterValue, if any of the required parameters are
|
||||
missing in the node's driver_info or instance_info.
|
||||
:raises: InvalidParameterValue, if any of the parameters have invalid
|
||||
value in the node's driver_info or instance_info.
|
||||
:raises: SwiftOperationError, if operation with Swift fails.
|
||||
:raises: ImageCreationFailed, if creation of boot ISO failed.
|
||||
:raises: exception.ImageRefValidationFailed if ilo_boot_iso is not
|
||||
HTTP(S) URL.
|
||||
"""
|
||||
LOG.debug("Trying to get a boot ISO to boot the baremetal node")
|
||||
|
||||
# Option 1 - Check if user has provided ilo_boot_iso in node's
|
||||
# instance_info
|
||||
if task.node.instance_info.get('ilo_boot_iso'):
|
||||
LOG.debug("Using ilo_boot_iso provided in node's instance_info")
|
||||
boot_iso = task.node.instance_info['ilo_boot_iso']
|
||||
if not service_utils.is_glance_image(boot_iso):
|
||||
try:
|
||||
image_service.HttpImageService().validate_href(boot_iso)
|
||||
except exception.ImageRefValidationFailed:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Virtual media deploy accepts only Glance "
|
||||
"images or HTTP(S) URLs as "
|
||||
"instance_info['ilo_boot_iso']. Either %s "
|
||||
"is not a valid HTTP(S) URL or is "
|
||||
"not reachable."), boot_iso)
|
||||
|
||||
return task.node.instance_info['ilo_boot_iso']
|
||||
|
||||
# Option 2 - Check if user has provided a boot_iso in Glance. If boot_iso
|
||||
# is a supported non-glance href execution will proceed to option 3.
|
||||
deploy_info = _parse_deploy_info(task.node)
|
||||
|
||||
image_href = deploy_info['image_source']
|
||||
image_properties = (
|
||||
images.get_image_properties(
|
||||
task.context, image_href, ['boot_iso', 'kernel_id', 'ramdisk_id']))
|
||||
|
||||
boot_iso_uuid = image_properties.get('boot_iso')
|
||||
kernel_href = (task.node.instance_info.get('kernel') or
|
||||
image_properties.get('kernel_id'))
|
||||
ramdisk_href = (task.node.instance_info.get('ramdisk') or
|
||||
image_properties.get('ramdisk_id'))
|
||||
|
||||
if boot_iso_uuid:
|
||||
LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid)
|
||||
return boot_iso_uuid
|
||||
|
||||
if not kernel_href or not ramdisk_href:
|
||||
LOG.error(_LE("Unable to find kernel or ramdisk for "
|
||||
"image %(image)s to generate boot ISO for %(node)s"),
|
||||
{'image': image_href, 'node': task.node.uuid})
|
||||
return
|
||||
|
||||
# NOTE(rameshg87): Functionality to share the boot ISOs created for
|
||||
# similar instances (instances with same deployed image) is
|
||||
# not implemented as of now. Creation/Deletion of such a shared boot ISO
|
||||
# will require synchronisation across conductor nodes for the shared boot
|
||||
# ISO. Such a synchronisation mechanism doesn't exist in ironic as of now.
|
||||
|
||||
# Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift
|
||||
# or web server and provide its name.
|
||||
deploy_iso_uuid = deploy_info['ilo_deploy_iso']
|
||||
boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node)
|
||||
boot_iso_object_name = _get_boot_iso_object_name(task.node)
|
||||
kernel_params = CONF.pxe.pxe_append_params
|
||||
with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj:
|
||||
boot_iso_tmp_file = fileobj.name
|
||||
images.create_boot_iso(task.context, boot_iso_tmp_file,
|
||||
kernel_href, ramdisk_href,
|
||||
deploy_iso_uuid, root_uuid,
|
||||
kernel_params, boot_mode)
|
||||
if CONF.ilo.use_web_server_for_images:
|
||||
boot_iso_url = (
|
||||
ilo_common.copy_image_to_web_server(boot_iso_tmp_file,
|
||||
boot_iso_object_name))
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info['boot_iso_created_in_web_server'] = True
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s",
|
||||
{'boot_iso': boot_iso_url, 'node': task.node.uuid})
|
||||
return boot_iso_url
|
||||
else:
|
||||
container = CONF.ilo.swift_ilo_container
|
||||
swift_api = swift.SwiftAPI()
|
||||
swift_api.create_object(container, boot_iso_object_name,
|
||||
boot_iso_tmp_file)
|
||||
|
||||
LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name)
|
||||
return 'swift:%s' % boot_iso_object_name
|
||||
|
||||
|
||||
def _clean_up_boot_iso_for_instance(node):
|
||||
"""Deletes the boot ISO if it was created for the instance.
|
||||
|
||||
:param node: an ironic node object.
|
||||
"""
|
||||
ilo_boot_iso = node.instance_info.get('ilo_boot_iso')
|
||||
if not ilo_boot_iso:
|
||||
return
|
||||
if ilo_boot_iso.startswith('swift'):
|
||||
swift_api = swift.SwiftAPI()
|
||||
container = CONF.ilo.swift_ilo_container
|
||||
boot_iso_object_name = _get_boot_iso_object_name(node)
|
||||
try:
|
||||
swift_api.delete_object(container, boot_iso_object_name)
|
||||
except exception.SwiftOperationError as e:
|
||||
LOG.exception(_LE("Failed to clean up boot ISO for node "
|
||||
"%(node)s. Error: %(error)s."),
|
||||
{'node': node.uuid, 'error': e})
|
||||
elif CONF.ilo.use_web_server_for_images:
|
||||
result = urlparse.urlparse(ilo_boot_iso)
|
||||
ilo_boot_iso_name = os.path.basename(result.path)
|
||||
boot_iso_path = os.path.join(
|
||||
CONF.deploy.http_root, ilo_boot_iso_name)
|
||||
utils.unlink_without_raise(boot_iso_path)
|
||||
|
||||
|
||||
def _parse_driver_info(node):
|
||||
"""Gets the driver specific Node deployment info.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver to
|
||||
deploy images to the node.
|
||||
|
||||
:param node: a single Node.
|
||||
:returns: A dict with the driver_info values.
|
||||
:raises: MissingParameterValue, if any of the required parameters are
|
||||
missing.
|
||||
"""
|
||||
info = node.driver_info
|
||||
d_info = {}
|
||||
d_info['ilo_deploy_iso'] = info.get('ilo_deploy_iso')
|
||||
|
||||
error_msg = _("Error validating iLO virtual media deploy. Some parameters"
|
||||
" were missing in node's driver_info")
|
||||
deploy_utils.check_for_missing_params(d_info, error_msg)
|
||||
|
||||
return d_info
|
||||
|
||||
|
||||
def _parse_deploy_info(node):
|
||||
"""Gets the instance and driver specific Node deployment info.
|
||||
|
||||
This method validates whether the 'instance_info' and 'driver_info'
|
||||
property of the supplied node contains the required information for
|
||||
this driver to deploy images to the node.
|
||||
|
||||
:param node: a single Node.
|
||||
:returns: A dict with the instance_info and driver_info values.
|
||||
:raises: MissingParameterValue, if any of the required parameters are
|
||||
missing.
|
||||
:raises: InvalidParameterValue, if any of the parameters have invalid
|
||||
value.
|
||||
"""
|
||||
info = {}
|
||||
info.update(iscsi_deploy.parse_instance_info(node))
|
||||
info.update(_parse_driver_info(node))
|
||||
return info
|
||||
|
||||
|
||||
def _reboot_into(task, iso, ramdisk_options):
|
||||
"""Reboots the node into a given boot ISO.
|
||||
|
||||
This method attaches the given bootable ISO as virtual media, prepares the
|
||||
arguments for ramdisk in virtual media floppy, and then reboots the node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param iso: a bootable ISO image href to attach to. Should be either
|
||||
of below:
|
||||
* A Swift object - It should be of format 'swift:<object-name>'.
|
||||
It is assumed that the image object is present in
|
||||
CONF.ilo.swift_ilo_container;
|
||||
* A Glance image - It should be format 'glance://<glance-image-uuid>'
|
||||
or just <glance-image-uuid>;
|
||||
* An HTTP URL.
|
||||
:param ramdisk_options: the options to be passed to the ramdisk in virtual
|
||||
media floppy.
|
||||
:raises: ImageCreationFailed, if it failed while creating the floppy image.
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
ilo_common.setup_vmedia_for_boot(task, iso, ramdisk_options)
|
||||
|
||||
# In UEFI boot mode, upon inserting virtual CDROM, one has to reset the
|
||||
# system to see it as a valid boot device in persistent boot devices.
|
||||
# But virtual CDROM device is always available for one-time boot.
|
||||
# During enable/disable of secure boot settings, iLO internally resets
|
||||
# the server twice. But it retains one time boot settings across internal
|
||||
# resets. Hence no impact of this change for secure boot deploy.
|
||||
manager_utils.node_set_boot_device(task, boot_devices.CDROM)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
|
||||
|
||||
def _prepare_agent_vmedia_boot(task):
|
||||
"""Ejects virtual media devices and prepares for vmedia boot."""
|
||||
# Eject all virtual media devices, as we are going to use them
|
||||
@ -345,7 +61,8 @@ def _prepare_agent_vmedia_boot(task):
|
||||
|
||||
deploy_ramdisk_opts = deploy_utils.build_agent_options(task.node)
|
||||
deploy_iso = task.node.driver_info['ilo_deploy_iso']
|
||||
_reboot_into(task, deploy_iso, deploy_ramdisk_opts)
|
||||
ilo_common.setup_vmedia(task, deploy_iso, deploy_ramdisk_opts)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
|
||||
|
||||
def _disable_secure_boot(task):
|
||||
@ -411,28 +128,6 @@ def _prepare_node_for_deploy(task):
|
||||
task.node.save()
|
||||
|
||||
|
||||
def _update_secure_boot_mode(task, mode):
|
||||
"""Changes secure boot mode for next boot on the node.
|
||||
|
||||
This method changes secure boot mode on the node for next boot. It changes
|
||||
the secure boot mode setting on node only if the deploy has requested for
|
||||
the secure boot.
|
||||
During deploy, this method is used to enable secure boot on the node by
|
||||
passing 'mode' as 'True'.
|
||||
During teardown, this method is used to disable secure boot on the node by
|
||||
passing 'mode' as 'False'.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param mode: Boolean value requesting the next state for secure boot
|
||||
:raises: IloOperationNotSupported, if operation is not supported on iLO
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
if deploy_utils.is_secure_boot_requested(task.node):
|
||||
ilo_common.set_secure_boot_mode(task, mode)
|
||||
LOG.info(_LI('Changed secure boot to %(mode)s for node %(node)s'),
|
||||
{'mode': mode, 'node': task.node.uuid})
|
||||
|
||||
|
||||
def _disable_secure_boot_if_supported(task):
|
||||
"""Disables secure boot on node, does not throw if its not supported.
|
||||
|
||||
@ -440,7 +135,7 @@ def _disable_secure_boot_if_supported(task):
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
try:
|
||||
_update_secure_boot_mode(task, False)
|
||||
ilo_common.update_secure_boot_mode(task, False)
|
||||
# We need to handle IloOperationNotSupported exception so that if
|
||||
# the user has incorrectly specified the Node capability
|
||||
# 'secure_boot' to a node that does not have that capability and
|
||||
@ -451,79 +146,10 @@ def _disable_secure_boot_if_supported(task):
|
||||
task.node.uuid)
|
||||
|
||||
|
||||
class IloVirtualMediaIscsiDeploy(base.DeployInterface):
|
||||
class IloVirtualMediaIscsiDeploy(iscsi_deploy.ISCSIDeploy):
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the deployment information for the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue, if some information is invalid.
|
||||
:raises: MissingParameterValue if 'kernel_id' and 'ramdisk_id' are
|
||||
missing in the Glance image or 'kernel' and 'ramdisk' not provided
|
||||
in instance_info for non-Glance image.
|
||||
"""
|
||||
iscsi_deploy.validate(task)
|
||||
node = task.node
|
||||
|
||||
d_info = _parse_deploy_info(node)
|
||||
|
||||
if node.driver_internal_info.get('is_whole_disk_image'):
|
||||
props = []
|
||||
elif service_utils.is_glance_image(d_info['image_source']):
|
||||
props = ['kernel_id', 'ramdisk_id']
|
||||
else:
|
||||
props = ['kernel', 'ramdisk']
|
||||
deploy_utils.validate_image_properties(task.context, d_info, props)
|
||||
deploy_utils.validate_capabilities(node)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def deploy(self, task):
|
||||
"""Start deployment of the task's node.
|
||||
|
||||
Fetches the instance image, prepares the options for the deployment
|
||||
ramdisk, sets the node to boot from virtual media cdrom, and reboots
|
||||
the given node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:returns: deploy state DEPLOYWAIT.
|
||||
:raises: InstanceDeployFailure, if image size if greater than root
|
||||
partition.
|
||||
:raises: ImageCreationFailed, if it failed while creating the floppy
|
||||
image.
|
||||
:raises: IloOperationError, if some operation on iLO fails.
|
||||
"""
|
||||
node = task.node
|
||||
|
||||
# Clear ilo_boot_iso if it's a glance image to force recreate
|
||||
# another one again (or use existing one in glance).
|
||||
# This is mainly for rebuild scenario.
|
||||
if service_utils.is_glance_image(
|
||||
node.instance_info.get('image_source')):
|
||||
instance_info = node.instance_info
|
||||
instance_info.pop('ilo_boot_iso', None)
|
||||
node.instance_info = instance_info
|
||||
node.save()
|
||||
|
||||
# Eject all virtual media devices, as we are going to use them
|
||||
# during deploy.
|
||||
ilo_common.eject_vmedia_devices(task)
|
||||
|
||||
iscsi_deploy.cache_instance_image(task.context, node)
|
||||
iscsi_deploy.check_image_size(task)
|
||||
|
||||
deploy_ramdisk_opts = iscsi_deploy.build_deploy_ramdisk_options(node)
|
||||
agent_opts = deploy_utils.build_agent_options(node)
|
||||
deploy_ramdisk_opts.update(agent_opts)
|
||||
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
|
||||
deploy_ramdisk_opts['BOOTIF'] = deploy_nic_mac
|
||||
deploy_iso = node.driver_info['ilo_deploy_iso']
|
||||
|
||||
_reboot_into(task, deploy_iso, deploy_ramdisk_opts)
|
||||
|
||||
return states.DEPLOYWAIT
|
||||
return {}
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def tear_down(self, task):
|
||||
@ -534,15 +160,12 @@ class IloVirtualMediaIscsiDeploy(base.DeployInterface):
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:returns: deploy state DELETED.
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
_disable_secure_boot_if_supported(task)
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info.pop('boot_iso_created_in_web_server', None)
|
||||
driver_internal_info.pop('root_uuid_or_disk_id', None)
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
return states.DELETED
|
||||
return super(IloVirtualMediaIscsiDeploy, self).tear_down(task)
|
||||
|
||||
def prepare(self, task):
|
||||
"""Prepare the deployment environment for this task's node.
|
||||
@ -553,33 +176,7 @@ class IloVirtualMediaIscsiDeploy(base.DeployInterface):
|
||||
if task.node.provision_state != states.ACTIVE:
|
||||
_prepare_node_for_deploy(task)
|
||||
|
||||
def clean_up(self, task):
|
||||
"""Clean up the deployment environment for the task's node.
|
||||
|
||||
Unlinks instance image and triggers image cache cleanup.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
"""
|
||||
_clean_up_boot_iso_for_instance(task.node)
|
||||
if not CONF.ilo.use_web_server_for_images:
|
||||
iscsi_deploy.destroy_images(task.node.uuid)
|
||||
else:
|
||||
ilo_common.destroy_floppy_image_from_web_server(task.node)
|
||||
|
||||
def take_over(self, task):
|
||||
"""Enables boot up of an ACTIVE node.
|
||||
|
||||
It ensures that the ACTIVE node can be booted up successfully
|
||||
when node is taken over by another conductor.
|
||||
|
||||
:param: task: a TaskManager instance containing the node to act on.
|
||||
"""
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
boot_iso_created_in_web_server = (
|
||||
driver_internal_info.get('boot_iso_created_in_web_server'))
|
||||
if (CONF.ilo.use_web_server_for_images
|
||||
and boot_iso_created_in_web_server):
|
||||
_recreate_and_populate_ilo_boot_iso(task)
|
||||
super(IloVirtualMediaIscsiDeploy, self).prepare(task)
|
||||
|
||||
|
||||
class IloVirtualMediaAgentDeploy(base.DeployInterface):
|
||||
@ -590,7 +187,7 @@ class IloVirtualMediaAgentDeploy(base.DeployInterface):
|
||||
|
||||
:returns: dictionary of <property name>:<property description> entries.
|
||||
"""
|
||||
return COMMON_PROPERTIES
|
||||
return ilo_boot.COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the driver-specific Node deployment info.
|
||||
@ -600,7 +197,7 @@ class IloVirtualMediaAgentDeploy(base.DeployInterface):
|
||||
"""
|
||||
|
||||
deploy_utils.validate_capabilities(task.node)
|
||||
_parse_driver_info(task.node)
|
||||
ilo_boot.parse_driver_info(task.node)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def deploy(self, task):
|
||||
@ -709,32 +306,6 @@ class IloVirtualMediaAgentDeploy(base.DeployInterface):
|
||||
provider.delete_cleaning_ports(task)
|
||||
|
||||
|
||||
class IloVirtualMediaAgentVendorInterface(agent.AgentVendorInterface):
|
||||
"""Interface for vendor passthru related actions."""
|
||||
|
||||
def reboot_to_instance(self, task, **kwargs):
|
||||
node = task.node
|
||||
LOG.debug('Preparing to reboot to instance for node %s',
|
||||
node.uuid)
|
||||
|
||||
error = self.check_deploy_success(node)
|
||||
if error is None:
|
||||
# Set boot mode
|
||||
ilo_common.update_boot_mode(task)
|
||||
|
||||
# Need to enable secure boot, if being requested
|
||||
_update_secure_boot_mode(task, True)
|
||||
|
||||
super(IloVirtualMediaAgentVendorInterface,
|
||||
self).reboot_to_instance(task, **kwargs)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def continue_deploy(self, task, **kwargs):
|
||||
ilo_common.cleanup_vmedia_boot(task)
|
||||
super(IloVirtualMediaAgentVendorInterface,
|
||||
self).continue_deploy(task, **kwargs)
|
||||
|
||||
|
||||
class IloPXEDeploy(iscsi_deploy.ISCSIDeploy):
|
||||
|
||||
def prepare(self, task):
|
||||
@ -816,266 +387,3 @@ class IloConsoleInterface(ipmitool.IPMIShellinaboxConsole):
|
||||
|
||||
ilo_common.update_ipmi_properties(task)
|
||||
super(IloConsoleInterface, self).validate(task)
|
||||
|
||||
|
||||
class IloPXEVendorPassthru(iscsi_deploy.VendorPassthru):
|
||||
|
||||
@base.passthru(['POST'])
|
||||
def pass_deploy_info(self, task, **kwargs):
|
||||
LOG.debug('Pass deploy info for the deployment on node %s',
|
||||
task.node.uuid)
|
||||
manager_utils.node_set_boot_device(task, boot_devices.PXE,
|
||||
persistent=True)
|
||||
# Set boot mode
|
||||
ilo_common.update_boot_mode(task)
|
||||
# Need to enable secure boot, if being requested
|
||||
_update_secure_boot_mode(task, True)
|
||||
|
||||
super(IloPXEVendorPassthru, self).pass_deploy_info(task, **kwargs)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def continue_deploy(self, task, **kwargs):
|
||||
"""Method invoked when deployed with the IPA ramdisk.
|
||||
|
||||
This method is invoked during a heartbeat from an agent when
|
||||
the node is in wait-call-back state. This deploys the image on
|
||||
the node and then configures the node to boot according to the
|
||||
desired boot option (netboot or localboot).
|
||||
|
||||
:param task: a TaskManager object containing the node.
|
||||
:param kwargs: the kwargs passed from the heartbeat method.
|
||||
:raises: InstanceDeployFailure, if it encounters some error during
|
||||
the deploy.
|
||||
:raises: IloOperationError, if some operation on iLO failed.
|
||||
"""
|
||||
LOG.debug('Continuing the deployment on node %s', task.node.uuid)
|
||||
# Set boot mode
|
||||
ilo_common.update_boot_mode(task)
|
||||
# Need to enable secure boot, if being requested
|
||||
_update_secure_boot_mode(task, True)
|
||||
|
||||
super(IloPXEVendorPassthru, self).continue_deploy(task, **kwargs)
|
||||
|
||||
|
||||
class VendorPassthru(agent_base_vendor.BaseAgentVendor):
|
||||
"""Vendor-specific interfaces for iLO deploy drivers."""
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task, method, **kwargs):
|
||||
"""Validate vendor-specific actions.
|
||||
|
||||
Checks if a valid vendor passthru method was passed and validates
|
||||
the parameters for the vendor passthru method.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param method: method to be validated.
|
||||
:param kwargs: kwargs containing the vendor passthru method's
|
||||
parameters.
|
||||
:raises: MissingParameterValue, if some required parameters were not
|
||||
passed.
|
||||
:raises: InvalidParameterValue, if any of the parameters have invalid
|
||||
value.
|
||||
"""
|
||||
if method == 'pass_deploy_info':
|
||||
iscsi_deploy.get_deploy_info(task.node, **kwargs)
|
||||
elif method == 'pass_bootloader_install_info':
|
||||
iscsi_deploy.validate_pass_bootloader_info_input(task, kwargs)
|
||||
elif method == 'boot_into_iso':
|
||||
self._validate_boot_into_iso(task, kwargs)
|
||||
|
||||
def _validate_boot_into_iso(self, task, kwargs):
|
||||
"""Validates if attach_iso can be called and if inputs are proper."""
|
||||
if not (task.node.provision_state == states.MANAGEABLE or
|
||||
task.node.maintenance is True):
|
||||
msg = (_("The requested action 'boot_into_iso' can be performed "
|
||||
"only when node %(node_uuid)s is in %(state)s state or "
|
||||
"in 'maintenance' mode") %
|
||||
{'node_uuid': task.node.uuid,
|
||||
'state': states.MANAGEABLE})
|
||||
raise exception.InvalidStateRequested(msg)
|
||||
d_info = {'boot_iso_href': kwargs.get('boot_iso_href')}
|
||||
error_msg = _("Error validating input for boot_into_iso vendor "
|
||||
"passthru. Some parameters were not provided: ")
|
||||
deploy_utils.check_for_missing_params(d_info, error_msg)
|
||||
deploy_utils.validate_image_properties(
|
||||
task.context, {'image_source': kwargs.get('boot_iso_href')}, [])
|
||||
|
||||
@base.passthru(['POST'])
|
||||
@task_manager.require_exclusive_lock
|
||||
def boot_into_iso(self, task, **kwargs):
|
||||
"""Attaches an ISO image in glance and reboots bare metal.
|
||||
|
||||
This method accepts an ISO image href (a Glance UUID or an HTTP(S) URL)
|
||||
attaches it as virtual media and then reboots the node. This is
|
||||
useful for debugging purposes. This can be invoked only when the node
|
||||
is in manage state.
|
||||
|
||||
:param task: A TaskManager object.
|
||||
:param kwargs: The arguments sent with vendor passthru. The expected
|
||||
kwargs are::
|
||||
|
||||
'boot_iso_href': href of the image to be booted. This can be
|
||||
a Glance UUID or an HTTP(S) URL.
|
||||
"""
|
||||
_reboot_into(task, kwargs['boot_iso_href'], ramdisk_options=None)
|
||||
|
||||
def _configure_vmedia_boot(self, task, root_uuid):
|
||||
"""Configure vmedia boot for the node."""
|
||||
node = task.node
|
||||
boot_iso = _get_boot_iso(task, root_uuid)
|
||||
if not boot_iso:
|
||||
LOG.error(_LE("Cannot get boot ISO for node %s"), node.uuid)
|
||||
return
|
||||
|
||||
# Upon deploy complete, some distros cloud images reboot the system as
|
||||
# part of its configuration. Hence boot device should be persistent and
|
||||
# not one-time.
|
||||
ilo_common.setup_vmedia_for_boot(task, boot_iso)
|
||||
manager_utils.node_set_boot_device(task,
|
||||
boot_devices.CDROM,
|
||||
persistent=True)
|
||||
|
||||
i_info = node.instance_info
|
||||
if not i_info.get('ilo_boot_iso'):
|
||||
i_info['ilo_boot_iso'] = boot_iso
|
||||
node.instance_info = i_info
|
||||
|
||||
@base.passthru(['POST'])
|
||||
@task_manager.require_exclusive_lock
|
||||
def pass_bootloader_install_info(self, task, **kwargs):
|
||||
"""Accepts the results of bootloader installation.
|
||||
|
||||
This method acts as a vendor passthru and accepts the result of
|
||||
bootloader installation. If the bootloader installation was
|
||||
successful, then it notifies the baremetal to proceed to reboot
|
||||
and makes the instance active. If bootloader installation failed,
|
||||
then it sets provisioning as failed and powers off the node.
|
||||
|
||||
:param task: A TaskManager object.
|
||||
:param kwargs: The arguments sent with vendor passthru. The expected
|
||||
kwargs are::
|
||||
|
||||
'key': The deploy key for authorization
|
||||
'status': 'SUCCEEDED' or 'FAILED'
|
||||
'error': The error message if status == 'FAILED'
|
||||
'address': The IP address of the ramdisk
|
||||
"""
|
||||
LOG.warning(_LW("The node %s is using the bash deploy ramdisk for "
|
||||
"its deployment. This deploy ramdisk has been "
|
||||
"deprecated. Please use the ironic-python-agent "
|
||||
"(IPA) ramdisk instead."), task.node.uuid)
|
||||
task.process_event('resume')
|
||||
iscsi_deploy.validate_bootloader_install_status(task, kwargs)
|
||||
iscsi_deploy.finish_deploy(task, kwargs['address'])
|
||||
|
||||
@base.passthru(['POST'])
|
||||
@task_manager.require_exclusive_lock
|
||||
def pass_deploy_info(self, task, **kwargs):
|
||||
"""Continues the iSCSI deployment from where ramdisk left off.
|
||||
|
||||
This method continues the iSCSI deployment from the conductor node
|
||||
and writes the deploy image to the bare metal's disk. After that,
|
||||
it does the following depending on boot_option for deploy:
|
||||
|
||||
- If the boot_option requested for this deploy is 'local', then it
|
||||
sets the node to boot from disk (ramdisk installs the boot loader
|
||||
present within the image to the bare metal's disk).
|
||||
- If the boot_option requested is 'netboot' or no boot_option is
|
||||
requested, it finds/creates the boot ISO to boot the instance
|
||||
image, attaches the boot ISO to the bare metal and then sets
|
||||
the node to boot from CDROM.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param kwargs: kwargs containing parameters for iSCSI deployment.
|
||||
:raises: InvalidState
|
||||
"""
|
||||
node = task.node
|
||||
LOG.warning(_LW("The node %s is using the bash deploy ramdisk for "
|
||||
"its deployment. This deploy ramdisk has been "
|
||||
"deprecated. Please use the ironic-python-agent "
|
||||
"(IPA) ramdisk instead."), node.uuid)
|
||||
task.process_event('resume')
|
||||
|
||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||
ilo_common.cleanup_vmedia_boot(task)
|
||||
uuid_dict = iscsi_deploy.continue_deploy(task, **kwargs)
|
||||
root_uuid_or_disk_id = uuid_dict.get(
|
||||
'root uuid', uuid_dict.get('disk identifier'))
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
|
||||
try:
|
||||
# Set boot mode
|
||||
ilo_common.update_boot_mode(task)
|
||||
|
||||
# Need to enable secure boot, if being requested
|
||||
_update_secure_boot_mode(task, True)
|
||||
|
||||
# For iscsi_ilo driver, we boot from disk every time if the image
|
||||
# deployed is a whole disk image.
|
||||
if deploy_utils.get_boot_option(node) == "local" or iwdi:
|
||||
manager_utils.node_set_boot_device(task, boot_devices.DISK,
|
||||
persistent=True)
|
||||
|
||||
# Ask the ramdisk to install bootloader and
|
||||
# wait for the call-back through the vendor passthru
|
||||
# 'pass_bootloader_install_info', if it's not a whole
|
||||
# disk image.
|
||||
if not iwdi:
|
||||
deploy_utils.notify_ramdisk_to_proceed(kwargs['address'])
|
||||
task.process_event('wait')
|
||||
return
|
||||
else:
|
||||
self._configure_vmedia_boot(task, root_uuid_or_disk_id)
|
||||
except Exception as e:
|
||||
LOG.error(_LE('Deploy failed for instance %(instance)s. '
|
||||
'Error: %(error)s'),
|
||||
{'instance': node.instance_uuid, 'error': e})
|
||||
msg = _('Failed to continue iSCSI deployment.')
|
||||
deploy_utils.set_failed_state(task, msg)
|
||||
else:
|
||||
iscsi_deploy.finish_deploy(task, kwargs.get('address'))
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def continue_deploy(self, task, **kwargs):
|
||||
"""Method invoked when deployed with the IPA ramdisk.
|
||||
|
||||
This method is invoked during a heartbeat from an agent when
|
||||
the node is in wait-call-back state. This deploys the image on
|
||||
the node and then configures the node to boot according to the
|
||||
desired boot option (netboot or localboot).
|
||||
|
||||
:param task: a TaskManager object containing the node.
|
||||
:param kwargs: the kwargs passed from the heartbeat method.
|
||||
:raises: InstanceDeployFailure, if it encounters some error during
|
||||
the deploy.
|
||||
"""
|
||||
task.process_event('resume')
|
||||
node = task.node
|
||||
LOG.debug('Continuing the deployment on node %s', node.uuid)
|
||||
ilo_common.cleanup_vmedia_boot(task)
|
||||
|
||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||
uuid_dict = iscsi_deploy.do_agent_iscsi_deploy(task, self._client)
|
||||
root_uuid = uuid_dict.get('root uuid')
|
||||
|
||||
if deploy_utils.get_boot_option(node) == "local" or iwdi:
|
||||
efi_system_part_uuid = uuid_dict.get(
|
||||
'efi system partition uuid')
|
||||
self.configure_local_boot(
|
||||
task, root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid)
|
||||
else:
|
||||
self._configure_vmedia_boot(task, root_uuid)
|
||||
|
||||
# Set boot mode
|
||||
ilo_common.update_boot_mode(task)
|
||||
|
||||
# Need to enable secure boot, if being requested
|
||||
_update_secure_boot_mode(task, True)
|
||||
|
||||
self.reboot_and_finish_deploy(task)
|
||||
|
154
ironic/drivers/modules/ilo/vendor.py
Normal file
154
ironic/drivers/modules/ilo/vendor.py
Normal file
@ -0,0 +1,154 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Vendor Interface for iLO drivers and its supporting methods.
|
||||
"""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.ilo import common as ilo_common
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class IloVirtualMediaAgentVendorInterface(agent.AgentVendorInterface):
|
||||
"""Interface for vendor passthru related actions."""
|
||||
|
||||
def reboot_to_instance(self, task, **kwargs):
|
||||
node = task.node
|
||||
LOG.debug('Preparing to reboot to instance for node %s',
|
||||
node.uuid)
|
||||
|
||||
error = self.check_deploy_success(node)
|
||||
if error is None:
|
||||
# Set boot mode
|
||||
ilo_common.update_boot_mode(task)
|
||||
|
||||
# Need to enable secure boot, if being requested
|
||||
ilo_common.update_secure_boot_mode(task, True)
|
||||
|
||||
super(IloVirtualMediaAgentVendorInterface,
|
||||
self).reboot_to_instance(task, **kwargs)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def continue_deploy(self, task, **kwargs):
|
||||
ilo_common.cleanup_vmedia_boot(task)
|
||||
super(IloVirtualMediaAgentVendorInterface,
|
||||
self).continue_deploy(task, **kwargs)
|
||||
|
||||
|
||||
class VendorPassthru(iscsi_deploy.VendorPassthru):
|
||||
"""Vendor-specific interfaces for iLO deploy drivers."""
|
||||
|
||||
def get_properties(self):
|
||||
return {}
|
||||
|
||||
def validate(self, task, method, **kwargs):
|
||||
"""Validate vendor-specific actions.
|
||||
|
||||
Checks if a valid vendor passthru method was passed and validates
|
||||
the parameters for the vendor passthru method.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param method: method to be validated.
|
||||
:param kwargs: kwargs containing the vendor passthru method's
|
||||
parameters.
|
||||
:raises: MissingParameterValue, if some required parameters were not
|
||||
passed.
|
||||
:raises: InvalidParameterValue, if any of the parameters have invalid
|
||||
value.
|
||||
"""
|
||||
if method == 'boot_into_iso':
|
||||
self._validate_boot_into_iso(task, kwargs)
|
||||
return
|
||||
super(VendorPassthru, self).validate(task, method, **kwargs)
|
||||
|
||||
@base.passthru(['POST'])
|
||||
@task_manager.require_exclusive_lock
|
||||
def pass_deploy_info(self, task, **kwargs):
|
||||
"""Continues the deployment of baremetal node over iSCSI.
|
||||
|
||||
This method continues the deployment of the baremetal node over iSCSI
|
||||
from where the deployment ramdisk has left off.
|
||||
This updates boot mode and secure boot settings, if required.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param **kwargs: kwargs for performing iscsi deployment.
|
||||
:raises: InvalidState
|
||||
"""
|
||||
ilo_common.update_boot_mode(task)
|
||||
ilo_common.update_secure_boot_mode(task, True)
|
||||
super(VendorPassthru, self).pass_deploy_info(task, **kwargs)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def continue_deploy(self, task, **kwargs):
|
||||
"""Method invoked when deployed with the IPA ramdisk.
|
||||
|
||||
This method is invoked during a heartbeat from an agent when
|
||||
the node is in wait-call-back state.
|
||||
This updates boot mode and secure boot settings, if required.
|
||||
"""
|
||||
ilo_common.update_boot_mode(task)
|
||||
ilo_common.update_secure_boot_mode(task, True)
|
||||
super(VendorPassthru, self).continue_deploy(task, **kwargs)
|
||||
|
||||
def _validate_boot_into_iso(self, task, kwargs):
|
||||
"""Validates if attach_iso can be called and if inputs are proper."""
|
||||
if not (task.node.provision_state == states.MANAGEABLE or
|
||||
task.node.maintenance is True):
|
||||
msg = (_("The requested action 'boot_into_iso' can be performed "
|
||||
"only when node %(node_uuid)s is in %(state)s state or "
|
||||
"in 'maintenance' mode") %
|
||||
{'node_uuid': task.node.uuid,
|
||||
'state': states.MANAGEABLE})
|
||||
raise exception.InvalidStateRequested(msg)
|
||||
d_info = {'boot_iso_href': kwargs.get('boot_iso_href')}
|
||||
error_msg = _("Error validating input for boot_into_iso vendor "
|
||||
"passthru. Some parameters were not provided: ")
|
||||
deploy_utils.check_for_missing_params(d_info, error_msg)
|
||||
deploy_utils.validate_image_properties(
|
||||
task.context, {'image_source': kwargs.get('boot_iso_href')}, [])
|
||||
|
||||
@base.passthru(['POST'])
|
||||
@task_manager.require_exclusive_lock
|
||||
def boot_into_iso(self, task, **kwargs):
|
||||
"""Attaches an ISO image in glance and reboots bare metal.
|
||||
|
||||
This method accepts an ISO image href (a Glance UUID or an HTTP(S) URL)
|
||||
attaches it as virtual media and then reboots the node. This is
|
||||
useful for debugging purposes. This can be invoked only when the node
|
||||
is in manage state.
|
||||
|
||||
:param task: A TaskManager object.
|
||||
:param kwargs: The arguments sent with vendor passthru. The expected
|
||||
kwargs are::
|
||||
|
||||
'boot_iso_href': href of the image to be booted. This can be
|
||||
a Glance UUID or an HTTP(S) URL.
|
||||
"""
|
||||
ilo_common.setup_vmedia(task, kwargs['boot_iso_href'],
|
||||
ramdisk_options=None)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
@ -33,6 +33,7 @@ from ironic.drivers.modules.ilo import deploy as ilo_deploy
|
||||
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
||||
from ironic.drivers.modules.ilo import management as ilo_management
|
||||
from ironic.drivers.modules.ilo import power as ilo_power
|
||||
from ironic.drivers.modules.ilo import vendor as ilo_vendor
|
||||
from ironic.drivers.modules import inspector
|
||||
from ironic.drivers.modules import ipminative
|
||||
from ironic.drivers.modules import ipmitool
|
||||
@ -210,7 +211,7 @@ class PXEAndIloDriver(base.BaseDriver):
|
||||
self.power = ilo_power.IloPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = ilo_deploy.IloPXEDeploy()
|
||||
self.vendor = ilo_deploy.IloPXEVendorPassthru()
|
||||
self.vendor = ilo_vendor.VendorPassthru()
|
||||
self.console = ilo_deploy.IloConsoleInterface()
|
||||
self.management = ilo_management.IloManagement()
|
||||
self.inspect = ilo_inspect.IloInspect()
|
||||
|
540
ironic/tests/unit/drivers/modules/ilo/test_boot.py
Normal file
540
ironic/tests/unit/drivers/modules/ilo/test_boot.py
Normal file
@ -0,0 +1,540 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test class for common methods used by iLO modules."""
|
||||
|
||||
import tempfile
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import service_utils
|
||||
from ironic.common import image_service
|
||||
from ironic.common import images
|
||||
from ironic.common import swift
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.ilo import boot as ilo_boot
|
||||
from ironic.drivers.modules.ilo import common as ilo_common
|
||||
from ironic.drivers import utils as driver_utils
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.db import utils as db_utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
if six.PY3:
|
||||
import io
|
||||
file = io.BytesIO
|
||||
|
||||
INFO_DICT = db_utils.get_test_ilo_info()
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class IloBootPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IloBootPrivateMethodsTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="iscsi_ilo")
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, driver='iscsi_ilo', driver_info=INFO_DICT)
|
||||
|
||||
def test__get_boot_iso_object_name(self):
|
||||
boot_iso_actual = ilo_boot._get_boot_iso_object_name(self.node)
|
||||
boot_iso_expected = "boot-%s" % self.node.uuid
|
||||
self.assertEqual(boot_iso_expected, boot_iso_actual)
|
||||
|
||||
@mock.patch.object(image_service.HttpImageService, 'validate_href',
|
||||
spec_set=True, autospec=True)
|
||||
def test__get_boot_iso_http_url(self, service_mock):
|
||||
url = 'http://abc.org/image/qcow2'
|
||||
i_info = self.node.instance_info
|
||||
i_info['ilo_boot_iso'] = url
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
|
||||
service_mock.assert_called_once_with(mock.ANY, url)
|
||||
self.assertEqual(url, boot_iso_actual)
|
||||
|
||||
@mock.patch.object(image_service.HttpImageService, 'validate_href',
|
||||
spec_set=True, autospec=True)
|
||||
def test__get_boot_iso_unsupported_url(self, validate_href_mock):
|
||||
validate_href_mock.side_effect = iter(
|
||||
[exception.ImageRefValidationFailed(
|
||||
image_href='file://img.qcow2', reason='fail')])
|
||||
url = 'file://img.qcow2'
|
||||
i_info = self.node.instance_info
|
||||
i_info['ilo_boot_iso'] = url
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.ImageRefValidationFailed,
|
||||
ilo_boot._get_boot_iso, task, 'root-uuid')
|
||||
|
||||
@mock.patch.object(images, 'get_image_properties', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
|
||||
autospec=True)
|
||||
def test__get_boot_iso_glance_image(self, deploy_info_mock,
|
||||
image_props_mock):
|
||||
deploy_info_mock.return_value = {'image_source': 'image-uuid',
|
||||
'ilo_deploy_iso': 'deploy_iso_uuid'}
|
||||
image_props_mock.return_value = {'boot_iso': u'glance://uui\u0111',
|
||||
'kernel_id': None,
|
||||
'ramdisk_id': None}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info['boot_iso_created_in_web_server'] = False
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
|
||||
deploy_info_mock.assert_called_once_with(task.node)
|
||||
image_props_mock.assert_called_once_with(
|
||||
task.context, 'image-uuid',
|
||||
['boot_iso', 'kernel_id', 'ramdisk_id'])
|
||||
boot_iso_expected = u'glance://uui\u0111'
|
||||
self.assertEqual(boot_iso_expected, boot_iso_actual)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_boot.LOG, 'error', spec_set=True, autospec=True)
|
||||
@mock.patch.object(images, 'get_image_properties', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
|
||||
autospec=True)
|
||||
def test__get_boot_iso_uefi_no_glance_image(self,
|
||||
deploy_info_mock,
|
||||
image_props_mock,
|
||||
log_mock,
|
||||
boot_mode_mock):
|
||||
deploy_info_mock.return_value = {'image_source': 'image-uuid',
|
||||
'ilo_deploy_iso': 'deploy_iso_uuid'}
|
||||
image_props_mock.return_value = {'boot_iso': None,
|
||||
'kernel_id': None,
|
||||
'ramdisk_id': None}
|
||||
properties = {'capabilities': 'boot_mode:uefi'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.properties = properties
|
||||
boot_iso_result = ilo_boot._get_boot_iso(task, 'root-uuid')
|
||||
deploy_info_mock.assert_called_once_with(task.node)
|
||||
image_props_mock.assert_called_once_with(
|
||||
task.context, 'image-uuid',
|
||||
['boot_iso', 'kernel_id', 'ramdisk_id'])
|
||||
self.assertTrue(log_mock.called)
|
||||
self.assertFalse(boot_mode_mock.called)
|
||||
self.assertIsNone(boot_iso_result)
|
||||
|
||||
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
|
||||
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(images, 'get_image_properties', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
|
||||
autospec=True)
|
||||
def test__get_boot_iso_create(self, deploy_info_mock, image_props_mock,
|
||||
capability_mock, boot_object_name_mock,
|
||||
swift_api_mock,
|
||||
create_boot_iso_mock, tempfile_mock):
|
||||
CONF.ilo.swift_ilo_container = 'ilo-cont'
|
||||
CONF.pxe.pxe_append_params = 'kernel-params'
|
||||
|
||||
swift_obj_mock = swift_api_mock.return_value
|
||||
fileobj_mock = mock.MagicMock(spec=file)
|
||||
fileobj_mock.name = 'tmpfile'
|
||||
mock_file_handle = mock.MagicMock(spec=file)
|
||||
mock_file_handle.__enter__.return_value = fileobj_mock
|
||||
tempfile_mock.return_value = mock_file_handle
|
||||
|
||||
deploy_info_mock.return_value = {'image_source': 'image-uuid',
|
||||
'ilo_deploy_iso': 'deploy_iso_uuid'}
|
||||
image_props_mock.return_value = {'boot_iso': None,
|
||||
'kernel_id': 'kernel_uuid',
|
||||
'ramdisk_id': 'ramdisk_uuid'}
|
||||
boot_object_name_mock.return_value = 'abcdef'
|
||||
create_boot_iso_mock.return_value = '/path/to/boot-iso'
|
||||
capability_mock.return_value = 'uefi'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
|
||||
deploy_info_mock.assert_called_once_with(task.node)
|
||||
image_props_mock.assert_called_once_with(
|
||||
task.context, 'image-uuid',
|
||||
['boot_iso', 'kernel_id', 'ramdisk_id'])
|
||||
boot_object_name_mock.assert_called_once_with(task.node)
|
||||
create_boot_iso_mock.assert_called_once_with(task.context,
|
||||
'tmpfile',
|
||||
'kernel_uuid',
|
||||
'ramdisk_uuid',
|
||||
'deploy_iso_uuid',
|
||||
'root-uuid',
|
||||
'kernel-params',
|
||||
'uefi')
|
||||
swift_obj_mock.create_object.assert_called_once_with('ilo-cont',
|
||||
'abcdef',
|
||||
'tmpfile')
|
||||
boot_iso_expected = 'swift:abcdef'
|
||||
self.assertEqual(boot_iso_expected, boot_iso_actual)
|
||||
|
||||
@mock.patch.object(ilo_common, 'copy_image_to_web_server', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(images, 'get_image_properties', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
|
||||
autospec=True)
|
||||
def test__get_boot_iso_create_use_webserver_true_ramdisk_webserver(
|
||||
self, deploy_info_mock, image_props_mock,
|
||||
capability_mock, boot_object_name_mock,
|
||||
create_boot_iso_mock, tempfile_mock,
|
||||
copy_file_mock):
|
||||
CONF.ilo.swift_ilo_container = 'ilo-cont'
|
||||
CONF.ilo.use_web_server_for_images = True
|
||||
CONF.deploy.http_url = "http://10.10.1.30/httpboot"
|
||||
CONF.deploy.http_root = "/httpboot"
|
||||
CONF.pxe.pxe_append_params = 'kernel-params'
|
||||
|
||||
fileobj_mock = mock.MagicMock(spec=file)
|
||||
fileobj_mock.name = 'tmpfile'
|
||||
mock_file_handle = mock.MagicMock(spec=file)
|
||||
mock_file_handle.__enter__.return_value = fileobj_mock
|
||||
tempfile_mock.return_value = mock_file_handle
|
||||
|
||||
ramdisk_href = "http://10.10.1.30/httpboot/ramdisk"
|
||||
kernel_href = "http://10.10.1.30/httpboot/kernel"
|
||||
deploy_info_mock.return_value = {'image_source': 'image-uuid',
|
||||
'ilo_deploy_iso': 'deploy_iso_uuid'}
|
||||
image_props_mock.return_value = {'boot_iso': None,
|
||||
'kernel_id': kernel_href,
|
||||
'ramdisk_id': ramdisk_href}
|
||||
boot_object_name_mock.return_value = 'abcdef'
|
||||
create_boot_iso_mock.return_value = '/path/to/boot-iso'
|
||||
capability_mock.return_value = 'uefi'
|
||||
copy_file_mock.return_value = "http://10.10.1.30/httpboot/abcdef"
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
|
||||
deploy_info_mock.assert_called_once_with(task.node)
|
||||
image_props_mock.assert_called_once_with(
|
||||
task.context, 'image-uuid',
|
||||
['boot_iso', 'kernel_id', 'ramdisk_id'])
|
||||
boot_object_name_mock.assert_called_once_with(task.node)
|
||||
create_boot_iso_mock.assert_called_once_with(task.context,
|
||||
'tmpfile',
|
||||
kernel_href,
|
||||
ramdisk_href,
|
||||
'deploy_iso_uuid',
|
||||
'root-uuid',
|
||||
'kernel-params',
|
||||
'uefi')
|
||||
boot_iso_expected = 'http://10.10.1.30/httpboot/abcdef'
|
||||
self.assertEqual(boot_iso_expected, boot_iso_actual)
|
||||
copy_file_mock.assert_called_once_with(fileobj_mock.name,
|
||||
'abcdef')
|
||||
|
||||
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
|
||||
def test__clean_up_boot_iso_for_instance(self, swift_mock,
|
||||
boot_object_name_mock):
|
||||
swift_obj_mock = swift_mock.return_value
|
||||
CONF.ilo.swift_ilo_container = 'ilo-cont'
|
||||
boot_object_name_mock.return_value = 'boot-object'
|
||||
i_info = self.node.instance_info
|
||||
i_info['ilo_boot_iso'] = 'swift:bootiso'
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
ilo_boot._clean_up_boot_iso_for_instance(self.node)
|
||||
swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
|
||||
'boot-object')
|
||||
|
||||
@mock.patch.object(ilo_boot.LOG, 'exception', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
|
||||
def test__clean_up_boot_iso_for_instance_exc(self, swift_mock,
|
||||
boot_object_name_mock,
|
||||
log_mock):
|
||||
swift_obj_mock = swift_mock.return_value
|
||||
exc = exception.SwiftObjectNotFoundError('error')
|
||||
swift_obj_mock.delete_object.side_effect = exc
|
||||
CONF.ilo.swift_ilo_container = 'ilo-cont'
|
||||
boot_object_name_mock.return_value = 'boot-object'
|
||||
i_info = self.node.instance_info
|
||||
i_info['ilo_boot_iso'] = 'swift:bootiso'
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
ilo_boot._clean_up_boot_iso_for_instance(self.node)
|
||||
swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
|
||||
'boot-object')
|
||||
self.assertTrue(log_mock.called)
|
||||
|
||||
@mock.patch.object(utils, 'unlink_without_raise', spec_set=True,
|
||||
autospec=True)
|
||||
def test__clean_up_boot_iso_for_instance_on_webserver(self, unlink_mock):
|
||||
|
||||
CONF.ilo.use_web_server_for_images = True
|
||||
CONF.deploy.http_root = "/webserver"
|
||||
i_info = self.node.instance_info
|
||||
i_info['ilo_boot_iso'] = 'http://x.y.z.a/webserver/boot-object'
|
||||
self.node.instance_info = i_info
|
||||
self.node.save()
|
||||
boot_iso_path = "/webserver/boot-object"
|
||||
ilo_boot._clean_up_boot_iso_for_instance(self.node)
|
||||
unlink_mock.assert_called_once_with(boot_iso_path)
|
||||
|
||||
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
|
||||
autospec=True)
|
||||
def test__clean_up_boot_iso_for_instance_no_boot_iso(
|
||||
self, boot_object_name_mock):
|
||||
ilo_boot._clean_up_boot_iso_for_instance(self.node)
|
||||
self.assertFalse(boot_object_name_mock.called)
|
||||
|
||||
@mock.patch.object(ilo_boot, 'parse_driver_info', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'get_image_instance_info',
|
||||
spec_set=True, autospec=True)
|
||||
def test__parse_deploy_info(self, instance_info_mock, driver_info_mock):
|
||||
instance_info_mock.return_value = {'a': 'b'}
|
||||
driver_info_mock.return_value = {'c': 'd'}
|
||||
expected_info = {'a': 'b', 'c': 'd'}
|
||||
actual_info = ilo_boot._parse_deploy_info(self.node)
|
||||
self.assertEqual(expected_info, actual_info)
|
||||
|
||||
|
||||
class IloVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IloVirtualMediaBootTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="iscsi_ilo")
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, driver='iscsi_ilo', driver_info=INFO_DICT)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
|
||||
autospec=True)
|
||||
def _test_validate(self,
|
||||
deploy_info_mock,
|
||||
validate_prop_mock,
|
||||
props_expected):
|
||||
d_info = {'image_source': 'uuid'}
|
||||
deploy_info_mock.return_value = d_info
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.boot.validate(task)
|
||||
deploy_info_mock.assert_called_once_with(task.node)
|
||||
validate_prop_mock.assert_called_once_with(
|
||||
task.context, d_info, props_expected)
|
||||
|
||||
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
|
||||
autospec=True)
|
||||
def test_validate_glance_partition_image(self, is_glance_image_mock):
|
||||
is_glance_image_mock.return_value = True
|
||||
self._test_validate(props_expected=['kernel_id', 'ramdisk_id'])
|
||||
|
||||
def test_validate_whole_disk_image(self):
|
||||
self.node.driver_internal_info = {'is_whole_disk_image': True}
|
||||
self.node.save()
|
||||
self._test_validate(props_expected=[])
|
||||
|
||||
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
|
||||
autospec=True)
|
||||
def test_validate_non_glance_partition_image(self, is_glance_image_mock):
|
||||
is_glance_image_mock.return_value = False
|
||||
self._test_validate(props_expected=['kernel', 'ramdisk'])
|
||||
|
||||
@mock.patch.object(ilo_common, 'eject_vmedia_devices',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_common, 'setup_vmedia', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id',
|
||||
spec_set=True, autospec=True)
|
||||
def _test_prepare_ramdisk(self, get_nic_mock, setup_vmedia_mock,
|
||||
eject_mock, ilo_boot_iso, image_source,
|
||||
ramdisk_params={'a': 'b'}):
|
||||
instance_info = self.node.instance_info
|
||||
instance_info['ilo_boot_iso'] = ilo_boot_iso
|
||||
instance_info['image_source'] = image_source
|
||||
self.node.instance_info = instance_info
|
||||
self.node.save()
|
||||
|
||||
get_nic_mock.return_value = '12:34:56:78:90:ab'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
|
||||
task.node.driver_info['ilo_deploy_iso'] = 'deploy-iso'
|
||||
|
||||
task.driver.boot.prepare_ramdisk(task, ramdisk_params)
|
||||
|
||||
eject_mock.assert_called_once_with(task)
|
||||
expected_ramdisk_opts = {'a': 'b', 'BOOTIF': '12:34:56:78:90:ab'}
|
||||
get_nic_mock.assert_called_once_with(task)
|
||||
setup_vmedia_mock.assert_called_once_with(task, 'deploy-iso',
|
||||
expected_ramdisk_opts)
|
||||
|
||||
def test_prepare_ramdisk_glance_image(self):
|
||||
self._test_prepare_ramdisk(
|
||||
ilo_boot_iso='swift:abcdef',
|
||||
image_source='6b2f0c0c-79e8-4db6-842e-43c9764204af')
|
||||
self.node.refresh()
|
||||
self.assertNotIn('ilo_boot_iso', self.node.instance_info)
|
||||
|
||||
def test_prepare_ramdisk_not_a_glance_image(self):
|
||||
self._test_prepare_ramdisk(
|
||||
ilo_boot_iso='http://mybootiso',
|
||||
image_source='http://myimage')
|
||||
self.node.refresh()
|
||||
self.assertEqual('http://mybootiso',
|
||||
self.node.instance_info['ilo_boot_iso'])
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_get_boot_iso', spec_set=True,
|
||||
autospec=True)
|
||||
def test__configure_vmedia_boot_with_boot_iso(
|
||||
self, get_boot_iso_mock, setup_vmedia_mock, set_boot_device_mock):
|
||||
root_uuid = {'root uuid': 'root_uuid'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
get_boot_iso_mock.return_value = 'boot.iso'
|
||||
|
||||
task.driver.boot._configure_vmedia_boot(
|
||||
task, root_uuid)
|
||||
|
||||
get_boot_iso_mock.assert_called_once_with(
|
||||
task, root_uuid)
|
||||
setup_vmedia_mock.assert_called_once_with(
|
||||
task, 'boot.iso')
|
||||
set_boot_device_mock.assert_called_once_with(
|
||||
task, boot_devices.CDROM, persistent=True)
|
||||
self.assertEqual('boot.iso',
|
||||
task.node.instance_info['ilo_boot_iso'])
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_get_boot_iso', spec_set=True,
|
||||
autospec=True)
|
||||
def test__configure_vmedia_boot_without_boot_iso(
|
||||
self, get_boot_iso_mock, setup_vmedia_mock, set_boot_device_mock):
|
||||
root_uuid = {'root uuid': 'root_uuid'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
get_boot_iso_mock.return_value = None
|
||||
|
||||
task.driver.boot._configure_vmedia_boot(
|
||||
task, root_uuid)
|
||||
|
||||
get_boot_iso_mock.assert_called_once_with(
|
||||
task, root_uuid)
|
||||
self.assertFalse(setup_vmedia_mock.called)
|
||||
self.assertFalse(set_boot_device_mock.called)
|
||||
|
||||
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_boot, '_clean_up_boot_iso_for_instance',
|
||||
spec_set=True, autospec=True)
|
||||
def test_clean_up_instance(self, cleanup_iso_mock,
|
||||
cleanup_vmedia_mock):
|
||||
CONF.ilo.use_web_server_for_images = False
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.boot.clean_up_instance(task)
|
||||
cleanup_iso_mock.assert_called_once_with(task.node)
|
||||
cleanup_vmedia_mock.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
|
||||
autospec=True)
|
||||
def test_clean_up_ramdisk(self, cleanup_vmedia_mock):
|
||||
CONF.ilo.use_web_server_for_images = False
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.boot.clean_up_ramdisk(task)
|
||||
cleanup_vmedia_mock.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
|
||||
autospec=True)
|
||||
def _test_prepare_instance_whole_disk_image(
|
||||
self, cleanup_vmedia_boot_mock, set_boot_device_mock):
|
||||
self.node.driver_internal_info = {'is_whole_disk_image': True}
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
cleanup_vmedia_boot_mock.assert_called_once_with(task)
|
||||
set_boot_device_mock.assert_called_once_with(task,
|
||||
boot_devices.DISK,
|
||||
persistent=True)
|
||||
|
||||
def test_prepare_instance_whole_disk_image_local(self):
|
||||
self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
|
||||
self.node.save()
|
||||
self._test_prepare_instance_whole_disk_image()
|
||||
|
||||
def test_prepare_instance_whole_disk_image(self):
|
||||
self._test_prepare_instance_whole_disk_image()
|
||||
|
||||
@mock.patch.object(ilo_boot.IloVirtualMediaBoot,
|
||||
'_configure_vmedia_boot', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
|
||||
autospec=True)
|
||||
def test_prepare_instance_partition_image(
|
||||
self, cleanup_vmedia_boot_mock,
|
||||
configure_vmedia_mock):
|
||||
self.node.driver_internal_info = {'root_uuid_or_disk_id': "some_uuid"}
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
cleanup_vmedia_boot_mock.assert_called_once_with(task)
|
||||
configure_vmedia_mock.assert_called_once_with(mock.ANY, task,
|
||||
"some_uuid")
|
@ -24,11 +24,14 @@ from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common import images
|
||||
from ironic.common import swift
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.ilo import common as ilo_common
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
@ -721,3 +724,46 @@ class IloCommonMethodsTestCase(db_base.DbTestCase):
|
||||
ilo_common.destroy_floppy_image_from_web_server(task.node)
|
||||
get_floppy_name_mock.assert_called_once_with(task.node)
|
||||
utils_mock.assert_called_once_with('/webserver/image-uuid')
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
|
||||
autospec=True)
|
||||
def test_setup_vmedia(self,
|
||||
func_setup_vmedia_for_boot,
|
||||
func_set_boot_device):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
parameters = {'a': 'b'}
|
||||
iso = '733d1c44-a2ea-414b-aca7-69decf20d810'
|
||||
ilo_common.setup_vmedia(task, iso, parameters)
|
||||
func_setup_vmedia_for_boot.assert_called_once_with(task, iso,
|
||||
parameters)
|
||||
func_set_boot_device.assert_called_once_with(task,
|
||||
boot_devices.CDROM)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'is_secure_boot_requested', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'set_secure_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
def test__update_secure_boot_mode_passed_true(self,
|
||||
func_set_secure_boot_mode,
|
||||
func_is_secure_boot_req):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
func_is_secure_boot_req.return_value = True
|
||||
ilo_common.update_secure_boot_mode(task, True)
|
||||
func_set_secure_boot_mode.assert_called_once_with(task, True)
|
||||
|
||||
@mock.patch.object(deploy_utils, 'is_secure_boot_requested', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'set_secure_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
def test__update_secure_boot_mode_passed_false(self,
|
||||
func_set_secure_boot_mode,
|
||||
func_is_secure_boot_req):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
func_is_secure_boot_req.return_value = False
|
||||
ilo_common.update_secure_boot_mode(task, False)
|
||||
self.assertFalse(func_set_secure_boot_mode.called)
|
||||
|
File diff suppressed because it is too large
Load Diff
228
ironic/tests/unit/drivers/modules/ilo/test_vendor.py
Normal file
228
ironic/tests/unit/drivers/modules/ilo/test_vendor.py
Normal file
@ -0,0 +1,228 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test class for common methods used by iLO modules."""
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules.ilo import common as ilo_common
|
||||
from ironic.drivers.modules.ilo import vendor as ilo_vendor
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.db import utils as db_utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
if six.PY3:
|
||||
import io
|
||||
file = io.BytesIO
|
||||
|
||||
INFO_DICT = db_utils.get_test_ilo_info()
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class VendorPassthruTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VendorPassthruTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="iscsi_ilo")
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='iscsi_ilo',
|
||||
driver_info=INFO_DICT)
|
||||
|
||||
@mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'setup_vmedia', spec_set=True,
|
||||
autospec=True)
|
||||
def test_boot_into_iso(self, setup_vmedia_mock, power_action_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.vendor.boot_into_iso(task, boot_iso_href='foo')
|
||||
setup_vmedia_mock.assert_called_once_with(task, 'foo',
|
||||
ramdisk_options=None)
|
||||
power_action_mock.assert_called_once_with(task, states.REBOOT)
|
||||
|
||||
@mock.patch.object(ilo_vendor.VendorPassthru, '_validate_boot_into_iso',
|
||||
spec_set=True, autospec=True)
|
||||
def test_validate_boot_into_iso(self, validate_boot_into_iso_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
vendor = ilo_vendor.VendorPassthru()
|
||||
vendor.validate(task, method='boot_into_iso', foo='bar')
|
||||
validate_boot_into_iso_mock.assert_called_once_with(
|
||||
vendor, task, {'foo': 'bar'})
|
||||
|
||||
def test__validate_boot_into_iso_invalid_state(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.AVAILABLE
|
||||
self.assertRaises(
|
||||
exception.InvalidStateRequested,
|
||||
task.driver.vendor._validate_boot_into_iso,
|
||||
task, {})
|
||||
|
||||
def test__validate_boot_into_iso_missing_boot_iso_href(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.MANAGEABLE
|
||||
self.assertRaises(
|
||||
exception.MissingParameterValue,
|
||||
task.driver.vendor._validate_boot_into_iso,
|
||||
task, {})
|
||||
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
spec_set=True, autospec=True)
|
||||
def test__validate_boot_into_iso_manage(self, validate_image_prop_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
info = {'boot_iso_href': 'foo'}
|
||||
task.node.provision_state = states.MANAGEABLE
|
||||
task.driver.vendor._validate_boot_into_iso(
|
||||
task, info)
|
||||
validate_image_prop_mock.assert_called_once_with(
|
||||
task.context, {'image_source': 'foo'}, [])
|
||||
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
spec_set=True, autospec=True)
|
||||
def test__validate_boot_into_iso_maintenance(
|
||||
self, validate_image_prop_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
info = {'boot_iso_href': 'foo'}
|
||||
task.node.maintenance = True
|
||||
task.driver.vendor._validate_boot_into_iso(
|
||||
task, info)
|
||||
validate_image_prop_mock.assert_called_once_with(
|
||||
task.context, {'image_source': 'foo'}, [])
|
||||
|
||||
@mock.patch.object(iscsi_deploy.VendorPassthru, 'pass_deploy_info',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pass_deploy_info(self, func_update_boot_mode,
|
||||
func_update_secure_boot_mode,
|
||||
vendorpassthru_mock):
|
||||
kwargs = {'address': '123456'}
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.DEPLOYWAIT
|
||||
task.node.target_provision_state = states.ACTIVE
|
||||
task.driver.vendor.pass_deploy_info(task, **kwargs)
|
||||
func_update_boot_mode.assert_called_once_with(task)
|
||||
func_update_secure_boot_mode.assert_called_once_with(task, True)
|
||||
vendorpassthru_mock.assert_called_once_with(
|
||||
mock.ANY, task, **kwargs)
|
||||
|
||||
@mock.patch.object(iscsi_deploy.VendorPassthru, 'continue_deploy',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_secure_boot_mode', autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_boot_mode', autospec=True)
|
||||
def test_continue_deploy(self,
|
||||
func_update_boot_mode,
|
||||
func_update_secure_boot_mode,
|
||||
pxe_vendorpassthru_mock):
|
||||
kwargs = {'address': '123456'}
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.DEPLOYWAIT
|
||||
task.node.target_provision_state = states.ACTIVE
|
||||
task.driver.vendor.continue_deploy(task, **kwargs)
|
||||
func_update_boot_mode.assert_called_once_with(task)
|
||||
func_update_secure_boot_mode.assert_called_once_with(task, True)
|
||||
pxe_vendorpassthru_mock.assert_called_once_with(
|
||||
mock.ANY, task, **kwargs)
|
||||
|
||||
|
||||
class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IloVirtualMediaAgentVendorInterfaceTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="agent_ilo")
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, driver='agent_ilo', driver_info=INFO_DICT)
|
||||
|
||||
@mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
def test_reboot_to_instance(self, func_update_secure_boot_mode,
|
||||
func_update_boot_mode,
|
||||
check_deploy_success_mock,
|
||||
agent_reboot_to_instance_mock):
|
||||
kwargs = {'address': '123456'}
|
||||
check_deploy_success_mock.return_value = None
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.vendor.reboot_to_instance(task, **kwargs)
|
||||
check_deploy_success_mock.assert_called_once_with(
|
||||
mock.ANY, task.node)
|
||||
func_update_boot_mode.assert_called_once_with(task)
|
||||
func_update_secure_boot_mode.assert_called_once_with(task, True)
|
||||
agent_reboot_to_instance_mock.assert_called_once_with(
|
||||
mock.ANY, task, **kwargs)
|
||||
|
||||
@mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
|
||||
autospec=True)
|
||||
def test_reboot_to_instance_deploy_fail(self, func_update_secure_boot_mode,
|
||||
func_update_boot_mode,
|
||||
check_deploy_success_mock,
|
||||
agent_reboot_to_instance_mock):
|
||||
kwargs = {'address': '123456'}
|
||||
check_deploy_success_mock.return_value = "Error"
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.vendor.reboot_to_instance(task, **kwargs)
|
||||
check_deploy_success_mock.assert_called_once_with(
|
||||
mock.ANY, task.node)
|
||||
self.assertFalse(func_update_boot_mode.called)
|
||||
self.assertFalse(func_update_secure_boot_mode.called)
|
||||
agent_reboot_to_instance_mock.assert_called_once_with(
|
||||
mock.ANY, task, **kwargs)
|
||||
|
||||
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(agent.AgentVendorInterface, 'continue_deploy',
|
||||
spec_set=True, autospec=True)
|
||||
def test_continue_deploy(self, agent_continue_deploy_mock,
|
||||
cleanup_mock):
|
||||
CONF.ilo.use_web_server_for_images = True
|
||||
kwargs = {'address': '123456'}
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.vendor.continue_deploy(task, **kwargs)
|
||||
cleanup_mock.assert_called_once_with(task)
|
||||
agent_continue_deploy_mock.assert_called_once_with(
|
||||
mock.ANY, task, **kwargs)
|
Loading…
Reference in New Issue
Block a user