IloVirtualMediaIscsi deploy driver

This commit introduces a new iLo deploy driver which uses virtual
media to boot up proliant baremetal nodes, and uses iSCSI to deploy
the baremetal nodes.

Change-Id: I98f47ed6082a3a28fce3148f6d5177cdb5c61881
Implements: blueprint ironic-ilo-virtualmedia-driver
This commit is contained in:
Ramakrishnan G 2014-08-26 00:33:16 +05:30 committed by Jim Rollenhagen
parent 1773bcd834
commit 571579a005
14 changed files with 1380 additions and 51 deletions

View File

@ -838,6 +838,13 @@
# Port to be used for iLO operations (integer value)
#client_port=443
# The Swift iLO container to store data. (string value)
#swift_ilo_container=ironic_ilo_container
# Amount of time in seconds for Swift objects to auto-expire.
# (integer value)
#swift_object_expiry_timeout=900
#
# Options defined in ironic.drivers.modules.ilo.power

View File

@ -308,3 +308,65 @@ def converted_size(path):
if data.file_format == "raw" or not CONF.force_raw_images:
return 0
return data.virtual_size
def get_glance_image_property(context, image_uuid, property):
"""Returns the value of a glance image property.
:param context: context
:param image_uuid: the UUID of the image in glance
:param property: the property whose value is required.
:returns: the value of the property if it exists, otherwise None.
"""
glance_service = service.Service(version=1, context=context)
iproperties = glance_service.show(image_uuid)['properties']
return iproperties.get(property)
def get_temp_url_for_glance_image(context, image_uuid):
"""Returns the tmp url for a glance image.
:param context: context
:param image_uuid: the UUID of the image in glance
:returns: the tmp url for the glance image.
"""
# Glance API version 2 is required for getting direct_url of the image.
glance_service = service.Service(version=2, context=context)
image_properties = glance_service.show(image_uuid)
LOG.debug('Got image info: %(info)s for image %(image_uuid)s.',
{'info': image_properties, 'image_uuid': image_uuid})
return glance_service.swift_temp_url(image_properties)
def create_boot_iso(context, output_filename, kernel_uuid,
ramdisk_uuid, root_uuid=None, kernel_params=None):
"""Creates a bootable ISO image for a node.
Given the glance UUID of kernel, ramdisk, root partition's UUID and
kernel cmdline arguments, this method fetches the kernel, ramdisk from
glance, and builds a bootable ISO image that can be used to boot up the
baremetal node.
:param context: context
:param output_filename: the absolute path of the output ISO file
:param kernel_uuid: glance uuid of the kernel to use
:param ramdisk_uuid: glance uuid of the ramdisk to use
:param root_uuid: uuid of the root filesystem (optional)
:param kernel_params: a string containing whitespace separated values
kernel cmdline arguments of the form K=V or K (optional).
:raises: ImageCreationFailed, if creating boot ISO failed.
"""
with utils.tempdir() as tmpdir:
kernel_path = os.path.join(tmpdir, kernel_uuid)
ramdisk_path = os.path.join(tmpdir, ramdisk_uuid)
fetch_to_raw(context, kernel_uuid, kernel_path)
fetch_to_raw(context, ramdisk_uuid, ramdisk_path)
params = []
if root_uuid:
params.append('root=UUID=%s' % root_uuid)
if kernel_params:
params.append(kernel_params)
create_isolinux_image(output_filename, kernel_path,
ramdisk_path, params)

View File

@ -30,9 +30,9 @@ def _is_apiv3(auth_url, auth_version):
This method inspects auth_url and auth_version, and checks whether V3
version of the API is being used or not.
:param auth_url: a http or https url to be inspected(like
:param auth_url: a http or https url to be inspected (like
'http://127.0.0.1:9898/').
:param auth_version: a string containing the version(like 'v2', 'v3.0')
:param auth_version: a string containing the version (like 'v2', 'v3.0')
:returns: True if V3 of the API is being used.
"""
return auth_version == 'v3.0' or '/v3' in parse.urlparse(auth_url).path
@ -44,9 +44,9 @@ def get_keystone_url(auth_url, auth_version):
Given an auth_url and auth_version, this method generates the url in
which keystone can be reached.
:param auth_url: a http or https url to be inspected(like
:param auth_url: a http or https url to be inspected (like
'http://127.0.0.1:9898/').
:param auth_version: a string containing the version(like v2, v3.0, etc)
:param auth_version: a string containing the version (like v2, v3.0, etc)
:returns: a string containing the keystone url
"""
api_v3 = _is_apiv3(auth_url, auth_version)

48
ironic/drivers/ilo.py Normal file
View File

@ -0,0 +1,48 @@
# Copyright 2014 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.
"""
iLO Driver for managing HP Proliant Gen8 and above servers.
"""
from oslo.utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.drivers import base
from ironic.drivers.modules.ilo import deploy
from ironic.drivers.modules.ilo import power
from ironic.drivers.modules import ipmitool
class IloVirtualMediaIscsiDriver(base.BaseDriver):
"""IloDriver using IloClient interface.
This driver implements the `core` functionality using
:class:ironic.drivers.modules.ilo.power.IloPower for power management.
and
:class:ironic.drivers.modules.ilo.deploy.IloVirtualMediaIscsiDeploy for
deploy.
"""
def __init__(self):
if not importutils.try_import('proliantutils'):
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason=_("Unable to import proliantutils library"))
self.power = power.IloPower()
self.deploy = deploy.IloVirtualMediaIscsiDeploy()
self.console = ipmitool.IPMIShellinaboxConsole()
self.management = ipmitool.IPMIManagement()
self.vendor = deploy.VendorPassthru()

View File

@ -16,21 +16,25 @@
Common functionalities shared between different iLO modules.
"""
import tempfile
from oslo.config import cfg
from oslo.utils import importutils
from ironic.common import exception
from ironic.common import i18n
from ironic.common.i18n import _
from ironic.common import images
from ironic.common import swift
from ironic.common import utils
from ironic.openstack.common import log as logging
ilo_client = importutils.try_import('proliantutils.ilo.ribcl')
STANDARD_LICENSE = 1
ESSENTIALS_LICENSE = 2
ADVANCED_LICENSE = 3
opts = [
cfg.IntOpt('client_timeout',
default=60,
@ -38,6 +42,13 @@ opts = [
cfg.IntOpt('client_port',
default=443,
help='Port to be used for iLO operations'),
cfg.StrOpt('swift_ilo_container',
default='ironic_ilo_container',
help='The Swift iLO container to store data.'),
cfg.IntOpt('swift_object_expiry_timeout',
default=900,
help='Amount of time in seconds for Swift objects to '
'auto-expire.'),
]
CONF = cfg.CONF
@ -45,6 +56,9 @@ CONF.register_opts(opts, group='ilo')
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
REQUIRED_PROPERTIES = {
'ilo_address': _("IP address or hostname of the iLO. Required."),
'ilo_username': _("username for the iLO with administrator privileges. "
@ -156,3 +170,180 @@ def get_ilo_license(node):
return ESSENTIALS_LICENSE
else:
return STANDARD_LICENSE
def _get_floppy_image_name(node):
"""Returns the floppy image name for a given node.
:param node: the node for which image name is to be provided.
"""
return "image-%s" % node.uuid
def _prepare_floppy_image(task, params):
"""Prepares the floppy image for passing the parameters.
This method prepares a temporary vfat filesystem image. Then it adds
two files into the image - one containing the authentication token and
the other containing the parameters to be passed to the ramdisk. Then it
uploads the file to Swift in 'swift_ilo_container', setting it to
auto-expire after 'swift_object_expiry_timeout' seconds. Then it returns
the temp url for the Swift object.
:param task: a TaskManager instance containing the node to act on.
:param params: a dictionary containing 'parameter name'->'value' mapping
to be passed to the deploy ramdisk via the floppy image.
:returns: the Swift temp url for the floppy image.
"""
with tempfile.NamedTemporaryFile() as vfat_image_tmpfile_obj:
files_info = {}
token_tmpfile_obj = None
vfat_image_tmpfile = vfat_image_tmpfile_obj.name
# If auth_strategy is noauth, then no need to write token into
# the image file.
if task.context.auth_token:
token_tmpfile_obj = tempfile.NamedTemporaryFile()
token_tmpfile = token_tmpfile_obj.name
utils.write_to_file(token_tmpfile, task.context.auth_token)
files_info[token_tmpfile] = 'token'
try:
images.create_vfat_image(vfat_image_tmpfile, files_info=files_info,
parameters=params)
finally:
if token_tmpfile_obj:
token_tmpfile_obj.close()
container = CONF.ilo.swift_ilo_container
object_name = _get_floppy_image_name(task.node)
timeout = CONF.ilo.swift_object_expiry_timeout
object_headers = {'X-Delete-After': timeout}
swift_api = swift.SwiftAPI()
swift_api.create_object(container, object_name,
vfat_image_tmpfile,
object_headers=object_headers)
temp_url = swift_api.get_temp_url(container, object_name, timeout)
LOG.debug("Uploaded floppy image %(object_name)s to %(container)s "
"for deployment.",
{'object_name': object_name, 'container': container})
return temp_url
def attach_vmedia(node, device, url):
"""Attaches the given url as virtual media on the node.
:param node: an ironic node object.
:param device: the virtual media device to attach
:param url: the http/https url to attach as the virtual media device
:raises: IloOperationError if insert virtual media failed.
"""
ilo_object = get_ilo_object(node)
try:
ilo_object.insert_virtual_media(url, device=device)
ilo_object.set_vm_status(device=device, boot_option='CONNECT',
write_protect='YES')
except ilo_client.IloError as ilo_exception:
operation = _("Inserting virtual media %s") % device
raise exception.IloOperationError(operation=operation,
error=ilo_exception)
LOG.info(_LI("Attached virtual media %s successfully."), device)
# TODO(rameshg87): This needs to be moved to iLO's management interface.
def set_boot_device(node, device):
"""Sets the node to boot from a device for the next boot.
:param node: an ironic node object.
:param device: the device to boot from
:raises: IloOperationError if setting boot device failed.
"""
ilo_object = get_ilo_object(node)
try:
ilo_object.set_one_time_boot(device)
except ilo_client.IloError as ilo_exception:
operation = _("Setting %s as boot device") % device
raise exception.IloOperationError(operation=operation,
error=ilo_exception)
LOG.debug(_LI("Node %(uuid)s set to boot from %(device)s."),
{'uuid': node.uuid, 'device': device})
def setup_vmedia_for_boot(task, boot_iso, parameters=None):
"""Sets up the node to boot from the given ISO image.
This method attaches the given boot_iso on the node and passes
the required parameters to it via virtual floppy image.
:param task: a TaskManager instance containing the node to act on.
:param boot_iso: a bootable ISO image to attach to. The boot iso
should be present in either Glance or in Swift. If present in
Glance, it should be of format 'glance:<glance-image-uuid>'.
If present in Swift, it should be of format 'swift:<object-name>'.
It is assumed that object is present in CONF.ilo.swift_ilo_container.
:param parameters: the parameters to pass in the virtual floppy image
in a dictionary. This is optional.
:raises: ImageCreationFailed, if it failed while creating the floppy image.
:raises: IloOperationError, if attaching virtual media failed.
"""
LOG.info("Setting up node %s to boot from virtual media", task.node.uuid)
if parameters:
floppy_image_temp_url = _prepare_floppy_image(task, parameters)
attach_vmedia(task.node, 'FLOPPY', floppy_image_temp_url)
boot_iso_temp_url = None
scheme, boot_iso_ref = boot_iso.split(':')
if scheme == 'swift':
swift_api = swift.SwiftAPI()
container = CONF.ilo.swift_ilo_container
object_name = boot_iso_ref
timeout = CONF.ilo.swift_object_expiry_timeout
boot_iso_temp_url = swift_api.get_temp_url(container, object_name,
timeout)
elif scheme == 'glance':
glance_uuid = boot_iso_ref
boot_iso_temp_url = images.get_temp_url_for_glance_image(task.context,
glance_uuid)
attach_vmedia(task.node, 'CDROM', boot_iso_temp_url)
def cleanup_vmedia_boot(task):
"""Cleans a node after a virtual media boot.
This method cleans up a node after a virtual media boot. It deletes the
floppy image if it exists in CONF.ilo.swift_ilo_container. It also
ejects both virtual media cdrom and virtual media floppy.
:param task: a TaskManager instance containing the node to act on.
"""
LOG.debug("Cleaning up node %s after virtual media boot", task.node.uuid)
container = CONF.ilo.swift_ilo_container
object_name = _get_floppy_image_name(task.node)
try:
swift_api = swift.SwiftAPI()
swift_api.delete_object(container, object_name)
except exception.SwiftOperationError as e:
LOG.exception(_LE("Error while deleting %(object_name)s from "
"%(container)s. Error: %(error)s"),
{'object_name': object_name, 'container': container,
'error': e})
ilo_object = get_ilo_object(task.node)
for device in ('FLOPPY', 'CDROM'):
try:
ilo_object.eject_virtual_media(device)
except ilo_client.IloError as ilo_exception:
LOG.exception(_LE("Error while ejecting virtual media %(device)s "
"from node %(uuid)s. Error: %(error)s"),
{'device': device, 'uuid': task.node.uuid,
'error': ilo_exception})

View File

@ -0,0 +1,393 @@
# Copyright 2014 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.
"""
iLO Deploy Driver(s) and supporting methods.
"""
import tempfile
from oslo.config import cfg
from ironic.common import exception
from ironic.common import i18n
from ironic.common.i18n import _
from ironic.common import images
from ironic.common import states
from ironic.common import swift
from ironic.conductor import task_manager
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
from ironic.drivers.modules import iscsi_deploy
from ironic.openstack.common import log as logging
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
CONF = cfg.CONF
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',
group='ilo')
def _get_boot_iso_object_name(node):
"""Returns the floppy image name for a given node.
:param node: the node for which image 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 two options in the order as below:
1. Image deployed has a meta-property 'boot_iso' in Glance. This should
refer to the UUID of the boot_iso which exists in Glance.
2. 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: the information about the boot ISO. Returns the information in
the format 'glance:<glance-boot-iso-uuid>' or
'swift:<swift-boot_iso-object-name>'. In case of Swift, it is assumed
that the object exists in CONF.ilo.swift_ilo_container.
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.
"""
# Option 1 - Check if user has provided a boot_iso in Glance.
LOG.debug("Trying to get a boot ISO to boot the baremetal node")
deploy_info = _parse_deploy_info(task.node)
image_uuid = deploy_info['image_source']
boot_iso_uuid = images.get_glance_image_property(task.context,
image_uuid, 'boot_iso')
if boot_iso_uuid:
LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid)
return 'glance:%s' % boot_iso_uuid
kernel_uuid = images.get_glance_image_property(task.context,
image_uuid, 'kernel_id')
ramdisk_uuid = images.get_glance_image_property(task.context,
image_uuid, 'ramdisk_id')
if not kernel_uuid or not ramdisk_uuid:
LOG.error(_LE("Unable to find 'kernel_id' and 'ramdisk_id' in Glance "
"image %(image)s for generating boot ISO for %(node)s"),
{'image': image_uuid, '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 2 - Create boot_iso from kernel/ramdisk, upload to Swift
# and provide its name.
boot_iso_object_name = _get_boot_iso_object_name(task.node)
kernel_params = CONF.pxe.pxe_append_params
container = CONF.ilo.swift_ilo_container
with tempfile.NamedTemporaryFile() as fileobj:
boot_iso_tmp_file = fileobj.name
images.create_boot_iso(task.context, boot_iso_tmp_file,
kernel_uuid, ramdisk_uuid, root_uuid, kernel_params)
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 created in Swift for the instance.
:param node: an ironic node object.
"""
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)s."
"Error: %(error)s."),
{'node': node.uuid, 'error': e})
def _get_single_nic_with_vif_port_id(task):
"""Returns the MAC address of a port which has a VIF port id.
:param task: a TaskManager instance containing the ports to act on.
:returns: MAC address of the port connected to deployment network.
None if it cannot find any port with vif id.
"""
for port in task.ports:
if port.extra.get('vif_port_id'):
return port.address
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")
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 to attach to. The boot iso
should be present in either Glance or in Swift. If present in
Glance, it should be of format 'glance:<glance-image-uuid>'.
If present in Swift, it should be of format 'swift:<object-name>'.
It is assumed that object is present in CONF.ilo.swift_ilo_container.
: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)
ilo_common.set_boot_device(task.node, 'CDROM')
manager_utils.node_power_action(task, states.REBOOT)
class IloVirtualMediaIscsiDeploy(base.DeployInterface):
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.
"""
iscsi_deploy.validate(task)
props = ['kernel_id', 'ramdisk_id']
d_info = _parse_deploy_info(task.node)
iscsi_deploy.validate_glance_image_properties(task.context, d_info,
props)
@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.
"""
manager_utils.node_power_action(task, states.POWER_OFF)
iscsi_deploy.cache_instance_image(task.context, task.node)
iscsi_deploy.check_image_size(task)
deploy_ramdisk_opts = iscsi_deploy.build_deploy_ramdisk_options(
task.node, task.context)
deploy_nic_mac = _get_single_nic_with_vif_port_id(task)
deploy_ramdisk_opts['BOOTIF'] = deploy_nic_mac
deploy_iso_uuid = task.node.driver_info['ilo_deploy_iso']
deploy_iso = 'glance:' + deploy_iso_uuid
_reboot_into(task, deploy_iso, deploy_ramdisk_opts)
return states.DEPLOYWAIT
@task_manager.require_exclusive_lock
def tear_down(self, task):
"""Tear down a previous deployment on the task's node.
Power off the node. All actual clean-up is done in the clean_up()
method which should be called separately.
:param task: a TaskManager instance containing the node to act on.
:returns: deploy state DELETED.
"""
manager_utils.node_power_action(task, states.POWER_OFF)
return states.DELETED
def prepare(self, task):
"""Prepare the deployment environment for this task's node.
:param task: a TaskManager instance containing the node to act on.
"""
pass
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)
iscsi_deploy.destroy_images(task.node.uuid)
def take_over(self, task):
pass
class VendorPassthru(base.VendorInterface):
"""Vendor-specific interfaces for iLO deploy drivers."""
def get_properties(self):
return COMMON_PROPERTIES
def validate(self, task, **kwargs):
"""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 kwargs: kwargs containing the vendor passthru method and its
parameters.
:raises: MissingParameterValue, if some required parameters were not
passed.
:raises: InvalidParameterValue, if any of the parameters have invalid
value.
"""
method = kwargs['method']
if method == 'pass_deploy_info':
iscsi_deploy.get_deploy_info(task.node, **kwargs)
else:
raise exception.InvalidParameterValue(_(
"Unsupported method (%s) passed to iLO driver.")
% method)
@task_manager.require_exclusive_lock
def _continue_deploy(self, task, **kwargs):
"""Continues the iSCSI deployment from where ramdisk left off.
Continues the iSCSI deployment from the conductor node, finds the
boot ISO to boot the node, and sets the node to boot from boot ISO.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs containing parameters for iSCSI deployment.
"""
node = task.node
if node.provision_state != states.DEPLOYWAIT:
LOG.error(_LE('Node %s is not waiting to be deployed.'), node.uuid)
return
ilo_common.cleanup_vmedia_boot(task)
root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)
if not root_uuid:
return
try:
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
ilo_common.setup_vmedia_for_boot(task, boot_iso)
ilo_common.set_boot_device(node, 'CDROM')
address = kwargs.get('address')
deploy_utils.notify_deploy_complete(address)
node.provision_state = states.ACTIVE
node.target_provision_state = states.NOSTATE
i_info = node.instance_info
i_info['ilo_boot_iso'] = boot_iso
node.instance_info = i_info
node.save(task.context)
LOG.info(_LI('Deployment to node %s done'), node.uuid)
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.')
iscsi_deploy.set_failed_state(task, msg)
def vendor_passthru(self, task, **kwargs):
"""Calls a valid vendor passthru method.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs containing the vendor passthru method and its
parameters.
"""
method = kwargs['method']
if method == 'pass_deploy_info':
self._continue_deploy(task, **kwargs)

View File

@ -47,6 +47,22 @@ CONF.register_opts(opts, group='ilo')
LOG = logging.getLogger(__name__)
def _attach_boot_iso(task):
"""Attaches boot ISO for a deployed node.
This method checks the instance info of the baremetal node for a
boot iso. It attaches the boot ISO on the baremetal node, and then
sets the node to boot from virtual media cdrom.
:param task: a TaskManager instance containing the node to act on.
"""
i_info = task.node.instance_info
if 'ilo_boot_iso' in i_info:
ilo_common.setup_vmedia_for_boot(task, i_info['ilo_boot_iso'])
ilo_common.set_boot_device(task.node, 'CDROM')
def _get_power_state(node):
"""Returns the current power state of the node.
@ -105,16 +121,17 @@ def _wait_for_state_change(node, target_state):
return state[0]
def _set_power_state(node, target_state):
def _set_power_state(task, target_state):
"""Turns the server power on/off or do a reboot.
:param node: an ironic node object.
:param task: a TaskManager instance containing the node to act on.
:param target_state: target state of the node.
:raises: InvalidParameterValue if an invalid power state was specified.
:raises: IloOperationError on an error from IloClient library.
:raises: PowerStateFailure if the power couldn't be set to target_state.
"""
node = task.node
ilo_object = ilo_common.get_ilo_object(node)
# Trigger the operation based on the target state.
@ -122,8 +139,10 @@ def _set_power_state(node, target_state):
if target_state == states.POWER_OFF:
ilo_object.hold_pwr_btn()
elif target_state == states.POWER_ON:
_attach_boot_iso(task)
ilo_object.set_host_power('ON')
elif target_state == states.REBOOT:
_attach_boot_iso(task)
ilo_object.reset_server()
target_state = states.POWER_ON
else:
@ -189,7 +208,7 @@ class IloPower(base.PowerInterface):
:raises: IloOperationError on an error from IloClient library.
:raises: PowerStateFailure if the power couldn't be set to power_state.
"""
_set_power_state(task.node, power_state)
_set_power_state(task, power_state)
@task_manager.require_exclusive_lock
def reboot(self, task):
@ -204,6 +223,6 @@ class IloPower(base.PowerInterface):
node = task.node
current_pstate = _get_power_state(node)
if current_pstate == states.POWER_ON:
_set_power_state(node, states.REBOOT)
_set_power_state(task, states.REBOOT)
elif current_pstate == states.POWER_OFF:
_set_power_state(node, states.POWER_ON)
_set_power_state(task, states.POWER_ON)

View File

@ -2259,6 +2259,16 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
'client_port', 'client_timeout']
self._check_driver_properties("fake_ilo", expected)
def test_driver_properties_ilo_iscsi(self):
expected = ['ilo_address', 'ilo_username', 'ilo_password',
'client_port', 'client_timeout', 'ilo_deploy_iso',
'ipmi_address', 'ipmi_terminal_port',
'ipmi_password', 'ipmi_priv_level',
'ipmi_username', 'ipmi_bridging', 'ipmi_transit_channel',
'ipmi_transit_address', 'ipmi_target_channel',
'ipmi_target_address', 'ipmi_local_address']
self._check_driver_properties("iscsi_ilo", expected)
def test_driver_properties_fail(self):
mgr_utils.mock_the_extension_manager()
self.driver = driver_factory.get_driver("fake")

View File

@ -16,15 +16,21 @@
"""Test class for common methods used by iLO modules."""
import mock
import tempfile
from oslo.config import cfg
from oslo.utils import importutils
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.db import api as dbapi
from ironic.drivers.modules.ilo import common as ilo_common
from ironic.openstack.common import context
from ironic.tests import base
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
@ -35,16 +41,16 @@ INFO_DICT = db_utils.get_test_ilo_info()
CONF = cfg.CONF
class IloCommonMethodsTestCase(base.TestCase):
class IloValidateParametersTestCase(base.TestCase):
def setUp(self):
super(IloCommonMethodsTestCase, self).setUp()
super(IloValidateParametersTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
def test_parse_driver_info(self):
node = obj_utils.create_test_node(self.context,
driver='ilo',
driver='fake_ilo',
driver_info=INFO_DICT)
info = ilo_common.parse_driver_info(node)
@ -107,16 +113,24 @@ class IloCommonMethodsTestCase(base.TestCase):
self.assertIn('ilo_password', str(e))
self.assertIn('ilo_address', str(e))
class IloCommonMethodsTestCase(base.TestCase):
def setUp(self):
super(IloCommonMethodsTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
mgr_utils.mock_the_extension_manager(driver="fake_ilo")
self.node = obj_utils.create_test_node(self.context,
driver='fake_ilo', driver_info=INFO_DICT)
@mock.patch.object(ilo_common, 'ilo_client')
def test_get_ilo_object(self, ilo_client_mock):
info = INFO_DICT
info['client_timeout'] = 60
info['client_port'] = 443
node = obj_utils.create_test_node(self.context,
driver='ilo',
driver_info=INFO_DICT)
ilo_client_mock.IloClient.return_value = 'ilo_object'
returned_ilo_object = ilo_common.get_ilo_object(node)
returned_ilo_object = ilo_common.get_ilo_object(self.node)
ilo_client_mock.IloClient.assert_called_with(
INFO_DICT['ilo_address'],
INFO_DICT['ilo_username'],
@ -127,30 +141,183 @@ class IloCommonMethodsTestCase(base.TestCase):
@mock.patch.object(ilo_common, 'ilo_client')
def test_get_ilo_license(self, ilo_client_mock):
node = obj_utils.create_test_node(self.context,
driver='ilo',
driver_info=INFO_DICT)
ilo_advanced_license = {'LICENSE_TYPE': 'iLO 3 Advanced'}
ilo_standard_license = {'LICENSE_TYPE': 'iLO 3'}
ilo_mock_object = ilo_client_mock.IloClient.return_value
ilo_mock_object.get_all_licenses.return_value = ilo_advanced_license
license = ilo_common.get_ilo_license(node)
self.assertEqual(license, ilo_common.ADVANCED_LICENSE)
license = ilo_common.get_ilo_license(self.node)
self.assertEqual(ilo_common.ADVANCED_LICENSE, license)
ilo_mock_object.get_all_licenses.return_value = ilo_standard_license
license = ilo_common.get_ilo_license(node)
self.assertEqual(license, ilo_common.STANDARD_LICENSE)
license = ilo_common.get_ilo_license(self.node)
self.assertEqual(ilo_common.STANDARD_LICENSE, license)
@mock.patch.object(ilo_common, 'ilo_client')
def test_get_ilo_license_fail(self, ilo_client_mock):
node = obj_utils.create_test_node(self.context,
driver='ilo',
driver_info=INFO_DICT)
ilo_client_mock.IloError = Exception
ilo_mock_object = ilo_client_mock.IloClient.return_value
ilo_mock_object.get_all_licenses.side_effect = [Exception()]
self.assertRaises(exception.IloOperationError,
ilo_common.get_ilo_license,
node)
self.node)
def test__get_floppy_image_name(self):
image_name_expected = 'image-' + self.node.uuid
image_name_actual = ilo_common._get_floppy_image_name(self.node)
self.assertEqual(image_name_expected, image_name_actual)
@mock.patch.object(swift, 'SwiftAPI')
@mock.patch.object(images, 'create_vfat_image')
@mock.patch.object(utils, 'write_to_file')
@mock.patch.object(tempfile, 'NamedTemporaryFile')
def test__prepare_floppy_image(self, tempfile_mock, write_mock,
fatimage_mock, swift_api_mock):
mock_token_file_obj = mock.MagicMock()
mock_token_file_obj.name = 'token-tmp-file'
mock_image_file_handle = mock.MagicMock(spec=file)
mock_image_file_obj = mock.MagicMock()
mock_image_file_obj.name = 'image-tmp-file'
mock_image_file_handle.__enter__.return_value = mock_image_file_obj
tempfile_mock.side_effect = [mock_image_file_handle,
mock_token_file_obj]
swift_obj_mock = swift_api_mock.return_value
self.config(swift_ilo_container='ilo_cont', group='ilo')
self.config(swift_object_expiry_timeout=1, group='ilo')
deploy_args = {'arg1': 'val1', 'arg2': 'val2'}
swift_obj_mock.get_temp_url.return_value = 'temp-url'
timeout = CONF.ilo.swift_object_expiry_timeout
object_headers = {'X-Delete-After': timeout}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.context.auth_token = 'token'
temp_url = ilo_common._prepare_floppy_image(task, deploy_args)
node_uuid = task.node.uuid
object_name = 'image-' + node_uuid
files_info = {'token-tmp-file': 'token'}
write_mock.assert_called_once_with('token-tmp-file', 'token')
mock_token_file_obj.close.assert_called_once_with()
fatimage_mock.assert_called_once_with('image-tmp-file',
files_info=files_info,
parameters=deploy_args)
swift_obj_mock.create_object.assert_called_once_with('ilo_cont',
object_name, 'image-tmp-file', object_headers=object_headers)
swift_obj_mock.get_temp_url.assert_called_once_with('ilo_cont',
object_name, timeout)
self.assertEqual('temp-url', temp_url)
@mock.patch.object(swift, 'SwiftAPI')
@mock.patch.object(images, 'create_vfat_image')
@mock.patch.object(tempfile, 'NamedTemporaryFile')
def test__prepare_floppy_image_noauth(self, tempfile_mock, fatimage_mock,
swift_api_mock):
mock_token_file_obj = mock.MagicMock()
mock_token_file_obj.name = 'token-tmp-file'
mock_image_file_handle = mock.MagicMock(spec=file)
mock_image_file_obj = mock.MagicMock()
mock_image_file_obj.name = 'image-tmp-file'
mock_image_file_handle.__enter__.return_value = mock_image_file_obj
tempfile_mock.side_effect = [mock_image_file_handle]
self.config(swift_ilo_container='ilo_cont', group='ilo')
self.config(swift_object_expiry_timeout=1, group='ilo')
deploy_args = {'arg1': 'val1', 'arg2': 'val2'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.context.auth_token = None
ilo_common._prepare_floppy_image(task, deploy_args)
files_info = {}
fatimage_mock.assert_called_once_with('image-tmp-file',
files_info=files_info,
parameters=deploy_args)
@mock.patch.object(ilo_common, 'ilo_client')
def test_attach_vmedia(self, ilo_client_mock):
ilo_client_mock.IloError = Exception
ilo_mock_object = ilo_client_mock.IloClient.return_value
insert_media_mock = ilo_mock_object.insert_virtual_media
set_status_mock = ilo_mock_object.set_vm_status
ilo_common.attach_vmedia(self.node, 'FLOPPY', 'url')
insert_media_mock.assert_called_once_with('url', device='FLOPPY')
set_status_mock.assert_called_once_with(device='FLOPPY',
boot_option='CONNECT', write_protect='YES')
set_status_mock.side_effect = Exception()
self.assertRaises(exception.IloOperationError,
ilo_common.attach_vmedia, self.node, 'FLOPPY', 'url')
@mock.patch.object(ilo_common, 'get_ilo_object')
def test_set_boot_device(self, get_ilo_object_mock):
ilo_object_mock = mock.MagicMock()
get_ilo_object_mock.return_value = ilo_object_mock
ilo_common.set_boot_device(self.node, 'CDROM')
get_ilo_object_mock.assert_called_once_with(self.node)
ilo_object_mock.set_one_time_boot.assert_called_once_with('CDROM')
@mock.patch.object(images, 'get_temp_url_for_glance_image')
@mock.patch.object(ilo_common, 'attach_vmedia')
@mock.patch.object(ilo_common, '_prepare_floppy_image')
def test_setup_vmedia_for_boot_with_parameters(self, prepare_image_mock,
attach_vmedia_mock, temp_url_mock):
parameters = {'a': 'b'}
boot_iso = 'glance:image-uuid'
prepare_image_mock.return_value = 'floppy_url'
temp_url_mock.return_value = 'image_url'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_common.setup_vmedia_for_boot(task, boot_iso, parameters)
prepare_image_mock.assert_called_once_with(task, parameters)
attach_vmedia_mock.assert_any_call(task.node, 'FLOPPY',
'floppy_url')
temp_url_mock.assert_called_once_with(task.context, 'image-uuid')
attach_vmedia_mock.assert_any_call(task.node, 'CDROM', 'image_url')
@mock.patch.object(swift, 'SwiftAPI')
@mock.patch.object(ilo_common, 'attach_vmedia')
def test_setup_vmedia_for_boot_with_swift(self, attach_vmedia_mock,
swift_api_mock):
swift_obj_mock = swift_api_mock.return_value
boot_iso = 'swift:object-name'
swift_obj_mock.get_temp_url.return_value = 'image_url'
CONF.keystone_authtoken.auth_uri = 'http://authurl'
CONF.ilo.swift_ilo_container = 'ilo_cont'
CONF.ilo.swift_object_expiry_timeout = 1
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_common.setup_vmedia_for_boot(task, boot_iso)
swift_obj_mock.get_temp_url.assert_called_once_with('ilo_cont',
'object-name', 1)
attach_vmedia_mock.assert_called_once_with(task.node, 'CDROM',
'image_url')
@mock.patch.object(ilo_common, 'get_ilo_object')
@mock.patch.object(swift, 'SwiftAPI')
@mock.patch.object(ilo_common, '_get_floppy_image_name')
def test_cleanup_vmedia_boot(self, get_name_mock, swift_api_mock,
get_ilo_object_mock):
swift_obj_mock = swift_api_mock.return_value
CONF.ilo.swift_ilo_container = 'ilo_cont'
ilo_object_mock = mock.MagicMock()
get_ilo_object_mock.return_value = ilo_object_mock
get_name_mock.return_value = 'image-node-uuid'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_common.cleanup_vmedia_boot(task)
swift_obj_mock.delete_object.assert_called_once_with('ilo_cont',
'image-node-uuid')
ilo_object_mock.eject_virtual_media.assert_any_call('CDROM')
ilo_object_mock.eject_virtual_media.assert_any_call('FLOPPY')

View File

@ -0,0 +1,317 @@
# Copyright 2014 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
import tempfile
from oslo.config import cfg
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.db import api as dbapi
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules.ilo import deploy as ilo_deploy
from ironic.drivers.modules import iscsi_deploy
from ironic.openstack.common import context
from ironic.openstack.common import importutils
from ironic.tests import base
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
ilo_client = importutils.try_import('proliantutils.ilo.ribcl')
INFO_DICT = db_utils.get_test_ilo_info()
CONF = cfg.CONF
class IloDeployPrivateMethodsTestCase(base.TestCase):
def setUp(self):
super(IloDeployPrivateMethodsTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
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_deploy._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(images, 'get_glance_image_property')
@mock.patch.object(ilo_deploy, '_parse_deploy_info')
def test__get_boot_iso_glance_image(self, deploy_info_mock,
image_prop_mock):
deploy_info_mock.return_value = {'image_source': 'image-uuid'}
image_prop_mock.return_value = 'boot-iso-uuid'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
boot_iso_actual = ilo_deploy._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_prop_mock.assert_called_once_with(task.context, 'image-uuid',
'boot_iso')
boot_iso_expected = 'glance:boot-iso-uuid'
self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(tempfile, 'NamedTemporaryFile')
@mock.patch.object(images, 'create_boot_iso')
@mock.patch.object(swift, 'SwiftAPI')
@mock.patch.object(ilo_deploy, '_get_boot_iso_object_name')
@mock.patch.object(images, 'get_glance_image_property')
@mock.patch.object(ilo_deploy, '_parse_deploy_info')
def test__get_boot_iso_create(self, deploy_info_mock, image_prop_mock,
boot_object_name_mock, swift_api_mock,
create_boot_iso_mock, tempfile_mock):
CONF.keystone_authtoken.auth_uri = 'http://authurl'
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()
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'}
image_prop_mock.side_effect = [None, 'kernel-uuid', 'ramdisk-uuid']
boot_object_name_mock.return_value = 'abcdef'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
boot_iso_actual = ilo_deploy._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_prop_mock.assert_any_call(task.context, 'image-uuid',
'boot_iso')
image_prop_mock.assert_any_call(task.context, 'image-uuid',
'kernel_id')
image_prop_mock.assert_any_call(task.context, 'image-uuid',
'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',
'root-uuid', 'kernel-params')
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_deploy, '_get_boot_iso_object_name')
@mock.patch.object(swift, 'SwiftAPI')
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'
ilo_deploy._clean_up_boot_iso_for_instance(self.node)
swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
'boot-object')
def test__get_single_nic_with_vif_port_id(self):
obj_utils.create_test_port(self.context, node_id=self.node.id, id=6,
address='aa:bb:cc', uuid=utils.generate_uuid(),
extra={'vif_port_id': 'test-vif-A'}, driver='iscsi_ilo')
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
address = ilo_deploy._get_single_nic_with_vif_port_id(task)
self.assertEqual('aa:bb:cc', address)
@mock.patch.object(deploy_utils, 'check_for_missing_params')
def test__parse_driver_info(self, check_params_mock):
self.node.driver_info['ilo_deploy_iso'] = 'deploy-iso-uuid'
driver_info_expected = {'ilo_deploy_iso': 'deploy-iso-uuid'}
driver_info_actual = ilo_deploy._parse_driver_info(self.node)
error_msg = 'Error validating iLO virtual media deploy'
check_params_mock.assert_called_once_with(driver_info_expected,
error_msg)
self.assertEqual(driver_info_expected, driver_info_actual)
@mock.patch.object(ilo_deploy, '_parse_driver_info')
@mock.patch.object(iscsi_deploy, 'parse_instance_info')
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_deploy._parse_deploy_info(self.node)
self.assertEqual(expected_info, actual_info)
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(ilo_common, 'set_boot_device')
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot')
def test__reboot_into(self, setup_vmedia_mock, set_boot_device_mock,
node_power_action_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
opts = {'a': 'b'}
ilo_deploy._reboot_into(task, 'iso', opts)
setup_vmedia_mock.assert_called_once_with(task, 'iso', opts)
set_boot_device_mock.assert_called_once_with(task.node, 'CDROM')
node_power_action_mock.assert_called_once_with(task, states.REBOOT)
class IloVirtualMediaIscsiDeployTestCase(base.TestCase):
def setUp(self):
super(IloVirtualMediaIscsiDeployTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
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(iscsi_deploy, 'validate_glance_image_properties')
@mock.patch.object(ilo_deploy, '_parse_deploy_info')
@mock.patch.object(iscsi_deploy, 'validate')
def test_validate(self, validate_mock, deploy_info_mock,
validate_prop_mock):
d_info = {'a': 'b'}
deploy_info_mock.return_value = d_info
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.deploy.validate(task)
validate_mock.assert_called_once_with(task)
deploy_info_mock.assert_called_once_with(task.node)
validate_prop_mock.assert_called_once_with(task.context,
d_info, ['kernel_id', 'ramdisk_id'])
@mock.patch.object(ilo_deploy, '_reboot_into')
@mock.patch.object(ilo_deploy, '_get_single_nic_with_vif_port_id')
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(ilo_common, 'set_boot_device')
@mock.patch.object(iscsi_deploy, 'check_image_size')
@mock.patch.object(iscsi_deploy, 'cache_instance_image')
def test_deploy(self, cache_instance_image_mock, check_image_size_mock,
set_boot_device_mock, node_power_action_mock,
build_opts_mock, get_nic_mock, reboot_into_mock):
deploy_opts = {'a': 'b'}
build_opts_mock.return_value = deploy_opts
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'
returned_state = task.driver.deploy.deploy(task)
node_power_action_mock.assert_any_call(task, states.POWER_OFF)
cache_instance_image_mock.assert_called_once_with(task.context,
task.node)
check_image_size_mock.assert_called_once_with(task)
expected_ramdisk_opts = {'a': 'b', 'BOOTIF': '12:34:56:78:90:ab'}
build_opts_mock.assert_called_once_with(task.node, task.context)
get_nic_mock.assert_called_once_with(task)
reboot_into_mock.assert_called_once_with(task, 'glance:deploy-iso',
expected_ramdisk_opts)
self.assertEqual(states.DEPLOYWAIT, returned_state)
@mock.patch.object(manager_utils, 'node_power_action')
def test_tear_down(self, node_power_action_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
returned_state = task.driver.deploy.tear_down(task)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
self.assertEqual(states.DELETED, returned_state)
@mock.patch.object(ilo_deploy, '_clean_up_boot_iso_for_instance')
@mock.patch.object(iscsi_deploy, 'destroy_images')
def test_clean_up(self, destroy_images_mock, clean_up_boot_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.deploy.clean_up(task)
destroy_images_mock.assert_called_once_with(task.node.uuid)
clean_up_boot_mock.assert_called_once_with(task.node)
class VendorPassthruTestCase(base.TestCase):
def setUp(self):
super(VendorPassthruTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
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, 'notify_deploy_complete')
@mock.patch.object(ilo_common, 'set_boot_device')
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot')
@mock.patch.object(ilo_deploy, '_get_boot_iso')
@mock.patch.object(iscsi_deploy, 'continue_deploy')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
def test__continue_deploy_good(self, cleanup_vmedia_boot_mock,
continue_deploy_mock, get_boot_iso_mock,
setup_vmedia_mock, set_boot_device_mock,
notify_deploy_complete_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
continue_deploy_mock.return_value = 'root-uuid'
get_boot_iso_mock.return_value = 'boot-iso'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.provision_state = states.DEPLOYWAIT
vendor = ilo_deploy.VendorPassthru()
vendor._continue_deploy(task, **kwargs)
cleanup_vmedia_boot_mock.assert_called_once_with(task)
continue_deploy_mock.assert_called_once_with(task, **kwargs)
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.node, 'CDROM')
self.assertEqual('boot-iso',
task.node.instance_info['ilo_boot_iso'])
notify_deploy_complete_mock.assert_called_once_with('123456')
@mock.patch.object(ilo_deploy, 'LOG')
def test__continue_deploy_bad(self, log_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.provision_state = states.NOSTATE
vendor = ilo_deploy.VendorPassthru()
vendor._continue_deploy(task, **kwargs)
self.assertTrue(log_mock.error.called)
@mock.patch.object(iscsi_deploy, 'continue_deploy')
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot')
def test__continue_deploy_deploy_no_boot_media(self,
cleanup_vmedia_boot_mock, continue_deploy_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
continue_deploy_mock.return_value = None
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.provision_state = states.DEPLOYWAIT
vendor = ilo_deploy.VendorPassthru()
vendor._continue_deploy(task, **kwargs)
cleanup_vmedia_boot_mock.assert_called_once_with(task)
continue_deploy_mock.assert_called_once_with(task, **kwargs)

View File

@ -24,6 +24,7 @@ from ironic.common import states
from ironic.conductor import task_manager
from ironic.db import api as dbapi
from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules.ilo import deploy as ilo_deploy
from ironic.drivers.modules.ilo import power as ilo_power
from ironic.openstack.common import context
from ironic.tests import base
@ -83,11 +84,13 @@ class IloPowerInternalMethodsTestCase(base.TestCase):
def test__set_power_state_invalid_state(self, power_ilo_client_mock,
common_ilo_client_mock):
power_ilo_client_mock.IloError = Exception
self.assertRaises(exception.IloOperationError,
ilo_power._set_power_state,
self.node,
states.ERROR)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
power_ilo_client_mock.IloError = Exception
self.assertRaises(exception.IloOperationError,
ilo_power._set_power_state,
task,
states.ERROR)
def test__set_power_state_reboot_fail(self, power_ilo_client_mock,
common_ilo_client_mock):
@ -95,10 +98,12 @@ class IloPowerInternalMethodsTestCase(base.TestCase):
ilo_mock_object = common_ilo_client_mock.IloClient.return_value
ilo_mock_object.reset_server.side_effect = Exception()
self.assertRaises(exception.IloOperationError,
ilo_power._set_power_state,
self.node,
states.REBOOT)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.IloOperationError,
ilo_power._set_power_state,
task,
states.REBOOT)
ilo_mock_object.reset_server.assert_called_once_with()
def test__set_power_state_reboot_ok(self, power_ilo_client_mock,
@ -107,7 +112,9 @@ class IloPowerInternalMethodsTestCase(base.TestCase):
ilo_mock_object = common_ilo_client_mock.IloClient.return_value
ilo_mock_object.get_host_power_status.side_effect = ['ON', 'OFF', 'ON']
ilo_power._set_power_state(self.node, states.REBOOT)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_power._set_power_state(task, states.REBOOT)
ilo_mock_object.reset_server.assert_called_once_with()
@ -117,10 +124,12 @@ class IloPowerInternalMethodsTestCase(base.TestCase):
ilo_mock_object = common_ilo_client_mock.IloClient.return_value
ilo_mock_object.get_host_power_status.return_value = 'ON'
self.assertRaises(exception.PowerStateFailure,
ilo_power._set_power_state,
self.node,
states.POWER_OFF)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.PowerStateFailure,
ilo_power._set_power_state,
task,
states.POWER_OFF)
ilo_mock_object.get_host_power_status.assert_called_with()
ilo_mock_object.hold_pwr_btn.assert_called_once_with()
@ -132,10 +141,23 @@ class IloPowerInternalMethodsTestCase(base.TestCase):
ilo_mock_object.get_host_power_status.side_effect = ['OFF', 'ON']
target_state = states.POWER_ON
ilo_power._set_power_state(self.node, target_state)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
ilo_power._set_power_state(task, target_state)
ilo_mock_object.get_host_power_status.assert_called_with()
ilo_mock_object.set_host_power.assert_called_once_with('ON')
@mock.patch.object(ilo_common, 'set_boot_device')
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot')
def test__attach_boot_iso(self, setup_vmedia_mock, set_boot_device_mock,
power_ilo_client_mock, common_ilo_client_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.instance_info['ilo_boot_iso'] = 'boot-iso'
ilo_power._attach_boot_iso(task)
setup_vmedia_mock.assert_called_once_with(task, 'boot-iso')
set_boot_device_mock.assert_called_once_with(task.node, 'CDROM')
class IloPowerTestCase(base.TestCase):
@ -151,6 +173,7 @@ class IloPowerTestCase(base.TestCase):
def test_get_properties(self):
expected = ilo_common.COMMON_PROPERTIES
expected.update(ilo_deploy.COMMON_PROPERTIES)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertEqual(expected, task.driver.get_properties())
@ -183,11 +206,11 @@ class IloPowerTestCase(base.TestCase):
@mock.patch.object(ilo_power, '_set_power_state')
def test_set_power_state(self, mock_set_power):
mock_set_power.return_value = states.POWER_ON
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
mock_set_power.return_value = states.POWER_ON
task.driver.power.set_power_state(task, states.POWER_ON)
mock_set_power.assert_called_once_with(task.node, states.POWER_ON)
mock_set_power.assert_called_once_with(task, states.POWER_ON)
@mock.patch.object(ilo_power, '_set_power_state')
@mock.patch.object(ilo_power, '_get_power_state')
@ -198,4 +221,4 @@ class IloPowerTestCase(base.TestCase):
mock_set_power.return_value = states.POWER_ON
task.driver.power.reboot(task)
mock_get_power.assert_called_once_with(task.node)
mock_set_power.assert_called_once_with(task.node, states.REBOOT)
mock_set_power.assert_called_once_with(task, states.REBOOT)

View File

@ -365,10 +365,6 @@ class FsImageTestCase(base.TestCase):
def test_create_isolinux_image_rootfs_fails(self, utils_mock,
tempdir_mock,
create_root_fs_mock):
mock_file_handle = mock.MagicMock(spec=file)
mock_file_handle.__enter__.return_value = 'tmpdir'
tempdir_mock.return_value = mock_file_handle
create_root_fs_mock.side_effect = IOError
self.assertRaises(exception.ImageCreationFailed,
@ -396,3 +392,54 @@ class FsImageTestCase(base.TestCase):
images.create_isolinux_image,
'tgt_file', 'path/to/kernel',
'path/to/ramdisk')
@mock.patch.object(images, 'create_isolinux_image')
@mock.patch.object(images, 'fetch_to_raw')
@mock.patch.object(utils, 'tempdir')
def test_create_boot_iso(self, tempdir_mock, fetch_images_mock,
create_isolinux_mock):
mock_file_handle = mock.MagicMock(spec=file)
mock_file_handle.__enter__.return_value = 'tmpdir'
tempdir_mock.return_value = mock_file_handle
images.create_boot_iso('ctx', 'output_file', 'kernel-uuid',
'ramdisk-uuid', 'root-uuid', 'kernel-params')
fetch_images_mock.assert_any_call('ctx', 'kernel-uuid',
'tmpdir/kernel-uuid')
fetch_images_mock.assert_any_call('ctx', 'ramdisk-uuid',
'tmpdir/ramdisk-uuid')
params = ['root=UUID=root-uuid', 'kernel-params']
create_isolinux_mock.assert_called_once_with('output_file',
'tmpdir/kernel-uuid', 'tmpdir/ramdisk-uuid', params)
@mock.patch.object(image_service, 'Service')
def test_get_glance_image_property(self, image_service_mock):
prop_dict = {'properties': {'prop1': 'val1'}}
image_service_obj_mock = image_service_mock.return_value
image_service_obj_mock.show.return_value = prop_dict
ret_val = images.get_glance_image_property('con', 'uuid', 'prop1')
image_service_mock.assert_called_once_with(version=1, context='con')
image_service_obj_mock.show.assert_called_once_with('uuid')
self.assertEqual('val1', ret_val)
ret_val = images.get_glance_image_property('con', 'uuid', 'prop2')
self.assertIsNone(ret_val)
@mock.patch.object(image_service, 'Service')
def test_get_temp_url_for_glance_image(self, image_service_mock):
direct_url = 'swift+http://host/v1/AUTH_xx/con/obj'
image_info = {'id': 'qwe', 'properties': {'direct_url': direct_url}}
glance_service_mock = image_service_mock.return_value
glance_service_mock.swift_temp_url.return_value = 'temp-url'
glance_service_mock.show.return_value = image_info
temp_url = images.get_temp_url_for_glance_image('context',
'glance_uuid')
glance_service_mock.show.assert_called_once_with('glance_uuid')
self.assertEqual('temp-url', temp_url)

View File

@ -17,6 +17,7 @@ import errno
import hashlib
import os
import os.path
import shutil
import tempfile
import uuid
@ -415,3 +416,46 @@ class UUIDTestCase(base.TestCase):
def test_name_is_uuid_like(self):
self.assertFalse(utils.is_uuid_like('zhongyueluo'))
class TempFilesTestCase(base.TestCase):
def test_tempdir(self):
dirname = None
with utils.tempdir() as tempdir:
self.assertTrue(os.path.isdir(tempdir))
dirname = tempdir
self.assertFalse(os.path.exists(dirname))
@mock.patch.object(shutil, 'rmtree')
@mock.patch.object(tempfile, 'mkdtemp')
def test_tempdir_mocked(self, mkdtemp_mock, rmtree_mock):
self.config(tempdir='abc')
mkdtemp_mock.return_value = 'temp-dir'
kwargs = {'a': 'b'}
with utils.tempdir(**kwargs) as tempdir:
self.assertEqual('temp-dir', tempdir)
tempdir_created = tempdir
mkdtemp_mock.assert_called_once_with(**kwargs)
rmtree_mock.assert_called_once_with(tempdir_created)
@mock.patch.object(utils, 'LOG')
@mock.patch.object(shutil, 'rmtree')
@mock.patch.object(tempfile, 'mkdtemp')
def test_tempdir_mocked_error_on_rmtree(self, mkdtemp_mock, rmtree_mock,
log_mock):
self.config(tempdir='abc')
mkdtemp_mock.return_value = 'temp-dir'
rmtree_mock.side_effect = OSError
with utils.tempdir() as tempdir:
self.assertEqual('temp-dir', tempdir)
tempdir_created = tempdir
rmtree_mock.assert_called_once_with(tempdir_created)
self.assertTrue(log_mock.error.called)

View File

@ -49,6 +49,7 @@ ironic.drivers =
fake_ilo = ironic.drivers.fake:FakeIloDriver
fake_drac = ironic.drivers.fake:FakeDracDriver
fake_snmp = ironic.drivers.fake:FakeSNMPDriver
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver
pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver