703 lines
27 KiB
Python
703 lines
27 KiB
Python
# coding=utf-8
|
|
|
|
# Copyright 2013 International Business Machines Corporation
|
|
# 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.
|
|
|
|
"""
|
|
Ironic Native IPMI power manager.
|
|
"""
|
|
|
|
import os
|
|
|
|
from ironic_lib import metrics_utils
|
|
from ironic_lib import utils as ironic_utils
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import importutils
|
|
from oslo_utils import strutils
|
|
|
|
from ironic.common import boot_devices
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _, _LE, _LW
|
|
from ironic.common import states
|
|
from ironic.common import utils
|
|
from ironic.conductor import task_manager
|
|
from ironic.conf import CONF
|
|
from ironic.drivers import base
|
|
from ironic.drivers.modules import console_utils
|
|
from ironic.drivers.modules import deploy_utils
|
|
from ironic.drivers import utils as driver_utils
|
|
|
|
pyghmi = importutils.try_import('pyghmi')
|
|
if pyghmi:
|
|
from pyghmi import exceptions as pyghmi_exception
|
|
from pyghmi.ipmi import command as ipmi_command
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
METRICS = metrics_utils.get_metrics_logger(__name__)
|
|
|
|
REQUIRED_PROPERTIES = {'ipmi_address': _("IP of the node's BMC. Required."),
|
|
'ipmi_password': _("IPMI password. Required."),
|
|
'ipmi_username': _("IPMI username. Required.")}
|
|
OPTIONAL_PROPERTIES = {
|
|
'ipmi_force_boot_device': _("Whether Ironic should specify the boot "
|
|
"device to the BMC each time the server "
|
|
"is turned on, eg. because the BMC is not "
|
|
"capable of remembering the selected boot "
|
|
"device across power cycles; default value "
|
|
"is False. Optional.")
|
|
}
|
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
|
CONSOLE_PROPERTIES = {
|
|
'ipmi_terminal_port': _("node's UDP port to connect to. Only required for "
|
|
"console access.")
|
|
}
|
|
|
|
_BOOT_DEVICES_MAP = {
|
|
boot_devices.DISK: 'hd',
|
|
boot_devices.PXE: 'network',
|
|
boot_devices.CDROM: 'cdrom',
|
|
boot_devices.BIOS: 'setup',
|
|
}
|
|
|
|
|
|
def _parse_driver_info(node):
|
|
"""Gets the bmc access info for the given node.
|
|
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
:raises: InvalidParameterValue when the IPMI terminal port is not an
|
|
integer.
|
|
"""
|
|
|
|
info = node.driver_info or {}
|
|
missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)]
|
|
if missing_info:
|
|
raise exception.MissingParameterValue(_(
|
|
"Missing the following IPMI credentials in node's"
|
|
" driver_info: %s.") % missing_info)
|
|
|
|
bmc_info = {}
|
|
bmc_info['address'] = info.get('ipmi_address')
|
|
bmc_info['username'] = info.get('ipmi_username')
|
|
bmc_info['password'] = info.get('ipmi_password')
|
|
bmc_info['force_boot_device'] = info.get('ipmi_force_boot_device', False)
|
|
|
|
# get additional info
|
|
bmc_info['uuid'] = node.uuid
|
|
|
|
# terminal port must be an integer
|
|
port = info.get('ipmi_terminal_port')
|
|
if port is not None:
|
|
port = utils.validate_network_port(port, 'ipmi_terminal_port')
|
|
bmc_info['port'] = port
|
|
|
|
return bmc_info
|
|
|
|
|
|
def _console_pwfile_path(uuid):
|
|
"""Return the file path for storing the ipmi password."""
|
|
file_name = "%(uuid)s.pw" % {'uuid': uuid}
|
|
return os.path.join(CONF.tempdir, file_name)
|
|
|
|
|
|
def _power_on(driver_info):
|
|
"""Turn the power on for this node.
|
|
|
|
:param driver_info: the bmc access info for a node.
|
|
:returns: power state POWER_ON, one of :class:`ironic.common.states`.
|
|
:raises: IPMIFailure when the native ipmi call fails.
|
|
:raises: PowerStateFailure when invalid power state is returned
|
|
from ipmi.
|
|
"""
|
|
|
|
msg = _("IPMI power on failed for node %(node_id)s with the "
|
|
"following error: %(error)s")
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
wait = CONF.ipmi.retry_timeout
|
|
ret = ipmicmd.set_power('on', wait)
|
|
except pyghmi_exception.IpmiException as e:
|
|
error = msg % {'node_id': driver_info['uuid'], 'error': e}
|
|
LOG.error(error)
|
|
raise exception.IPMIFailure(error)
|
|
|
|
state = ret.get('powerstate')
|
|
if state == 'on':
|
|
return states.POWER_ON
|
|
else:
|
|
error = _("bad response: %s") % ret
|
|
LOG.error(msg, {'node_id': driver_info['uuid'], 'error': error})
|
|
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
|
|
|
|
|
def _power_off(driver_info):
|
|
"""Turn the power off for this node.
|
|
|
|
:param driver_info: the bmc access info for a node.
|
|
:returns: power state POWER_OFF, one of :class:`ironic.common.states`.
|
|
:raises: IPMIFailure when the native ipmi call fails.
|
|
:raises: PowerStateFailure when invalid power state is returned
|
|
from ipmi.
|
|
"""
|
|
|
|
msg = _("IPMI power off failed for node %(node_id)s with the "
|
|
"following error: %(error)s")
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
wait = CONF.ipmi.retry_timeout
|
|
ret = ipmicmd.set_power('off', wait)
|
|
except pyghmi_exception.IpmiException as e:
|
|
error = msg % {'node_id': driver_info['uuid'], 'error': e}
|
|
LOG.error(error)
|
|
raise exception.IPMIFailure(error)
|
|
|
|
state = ret.get('powerstate')
|
|
if state == 'off':
|
|
return states.POWER_OFF
|
|
else:
|
|
error = _("bad response: %s") % ret
|
|
LOG.error(msg, {'node_id': driver_info['uuid'], 'error': error})
|
|
raise exception.PowerStateFailure(pstate=states.POWER_OFF)
|
|
|
|
|
|
def _reboot(driver_info):
|
|
"""Reboot this node.
|
|
|
|
If the power is off, turn it on. If the power is on, reset it.
|
|
|
|
:param driver_info: the bmc access info for a node.
|
|
:returns: power state POWER_ON, one of :class:`ironic.common.states`.
|
|
:raises: IPMIFailure when the native ipmi call fails.
|
|
:raises: PowerStateFailure when invalid power state is returned
|
|
from ipmi.
|
|
"""
|
|
|
|
msg = _("IPMI power reboot failed for node %(node_id)s with the "
|
|
"following error: %(error)s")
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
wait = CONF.ipmi.retry_timeout
|
|
ret = ipmicmd.set_power('boot', wait)
|
|
except pyghmi_exception.IpmiException as e:
|
|
error = msg % {'node_id': driver_info['uuid'], 'error': e}
|
|
LOG.error(error)
|
|
raise exception.IPMIFailure(error)
|
|
|
|
if 'error' in ret:
|
|
error = _("bad response: %s") % ret
|
|
LOG.error(msg, {'node_id': driver_info['uuid'], 'error': error})
|
|
raise exception.PowerStateFailure(pstate=states.REBOOT)
|
|
|
|
return states.POWER_ON
|
|
|
|
|
|
def _power_status(driver_info):
|
|
"""Get the power status for this node.
|
|
|
|
:param driver_info: the bmc access info for a node.
|
|
:returns: power state POWER_ON, POWER_OFF or ERROR defined in
|
|
:class:`ironic.common.states`.
|
|
:raises: IPMIFailure when the native ipmi call fails.
|
|
"""
|
|
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
ret = ipmicmd.get_power()
|
|
except pyghmi_exception.IpmiException as e:
|
|
msg = (_("IPMI get power state failed for node %(node_id)s "
|
|
"with the following error: %(error)s") %
|
|
{'node_id': driver_info['uuid'], 'error': e})
|
|
LOG.error(msg)
|
|
raise exception.IPMIFailure(msg)
|
|
|
|
state = ret.get('powerstate')
|
|
if state == 'on':
|
|
return states.POWER_ON
|
|
elif state == 'off':
|
|
return states.POWER_OFF
|
|
else:
|
|
# NOTE(linggao): Do not throw an exception here because it might
|
|
# return other valid values. It is up to the caller to decide
|
|
# what to do.
|
|
LOG.warning(_LW("IPMI get power state for node %(node_id)s returns the"
|
|
" following details: %(detail)s"),
|
|
{'node_id': driver_info['uuid'], 'detail': ret})
|
|
return states.ERROR
|
|
|
|
|
|
def _get_sensors_data(driver_info):
|
|
"""Get sensors data.
|
|
|
|
:param driver_info: node's driver info
|
|
:raises: FailedToGetSensorData when getting the sensor data fails.
|
|
:returns: returns a dict of sensor data group by sensor type.
|
|
"""
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
ret = ipmicmd.get_sensor_data()
|
|
except Exception as e:
|
|
LOG.error(_LE("IPMI get sensor data failed for node %(node_id)s "
|
|
"with the following error: %(error)s"),
|
|
{'node_id': driver_info['uuid'], 'error': e})
|
|
raise exception.FailedToGetSensorData(
|
|
node=driver_info['uuid'], error=e)
|
|
|
|
if not ret:
|
|
return {}
|
|
|
|
sensors_data = {}
|
|
for reading in ret:
|
|
# ignore the sensor data which has no sensor reading value
|
|
if not reading.value:
|
|
continue
|
|
sensors_data.setdefault(
|
|
reading.type,
|
|
{})[reading.name] = {
|
|
'Sensor Reading': '%s %s' % (reading.value, reading.units),
|
|
'Sensor ID': reading.name,
|
|
'States': str(reading.states),
|
|
'Units': reading.units,
|
|
'Health': str(reading.health)}
|
|
|
|
return sensors_data
|
|
|
|
|
|
def _parse_raw_bytes(raw_bytes):
|
|
"""Parse raw bytes string.
|
|
|
|
:param raw_bytes: a string of hexadecimal raw bytes, e.g. '0x00 0x01'.
|
|
:returns: a tuple containing the arguments for pyghmi call as integers,
|
|
(IPMI net function, IPMI command, list of command's data).
|
|
:raises: InvalidParameterValue when an invalid value is specified.
|
|
"""
|
|
try:
|
|
bytes_list = [int(x, base=16) for x in raw_bytes.split()]
|
|
return bytes_list[0], bytes_list[1], bytes_list[2:]
|
|
except ValueError:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Invalid raw bytes string: '%s'") % raw_bytes)
|
|
except IndexError:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Raw bytes string requires two bytes at least."))
|
|
|
|
|
|
def _send_raw(driver_info, raw_bytes):
|
|
"""Send raw bytes to the BMC."""
|
|
netfn, command, data = _parse_raw_bytes(raw_bytes)
|
|
LOG.debug("Sending raw bytes %(bytes)s to node %(node_id)s",
|
|
{'bytes': raw_bytes, 'node_id': driver_info['uuid']})
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
ipmicmd.xraw_command(netfn, command, data=data)
|
|
except pyghmi_exception.IpmiException as e:
|
|
msg = (_("IPMI send raw bytes '%(bytes)s' failed for node %(node_id)s"
|
|
" with the following error: %(error)s") %
|
|
{'bytes': raw_bytes, 'node_id': driver_info['uuid'],
|
|
'error': e})
|
|
LOG.error(msg)
|
|
raise exception.IPMIFailure(msg)
|
|
|
|
|
|
class NativeIPMIPower(base.PowerInterface):
|
|
"""The power driver using native python-ipmi library."""
|
|
|
|
def get_properties(self):
|
|
return COMMON_PROPERTIES
|
|
|
|
@METRICS.timer('NativeIPMIPower.validate')
|
|
def validate(self, task):
|
|
"""Check that node['driver_info'] contains IPMI credentials.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
"""
|
|
_parse_driver_info(task.node)
|
|
|
|
@METRICS.timer('NativeIPMIPower.get_power_state')
|
|
def get_power_state(self, task):
|
|
"""Get the current power state of the task's node.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:returns: power state POWER_ON, POWER_OFF or ERROR defined in
|
|
:class:`ironic.common.states`.
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
:raises: IPMIFailure when the native ipmi call fails.
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
return _power_status(driver_info)
|
|
|
|
@METRICS.timer('NativeIPMIPower.set_power_state')
|
|
@task_manager.require_exclusive_lock
|
|
def set_power_state(self, task, pstate):
|
|
"""Turn the power on or off.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:param pstate: a power state that will be set on the task's node.
|
|
:raises: IPMIFailure when the native ipmi call fails.
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
:raises: InvalidParameterValue when an invalid power state
|
|
is specified
|
|
:raises: PowerStateFailure when invalid power state is returned
|
|
from ipmi.
|
|
"""
|
|
|
|
driver_info = _parse_driver_info(task.node)
|
|
|
|
if pstate == states.POWER_ON:
|
|
driver_utils.ensure_next_boot_device(task, driver_info)
|
|
_power_on(driver_info)
|
|
elif pstate == states.POWER_OFF:
|
|
_power_off(driver_info)
|
|
else:
|
|
raise exception.InvalidParameterValue(
|
|
_("set_power_state called with an invalid power state: %s."
|
|
) % pstate)
|
|
|
|
@METRICS.timer('NativeIPMIPower.reboot')
|
|
@task_manager.require_exclusive_lock
|
|
def reboot(self, task):
|
|
"""Cycles the power to the task's node.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:raises: IPMIFailure when the native ipmi call fails.
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
:raises: PowerStateFailure when invalid power state is returned
|
|
from ipmi.
|
|
"""
|
|
|
|
driver_info = _parse_driver_info(task.node)
|
|
driver_utils.ensure_next_boot_device(task, driver_info)
|
|
_reboot(driver_info)
|
|
|
|
|
|
class NativeIPMIManagement(base.ManagementInterface):
|
|
|
|
def get_properties(self):
|
|
return COMMON_PROPERTIES
|
|
|
|
@METRICS.timer('NativeIPMIManagement.validate')
|
|
def validate(self, task):
|
|
"""Check that 'driver_info' contains IPMI credentials.
|
|
|
|
Validates whether the 'driver_info' property of the supplied
|
|
task's node contains the required credentials information.
|
|
|
|
:param task: a task from TaskManager.
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
|
|
"""
|
|
_parse_driver_info(task.node)
|
|
|
|
def get_supported_boot_devices(self, task):
|
|
"""Get a list of the supported boot devices.
|
|
|
|
:param task: a task from TaskManager.
|
|
:returns: A list with the supported boot devices defined
|
|
in :mod:`ironic.common.boot_devices`.
|
|
|
|
"""
|
|
return list(_BOOT_DEVICES_MAP.keys())
|
|
|
|
@METRICS.timer('NativeIPMIManagement.set_boot_device')
|
|
@task_manager.require_exclusive_lock
|
|
def set_boot_device(self, task, device, persistent=False):
|
|
"""Set the boot device for the task's node.
|
|
|
|
Set the boot device to use on next reboot of the node.
|
|
|
|
:param task: a task from TaskManager.
|
|
:param device: the boot device, one of
|
|
:mod:`ironic.common.boot_devices`.
|
|
:param persistent: Boolean value. True if the boot device will
|
|
persist to all future boots, False if not.
|
|
Default: False.
|
|
:raises: InvalidParameterValue if an invalid boot device is specified
|
|
or required ipmi credentials are missing.
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
:raises: IPMIFailure on an error from pyghmi.
|
|
"""
|
|
if device not in self.get_supported_boot_devices(task):
|
|
raise exception.InvalidParameterValue(_(
|
|
"Invalid boot device %s specified.") % device)
|
|
|
|
if task.node.driver_info.get('ipmi_force_boot_device', False):
|
|
driver_utils.force_persistent_boot(task,
|
|
device,
|
|
persistent)
|
|
# Reset persistent to False, in case of BMC does not support
|
|
# persistent or we do not have admin rights.
|
|
persistent = False
|
|
|
|
boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node)
|
|
driver_info = _parse_driver_info(task.node)
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
bootdev = _BOOT_DEVICES_MAP[device]
|
|
uefiboot = boot_mode == 'uefi'
|
|
ipmicmd.set_bootdev(bootdev, persist=persistent, uefiboot=uefiboot)
|
|
except pyghmi_exception.IpmiException as e:
|
|
LOG.error(_LE("IPMI set boot device failed for node %(node_id)s "
|
|
"with the following error: %(error)s"),
|
|
{'node_id': driver_info['uuid'], 'error': e})
|
|
raise exception.IPMIFailure(cmd=e)
|
|
|
|
@METRICS.timer('NativeIPMIManagement.get_boot_device')
|
|
def get_boot_device(self, task):
|
|
"""Get the current boot device for the task's node.
|
|
|
|
Returns the current boot device of the node.
|
|
|
|
:param task: a task from TaskManager.
|
|
:raises: MissingParameterValue if required IPMI parameters
|
|
are missing.
|
|
:raises: IPMIFailure on an error from pyghmi.
|
|
:returns: a dictionary containing:
|
|
|
|
:boot_device: the boot device, one of
|
|
:mod:`ironic.common.boot_devices` or None if it is unknown.
|
|
:persistent: Whether the boot device will persist to all
|
|
future boots or not, None if it is unknown.
|
|
|
|
"""
|
|
driver_info = task.node.driver_info
|
|
driver_internal_info = task.node.driver_internal_info
|
|
if (driver_info.get('ipmi_force_boot_device', False) and
|
|
driver_internal_info.get('persistent_boot_device') and
|
|
driver_internal_info.get('is_next_boot_persistent', True)):
|
|
return {
|
|
'boot_device': driver_internal_info['persistent_boot_device'],
|
|
'persistent': True
|
|
}
|
|
|
|
driver_info = _parse_driver_info(task.node)
|
|
response = {'boot_device': None}
|
|
|
|
try:
|
|
ipmicmd = ipmi_command.Command(bmc=driver_info['address'],
|
|
userid=driver_info['username'],
|
|
password=driver_info['password'])
|
|
ret = ipmicmd.get_bootdev()
|
|
# FIXME(lucasagomes): pyghmi doesn't seem to handle errors
|
|
# consistently, for some errors it raises an exception
|
|
# others it just returns a dictionary with the error.
|
|
if 'error' in ret:
|
|
raise pyghmi_exception.IpmiException(ret['error'])
|
|
except pyghmi_exception.IpmiException as e:
|
|
LOG.error(_LE("IPMI get boot device failed for node %(node_id)s "
|
|
"with the following error: %(error)s"),
|
|
{'node_id': driver_info['uuid'], 'error': e})
|
|
raise exception.IPMIFailure(cmd=e)
|
|
|
|
response['persistent'] = ret.get('persistent')
|
|
bootdev = ret.get('bootdev')
|
|
if bootdev:
|
|
response['boot_device'] = next((dev for dev, hdev in
|
|
_BOOT_DEVICES_MAP.items()
|
|
if hdev == bootdev), None)
|
|
return response
|
|
|
|
@METRICS.timer('NativeIPMIManagement.get_sensors_data')
|
|
def get_sensors_data(self, task):
|
|
"""Get sensors data.
|
|
|
|
:param task: a TaskManager instance.
|
|
:raises: FailedToGetSensorData when getting the sensor data fails.
|
|
:raises: MissingParameterValue if required ipmi parameters are missing
|
|
:returns: returns a dict of sensor data group by sensor type.
|
|
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
return _get_sensors_data(driver_info)
|
|
|
|
|
|
class NativeIPMIShellinaboxConsole(base.ConsoleInterface):
|
|
"""A ConsoleInterface that uses pyghmi and shellinabox."""
|
|
|
|
def get_properties(self):
|
|
d = COMMON_PROPERTIES.copy()
|
|
d.update(CONSOLE_PROPERTIES)
|
|
return d
|
|
|
|
@METRICS.timer('NativeIPMIShellinaboxConsole.validate')
|
|
def validate(self, task):
|
|
"""Validate the Node console info.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:raises: MissingParameterValue when required IPMI credentials or
|
|
the IPMI terminal port are missing
|
|
:raises: InvalidParameterValue when the IPMI terminal port is not
|
|
an integer.
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
if not driver_info['port']:
|
|
raise exception.MissingParameterValue(_(
|
|
"Missing 'ipmi_terminal_port' parameter in node's"
|
|
" driver_info."))
|
|
|
|
@METRICS.timer('NativeIPMIShellinaboxConsole.start_console')
|
|
def start_console(self, task):
|
|
"""Start a remote console for the node.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:raises: MissingParameterValue when required ipmi credentials
|
|
are missing.
|
|
:raises: InvalidParameterValue when the IPMI terminal port is not an
|
|
integer.
|
|
:raises: ConsoleError if unable to start the console process.
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
|
|
path = _console_pwfile_path(driver_info['uuid'])
|
|
pw_file = console_utils.make_persistent_password_file(
|
|
path, driver_info['password'])
|
|
|
|
console_cmd = ("/:%(uid)s:%(gid)s:HOME:pyghmicons %(bmc)s"
|
|
" %(user)s"
|
|
" %(passwd_file)s"
|
|
% {'uid': os.getuid(),
|
|
'gid': os.getgid(),
|
|
'bmc': driver_info['address'],
|
|
'user': driver_info['username'],
|
|
'passwd_file': pw_file})
|
|
try:
|
|
console_utils.start_shellinabox_console(driver_info['uuid'],
|
|
driver_info['port'],
|
|
console_cmd)
|
|
except exception.ConsoleError:
|
|
with excutils.save_and_reraise_exception():
|
|
ironic_utils.unlink_without_raise(path)
|
|
|
|
@METRICS.timer('NativeIPMIShellinaboxConsole.stop_console')
|
|
def stop_console(self, task):
|
|
"""Stop the remote console session for the node.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:raises: ConsoleError if unable to stop the console process.
|
|
"""
|
|
try:
|
|
console_utils.stop_shellinabox_console(task.node.uuid)
|
|
finally:
|
|
password_file = _console_pwfile_path(task.node.uuid)
|
|
ironic_utils.unlink_without_raise(password_file)
|
|
|
|
@METRICS.timer('NativeIPMIShellinaboxConsole.get_console')
|
|
def get_console(self, task):
|
|
"""Get the type and connection information about the console.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:raises: MissingParameterValue when required IPMI credentials or
|
|
the IPMI terminal port are missing
|
|
:raises: InvalidParameterValue when the IPMI terminal port is not
|
|
an integer.
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
url = console_utils.get_shellinabox_console_url(driver_info['port'])
|
|
return {'type': 'shellinabox', 'url': url}
|
|
|
|
|
|
class VendorPassthru(base.VendorInterface):
|
|
|
|
def get_properties(self):
|
|
return COMMON_PROPERTIES
|
|
|
|
@METRICS.timer('VendorPassthru.validate')
|
|
def validate(self, task, method, **kwargs):
|
|
"""Validate vendor-specific actions.
|
|
|
|
:param task: a task from TaskManager.
|
|
:param method: method to be validated
|
|
:param kwargs: info for action.
|
|
:raises: InvalidParameterValue when an invalid parameter value is
|
|
specified.
|
|
:raises: MissingParameterValue if a required parameter is missing.
|
|
|
|
"""
|
|
if method == 'send_raw':
|
|
raw_bytes = kwargs.get('raw_bytes')
|
|
if not raw_bytes:
|
|
raise exception.MissingParameterValue(_(
|
|
'Parameter raw_bytes (string of bytes) was not '
|
|
'specified.'))
|
|
_parse_raw_bytes(raw_bytes)
|
|
|
|
_parse_driver_info(task.node)
|
|
|
|
@METRICS.timer('VendorPassthru.send_raw')
|
|
@base.passthru(['POST'],
|
|
description=_("Send raw bytes to the BMC. Required "
|
|
"argument: 'raw_bytes' - a string of raw "
|
|
"bytes (e.g. '0x00 0x01')."))
|
|
@task_manager.require_exclusive_lock
|
|
def send_raw(self, task, http_method, raw_bytes):
|
|
"""Send raw bytes to the BMC. Bytes should be a string of bytes.
|
|
|
|
:param task: a TaskManager instance.
|
|
:param http_method: the HTTP method used on the request.
|
|
:param raw_bytes: a string of raw bytes to send, e.g. '0x00 0x01'
|
|
:raises: IPMIFailure on an error from native IPMI call.
|
|
:raises: MissingParameterValue if a required parameter is missing.
|
|
:raises: InvalidParameterValue when an invalid value is specified.
|
|
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
_send_raw(driver_info, raw_bytes)
|
|
|
|
@METRICS.timer('VendorPassthru.bmc_reset')
|
|
@base.passthru(['POST'],
|
|
description=_("Reset the BMC. Required argument: 'warm' "
|
|
"(Boolean) - for warm (True) or cold (False) "
|
|
"reset."))
|
|
@task_manager.require_exclusive_lock
|
|
def bmc_reset(self, task, http_method, warm=True):
|
|
"""Reset BMC via IPMI command.
|
|
|
|
:param task: a TaskManager instance.
|
|
:param http_method: the HTTP method used on the request.
|
|
:param warm: boolean parameter to decide on warm or cold reset.
|
|
:raises: IPMIFailure on an error from native IPMI call.
|
|
:raises: MissingParameterValue if a required parameter is missing.
|
|
:raises: InvalidParameterValue when an invalid value is specified
|
|
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
warm = strutils.bool_from_string(warm)
|
|
# NOTE(yuriyz): pyghmi 0.8.0 does not have a method for BMC reset
|
|
command = '0x03' if warm else '0x02'
|
|
raw_command = '0x06 ' + command
|
|
_send_raw(driver_info, raw_command)
|