Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rameshg87 authored and jimrollenhagen committed Sep 3, 2014
1 parent 1773bcd commit 571579a
Show file tree
Hide file tree
Showing 14 changed files with 1,380 additions and 51 deletions.
7 changes: 7 additions & 0 deletions etc/ironic/ironic.conf.sample
Expand Up @@ -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
Expand Down
62 changes: 62 additions & 0 deletions ironic/common/images.py
Expand Up @@ -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)
8 changes: 4 additions & 4 deletions ironic/common/keystone.py
Expand Up @@ -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
Expand All @@ -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)
Expand Down
48 changes: 48 additions & 0 deletions ironic/drivers/ilo.py
@@ -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()
195 changes: 193 additions & 2 deletions ironic/drivers/modules/ilo/common.py
Expand Up @@ -16,35 +16,49 @@
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,
help='Timeout (in seconds) for iLO operations'),
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
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. "
Expand Down Expand Up @@ -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})

0 comments on commit 571579a

Please sign in to comment.