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:
parent
1773bcd834
commit
571579a005
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
48
ironic/drivers/ilo.py
Normal 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()
|
@ -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})
|
||||
|
393
ironic/drivers/modules/ilo/deploy.py
Normal file
393
ironic/drivers/modules/ilo/deploy.py
Normal 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)
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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')
|
||||
|
317
ironic/tests/drivers/ilo/test_deploy.py
Normal file
317
ironic/tests/drivers/ilo/test_deploy.py
Normal 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)
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user