Remove shellinabox
It's abandoned for years now. Also remove references to web console in the documentation since it seems rely on shellinabox functionality. Change-Id: I5e9eca79b4bdec582d2251ae7e339dfc50a17035 Signed-off-by: Clif Houck <me@clifhouck.com>
This commit is contained in:
@@ -7,10 +7,9 @@ Configuring Consoles
|
||||
Overview
|
||||
--------
|
||||
|
||||
There are three types of consoles which are available in Bare Metal service:
|
||||
There are two types of consoles which are available in Bare Metal service:
|
||||
|
||||
* (`Node graphical console`_) for a graphical console from a NoVNC web browser
|
||||
* (`Node web console`_) a terminal available from a web browser
|
||||
* (`Node serial console`_) for serial console support
|
||||
|
||||
Node graphical console
|
||||
@@ -51,164 +50,6 @@ displays an animation.
|
||||
enabled_hardware_types = fake-hardware
|
||||
enabled_console_interfaces = fake-graphical,no-console
|
||||
|
||||
Node web console
|
||||
----------------
|
||||
|
||||
The web console can be configured in Bare Metal service in the following way:
|
||||
|
||||
* Install shellinabox in ironic conductor node. For RHEL/CentOS, shellinabox package
|
||||
is not present in base repositories, the user must enable EPEL repository, you can
|
||||
find more from `FedoraProject page`_.
|
||||
|
||||
.. warning::
|
||||
|
||||
Shell In A Box is considered abandoned by the Ironic community. The
|
||||
original maintainer stopped maintaining the project and the project
|
||||
was thus forked. The resulting
|
||||
`fork <https://github.com/shellinabox/shellinabox>`_ has not received
|
||||
updates in a number of years and is considered abandoned.
|
||||
As such, shellinabox support has been deprecated by the Ironic community.
|
||||
|
||||
Installation example:
|
||||
|
||||
Ubuntu::
|
||||
|
||||
sudo apt-get install shellinabox
|
||||
|
||||
RHEL/CentOS/Fedora::
|
||||
|
||||
sudo dnf install shellinabox
|
||||
|
||||
You can find more about shellinabox on the `shellinabox page`_.
|
||||
|
||||
You can optionally use the SSL certificate in shellinabox. If you want to use the SSL
|
||||
certificate in shellinabox, you should install openssl and generate the SSL certificate.
|
||||
|
||||
1. Install openssl, for example:
|
||||
|
||||
Ubuntu::
|
||||
|
||||
sudo apt-get install openssl
|
||||
|
||||
RHEL/CentOS/Fedora::
|
||||
|
||||
sudo dnf install openssl
|
||||
|
||||
2. Generate the SSL certificate, here is an example, you can find more about openssl on
|
||||
the `openssl page`_::
|
||||
|
||||
cd /tmp/ca
|
||||
openssl genrsa -des3 -out my.key 1024
|
||||
openssl req -new -key my.key -out my.csr
|
||||
cp my.key my.key.org
|
||||
openssl rsa -in my.key.org -out my.key
|
||||
openssl x509 -req -days 3650 -in my.csr -signkey my.key -out my.crt
|
||||
cat my.crt my.key > certificate.pem
|
||||
|
||||
* Customize the console section in the Bare Metal service configuration
|
||||
file (/etc/ironic/ironic.conf), if you want to use SSL certificate in
|
||||
shellinabox, you should specify ``terminal_cert_dir``.
|
||||
For example::
|
||||
|
||||
[console]
|
||||
|
||||
#
|
||||
# Options defined in ironic.drivers.modules.console_utils
|
||||
#
|
||||
|
||||
# Path to serial console terminal program. Used only by Shell
|
||||
# In A Box console. (string value)
|
||||
#terminal=shellinaboxd
|
||||
|
||||
# Directory containing the terminal SSL cert (PEM) for serial
|
||||
# console access. Used only by Shell In A Box console. (string
|
||||
# value)
|
||||
terminal_cert_dir=/tmp/ca
|
||||
|
||||
# Directory for holding terminal pid files. If not specified,
|
||||
# the temporary directory will be used. (string value)
|
||||
#terminal_pid_dir=<None>
|
||||
|
||||
# Time interval (in seconds) for checking the status of
|
||||
# console subprocess. (integer value)
|
||||
#subprocess_checking_interval=1
|
||||
|
||||
# Time (in seconds) to wait for the console subprocess to
|
||||
# start. (integer value)
|
||||
#subprocess_timeout=10
|
||||
|
||||
* Append console parameters for bare metal PXE boot in the Bare Metal service
|
||||
configuration file (/etc/ironic/ironic.conf). See the reference for
|
||||
configuration in :ref:`kernel-boot-parameters`.
|
||||
|
||||
* Enable the ``ipmitool-shellinabox`` console interface, for example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[DEFAULT]
|
||||
enabled_console_interfaces = ipmitool-shellinabox,no-console
|
||||
|
||||
* Configure node web console.
|
||||
|
||||
If the node uses a hardware type, for example ``ipmi``, set the node's
|
||||
console interface to ``ipmitool-shellinabox``::
|
||||
|
||||
baremetal node set <node> --console-interface ipmitool-shellinabox
|
||||
|
||||
Enable the web console, for example::
|
||||
|
||||
baremetal node set <node> \
|
||||
--driver-info <terminal_port>=<customized_port>
|
||||
baremetal node console enable <node>
|
||||
|
||||
Check whether the console is enabled, for example::
|
||||
|
||||
baremetal node validate <node>
|
||||
|
||||
Disable the web console, for example::
|
||||
|
||||
baremetal node console disable <node>
|
||||
baremetal node unset <node> --driver-info <terminal_port>
|
||||
|
||||
The ``<terminal_port>`` is driver dependent. The actual name of this field can be
|
||||
checked in driver properties, for example::
|
||||
|
||||
baremetal driver property list <driver>
|
||||
|
||||
For the ``ipmi`` hardware type, this option is ``ipmi_terminal_port``.
|
||||
Give a customized port number to ``<customized_port>``,
|
||||
for example ``8023``, this customized port is used in web console url.
|
||||
|
||||
Get web console information for a node as follows::
|
||||
|
||||
baremetal node console show <node>
|
||||
+-----------------+----------------------------------------------------------------------+
|
||||
| Property | Value |
|
||||
+-----------------+----------------------------------------------------------------------+
|
||||
| console_enabled | True |
|
||||
| console_info | {u'url': u'http://<url>:<customized_port>', u'type': u'shellinabox'} |
|
||||
+-----------------+----------------------------------------------------------------------+
|
||||
|
||||
You can open the web console using the above ``url`` through web browser. If
|
||||
``console_enabled`` is ``false``, ``console_info`` is ``None``, web console is disabled.
|
||||
If you want to launch the web console, see the ``Configure node web console`` part.
|
||||
|
||||
.. note::
|
||||
|
||||
An error message you may encounter when enabling the console can read
|
||||
``Console subprocess failed to start. Timeout or error while waiting for
|
||||
console subprocess to start for node`` along with
|
||||
``[server] Failed to find any available port!``. This error is coming from
|
||||
shellinabox itself, not from the communication with the BMC. One potential
|
||||
cause for this issue is that there are already shellinabox daemons running
|
||||
which block the configured port (remove them if appropriate and retry to
|
||||
enable the console).
|
||||
|
||||
.. _`shellinabox page`: https://code.google.com/archive/p/shellinabox/
|
||||
.. _`openssl page`: https://www.openssl.org/
|
||||
.. _`FedoraProject page`: https://fedoraproject.org/wiki/Infrastructure/Mirroring
|
||||
|
||||
|
||||
Node serial console
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Please see :doc:`/install/configure-ipmi` for the required dependencies.
|
||||
|
||||
[DEFAULT]
|
||||
enabled_hardware_types = ipmi
|
||||
enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console
|
||||
enabled_console_interfaces = ipmitool-socat,no-console
|
||||
enabled_management_interfaces = ipmitool,noop
|
||||
enabled_power_interfaces = ipmitool
|
||||
enabled_vendor_interfaces = ipmitool,no-vendor
|
||||
|
||||
@@ -61,7 +61,7 @@ hardware interfaces:
|
||||
future. Instead, use ``irmc-pxe``.
|
||||
|
||||
* console
|
||||
Supports ``ipmitool-socat``, ``ipmitool-shellinabox``, and ``no-console``.
|
||||
Supports ``ipmitool-socat``, and ``no-console``.
|
||||
The default is ``ipmitool-socat``.
|
||||
|
||||
* inspect
|
||||
@@ -93,7 +93,7 @@ interfaces enabled for ``irmc`` hardware type.
|
||||
enabled_hardware_types = irmc
|
||||
enabled_bios_interfaces = irmc
|
||||
enabled_boot_interfaces = irmc-virtual-media,irmc-pxe
|
||||
enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console
|
||||
enabled_console_interfaces = ipmitool-socat,no-console
|
||||
enabled_deploy_interfaces = direct
|
||||
enabled_inspect_interfaces = irmc,agent,no-inspect
|
||||
enabled_management_interfaces = irmc
|
||||
|
||||
@@ -12,5 +12,3 @@ Advanced features
|
||||
.. include:: include/disk-label.inc
|
||||
|
||||
.. include:: include/notifications.inc
|
||||
|
||||
.. include:: include/console.inc
|
||||
|
||||
@@ -623,7 +623,7 @@ Consider the following configuration (shortened for simplicity):
|
||||
|
||||
[DEFAULT]
|
||||
enabled_hardware_types = ipmi,redfish
|
||||
enabled_console_interfaces = no-console,ipmitool-shellinabox
|
||||
enabled_console_interfaces = no-console,ipmitool-socat
|
||||
enabled_deploy_interfaces = direct
|
||||
enabled_management_interfaces = ipmitool,redfish
|
||||
enabled_power_interfaces = ipmitool,redfish
|
||||
@@ -660,11 +660,11 @@ power
|
||||
option has only one item: ``ipmitool``. It is used.
|
||||
console
|
||||
No default is configured. The ``ipmi`` hardware type supports the following
|
||||
console interfaces: ``ipmitool-socat``, ``ipmitool-shellinabox`` and
|
||||
``no-console`` (in this order). Of these three, only two are enabled:
|
||||
``no-console`` and ``ipmitool-shellinabox`` (order does not matter). The
|
||||
intersection contains ``ipmitool-shellinabox`` and ``no-console``.
|
||||
The first item is used, and it is ``ipmitool-shellinabox``.
|
||||
console interfaces: ``ipmitool-socat``, and ``no-console`` (in this
|
||||
order). Both are enabled: ``no-console`` and ``ipmitool-socat``
|
||||
(order does not matter). The intersection contains
|
||||
``ipmitool-socat`` and ``no-console``.
|
||||
The first item is used, and it is ``ipmitool-socat``.
|
||||
management
|
||||
Following the same calculation as *power*, the ``ipmitool`` management
|
||||
interface is used.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
Configuring node web console
|
||||
----------------------------
|
||||
|
||||
See :ref:`console`.
|
||||
|
||||
.. TODO(dtantsur): move the installation documentation here
|
||||
@@ -81,8 +81,8 @@ In order to change default console configuration in the Bare Metal
|
||||
service configuration file (``[pxe]`` section in ``/etc/ironic/ironic.conf``),
|
||||
include the serial port terminal and serial speed. Serial speed must be
|
||||
the same as the serial configuration in the BIOS settings, so that the
|
||||
operating system boot process can be seen in the serial console or web console.
|
||||
Following examples represent possible parameters for serial and web console
|
||||
operating system boot process can be seen in the serial console.
|
||||
Following examples represent possible parameters for serial console
|
||||
respectively.
|
||||
|
||||
* Node serial console. The console parameter ``console=ttyS0,115200n8``
|
||||
@@ -93,15 +93,6 @@ respectively.
|
||||
# Additional append parameters for baremetal PXE boot.
|
||||
kernel_append_params = nofb vga=normal console=ttyS0,115200n8
|
||||
|
||||
|
||||
* For node web console configuration is similar with the addition of ``ttyX``
|
||||
parameter, see example::
|
||||
|
||||
[pxe]
|
||||
|
||||
# Additional append parameters for baremetal PXE boot.
|
||||
kernel_append_params = nofb vga=normal console=tty0 console=ttyS0,115200n8
|
||||
|
||||
For detailed information on how to add consoles see the reference documents
|
||||
`kernel params`_ and `serial console`_.
|
||||
In case of local boot the Bare Metal service is not able to control kernel boot
|
||||
|
||||
+2
-10
@@ -46,14 +46,6 @@ class PortRange(types.String):
|
||||
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('terminal',
|
||||
default='shellinaboxd',
|
||||
help=_('Path to serial console terminal program. Used only '
|
||||
'by Shell In A Box console.')),
|
||||
cfg.StrOpt('terminal_cert_dir',
|
||||
help=_('Directory containing the terminal SSL cert (PEM) for '
|
||||
'serial console access. Used only by Shell In A Box '
|
||||
'console.')),
|
||||
cfg.StrOpt('terminal_pid_dir',
|
||||
help=_('Directory for holding terminal pid files. '
|
||||
'If not specified, the temporary directory '
|
||||
@@ -87,8 +79,8 @@ opts = [
|
||||
'proxy service running on the host of ironic '
|
||||
'conductor, in the form of <start>:<stop> or '
|
||||
'comma-separated ranges like '
|
||||
'<start>:<stop>,<start>:<stop>. This option is used by '
|
||||
'both Shellinabox and Socat console')),
|
||||
'<start>:<stop>,<start>:<stop>. This option is used '
|
||||
'only by Socat console')),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class IntelIPMIHardware(ipmi.IPMIHardware):
|
||||
"""Intel IPMI hardware type.
|
||||
|
||||
Uses ``ipmitool`` to implement power and management.
|
||||
Provides serial console implementations via ``shellinabox`` or ``socat``.
|
||||
Provides serial console implementations via ``socat``.
|
||||
Supports Intel SST-PP feature.
|
||||
"""
|
||||
@property
|
||||
|
||||
@@ -24,15 +24,14 @@ class IPMIHardware(generic.GenericHardware):
|
||||
"""IPMI hardware type.
|
||||
|
||||
Uses ``ipmitool`` to implement power and management.
|
||||
Provides serial console implementations via ``shellinabox`` or ``socat``.
|
||||
Provides serial console implementations via ``socat``.
|
||||
"""
|
||||
|
||||
@property
|
||||
def supported_console_interfaces(self):
|
||||
"""List of supported console interfaces."""
|
||||
return [
|
||||
ipmitool.IPMISocatConsole,
|
||||
ipmitool.IPMIShellinaboxConsole
|
||||
ipmitool.IPMISocatConsole
|
||||
] + super().supported_console_interfaces
|
||||
|
||||
@property
|
||||
|
||||
@@ -62,8 +62,7 @@ class IRMCHardware(generic.GenericHardware):
|
||||
def supported_console_interfaces(self):
|
||||
"""List of supported console interfaces."""
|
||||
return [
|
||||
ipmitool.IPMISocatConsole,
|
||||
ipmitool.IPMIShellinaboxConsole
|
||||
ipmitool.IPMISocatConsole
|
||||
] + super().supported_console_interfaces
|
||||
|
||||
@property
|
||||
|
||||
@@ -203,19 +203,6 @@ def release_port(port):
|
||||
ALLOCATED_PORTS.discard(port)
|
||||
|
||||
|
||||
def get_shellinabox_console_url(port):
|
||||
"""Get a url to access the console via shellinaboxd.
|
||||
|
||||
:param port: the terminal port for the node.
|
||||
"""
|
||||
|
||||
console_host = netutils.escape_ipv6(CONF.my_ip)
|
||||
scheme = 'https' if CONF.console.terminal_cert_dir else 'http'
|
||||
return '%(scheme)s://%(host)s:%(port)s' % {'scheme': scheme,
|
||||
'host': console_host,
|
||||
'port': port}
|
||||
|
||||
|
||||
class _PopenNonblockingPipe(object):
|
||||
def __init__(self, source):
|
||||
self._source = source
|
||||
@@ -251,120 +238,6 @@ class _PopenNonblockingPipe(object):
|
||||
return self._finished
|
||||
|
||||
|
||||
def start_shellinabox_console(node_uuid, port, console_cmd,
|
||||
env_variables=None):
|
||||
"""Open the serial console for a node.
|
||||
|
||||
:param node_uuid: the uuid for the node.
|
||||
:param port: the terminal port for the node.
|
||||
:param console_cmd: the shell command that gets the console.
|
||||
:param env_variables: optional dict of environment variables to pass to
|
||||
the subprocess (e.g. IPMI_PASSWORD for ipmitool -E).
|
||||
:raises: ConsoleError if the directory for the PID file cannot be created
|
||||
or an old process cannot be stopped.
|
||||
:raises: ConsoleSubprocessFailed when invoking the subprocess failed.
|
||||
"""
|
||||
|
||||
# make sure that the old console for this node is stopped
|
||||
# and the files are cleared
|
||||
try:
|
||||
_stop_console(node_uuid)
|
||||
except exception.NoConsolePid:
|
||||
pass
|
||||
|
||||
_ensure_console_pid_dir_exists()
|
||||
pid_file = _get_console_pid_file(node_uuid)
|
||||
|
||||
# put together the command and arguments for invoking the console
|
||||
args = []
|
||||
args.append(CONF.console.terminal)
|
||||
if CONF.console.terminal_cert_dir:
|
||||
args.append("-c")
|
||||
args.append(CONF.console.terminal_cert_dir)
|
||||
else:
|
||||
args.append("-t")
|
||||
args.append("-p")
|
||||
args.append(str(port))
|
||||
args.append("--background=%s" % pid_file)
|
||||
args.append("-s")
|
||||
args.append(console_cmd)
|
||||
|
||||
# run the command as a subprocess
|
||||
try:
|
||||
LOG.debug('Running subprocess: %s', ' '.join(args))
|
||||
# use pipe here to catch the error in case shellinaboxd
|
||||
# failed to start.
|
||||
popen_kwargs = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE}
|
||||
if env_variables is not None:
|
||||
popen_kwargs['env'] = dict(os.environ, **env_variables)
|
||||
obj = subprocess.Popen(args, **popen_kwargs)
|
||||
except (OSError, ValueError) as e:
|
||||
error = _("%(exec_error)s\n"
|
||||
"Command: %(command)s") % {'exec_error': str(e),
|
||||
'command': ' '.join(args)}
|
||||
LOG.warning(error)
|
||||
raise exception.ConsoleSubprocessFailed(error=error)
|
||||
|
||||
error_message = _(
|
||||
"Timeout or error while waiting for console subprocess to start for "
|
||||
"node: %(node)s.\nCommand: %(command)s.\n") % {
|
||||
'node': node_uuid,
|
||||
'command': ' '.join(args)}
|
||||
|
||||
stdout_pipe, stderr_pipe = (
|
||||
_PopenNonblockingPipe(obj.stdout), _PopenNonblockingPipe(obj.stderr))
|
||||
|
||||
def _wait(node_uuid, popen_obj):
|
||||
locals['returncode'] = popen_obj.poll()
|
||||
|
||||
# check if the console pid is created and the process is running.
|
||||
# if it is, then the shellinaboxd is invoked successfully as a daemon.
|
||||
# otherwise check the error.
|
||||
if (locals['returncode'] == 0 and os.path.exists(pid_file)
|
||||
and psutil.pid_exists(_get_console_pid(node_uuid))):
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if locals['returncode'] is not None:
|
||||
watched = (stdout_pipe, stderr_pipe)
|
||||
while time.time() < expiration and not all(
|
||||
(i.finished for i in watched)):
|
||||
for pipe in watched:
|
||||
pipe.read()
|
||||
locals['errstr'] = error_message + _(
|
||||
"Exit code: %(return_code)s.\nStdout: %(stdout)r\n"
|
||||
"Stderr: %(stderr)r") % {
|
||||
'return_code': locals['returncode'],
|
||||
'stdout': stdout_pipe.output, 'stderr': stderr_pipe.output}
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if time.time() > expiration:
|
||||
locals['errstr'] = error_message
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
locals = {'returncode': None, 'errstr': ''}
|
||||
expiration = time.time() + CONF.console.subprocess_timeout
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait, node_uuid, obj)
|
||||
timer.start(interval=CONF.console.subprocess_checking_interval).wait()
|
||||
|
||||
if locals['errstr']:
|
||||
LOG.warning(locals['errstr'])
|
||||
raise exception.ConsoleSubprocessFailed(error=locals['errstr'])
|
||||
|
||||
|
||||
def stop_shellinabox_console(node_uuid):
|
||||
"""Close the serial console for a node.
|
||||
|
||||
:param node_uuid: the UUID of the node
|
||||
:raises: ConsoleError if unable to stop the console process
|
||||
"""
|
||||
|
||||
try:
|
||||
_stop_console(node_uuid)
|
||||
except exception.NoConsolePid:
|
||||
LOG.warning("No console pid found for node %s while trying to "
|
||||
"stop shellinabox console.", node_uuid)
|
||||
|
||||
|
||||
def get_socat_console_url(port):
|
||||
"""Get a URL to access the console via socat.
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ from ironic.drivers.modules import ipmitool
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
|
||||
|
||||
class IloConsoleInterface(ipmitool.IPMIShellinaboxConsole):
|
||||
"""A ConsoleInterface that uses ipmitool and shellinabox."""
|
||||
class IloConsoleInterface(ipmitool.IPMISocatConsole):
|
||||
"""A ConsoleInterface that uses ipmitool."""
|
||||
|
||||
def get_properties(self):
|
||||
props = ilo_common.REQUIRED_PROPERTIES.copy()
|
||||
|
||||
@@ -1653,95 +1653,6 @@ class IPMIConsole(base.ConsoleInterface):
|
||||
raise
|
||||
|
||||
|
||||
class IPMIShellinaboxConsole(IPMIConsole):
|
||||
"""A ConsoleInterface that uses ipmitool and shellinabox."""
|
||||
|
||||
# TODO(TheJulia): This interface is deprecated due to the shellinabox
|
||||
# project being abandoned. It should be removed after the release of
|
||||
# 2025.2 in advance of 2026.1's release.
|
||||
# https://github.com/shellinabox/shellinabox
|
||||
# https://github.com/shellinabox/shellinabox/issues/531
|
||||
|
||||
supported = False
|
||||
|
||||
def _get_ipmi_cmd(self, driver_info, pw_file=None, use_pwd_env=False):
|
||||
"""Get ipmi command for ipmitool usage.
|
||||
|
||||
:param driver_info: driver info with the ipmitool parameters
|
||||
:param pw_file: password file to be used in ipmitool command
|
||||
:param use_pwd_env: when True, add -E so ipmitool reads password
|
||||
from IPMI_PASSWORD environment variable
|
||||
:returns: returns a command string for ipmitool
|
||||
"""
|
||||
command = super(IPMIShellinaboxConsole, self)._get_ipmi_cmd(
|
||||
driver_info, pw_file, use_pwd_env=use_pwd_env)
|
||||
return ("/:%(uid)s:%(gid)s:HOME:%(basic_command)s"
|
||||
% {'uid': os.getuid(),
|
||||
'gid': os.getgid(),
|
||||
'basic_command': command})
|
||||
|
||||
@METRICS.timer('IPMIShellinaboxConsole.start_console')
|
||||
def start_console(self, task):
|
||||
"""Start a remote console for the node.
|
||||
|
||||
:param task: a task from TaskManager
|
||||
:raises: InvalidParameterValue if required ipmi parameters are missing
|
||||
:raises: PasswordFileFailedToCreate if unable to create a file
|
||||
containing the password
|
||||
:raises: ConsoleError if the directory for the PID file cannot be
|
||||
created
|
||||
:raises: ConsoleSubprocessFailed when invoking the subprocess failed
|
||||
"""
|
||||
# Dealloc allocated port if any, so the same host can never has
|
||||
# duplicated port.
|
||||
_release_allocated_port(task)
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
if not driver_info['port']:
|
||||
driver_info['port'] = _allocate_port(task)
|
||||
|
||||
try:
|
||||
self._exec_stop_console(driver_info)
|
||||
except OSError:
|
||||
# We need to drop any existing sol sessions with sol deactivate.
|
||||
# OSError is raised when sol session is already deactivated,
|
||||
# so we can ignore it.
|
||||
pass
|
||||
self._start_console(driver_info,
|
||||
console_utils.start_shellinabox_console)
|
||||
|
||||
@METRICS.timer('IPMIShellinaboxConsole.stop_console')
|
||||
def stop_console(self, task):
|
||||
"""Stop the remote console session for the node.
|
||||
|
||||
:param task: a task from TaskManager
|
||||
:raises: ConsoleError if unable to stop the console
|
||||
"""
|
||||
try:
|
||||
console_utils.stop_shellinabox_console(task.node.uuid)
|
||||
finally:
|
||||
utils.unlink_without_raise(_console_pwfile_path(task.node.uuid))
|
||||
_release_allocated_port(task)
|
||||
|
||||
def _exec_stop_console(self, driver_info):
|
||||
cmd = "sol deactivate"
|
||||
_exec_ipmitool(driver_info, cmd, check_exit_code=[0, 1])
|
||||
|
||||
@METRICS.timer('IPMIShellinaboxConsole.get_console')
|
||||
def get_console(self, task):
|
||||
"""Get the type and connection information about the console."""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
|
||||
try:
|
||||
self._exec_stop_console(driver_info)
|
||||
except OSError:
|
||||
# We need to drop any existing sol sessions with sol deactivate.
|
||||
# OSError is raised when sol session is already deactivated,
|
||||
# so we can ignore it.
|
||||
pass
|
||||
url = console_utils.get_shellinabox_console_url(driver_info['port'])
|
||||
return {'type': 'shellinabox', 'url': url}
|
||||
|
||||
|
||||
class IPMISocatConsole(IPMIConsole):
|
||||
"""A ConsoleInterface that uses ipmitool and socat."""
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class IloConsoleInterfaceTestCase(test_common.BaseIloTest):
|
||||
|
||||
boot_interface = 'ilo-virtual-media'
|
||||
|
||||
@mock.patch.object(ipmitool.IPMIShellinaboxConsole, 'validate',
|
||||
@mock.patch.object(ipmitool.IPMISocatConsole, 'validate',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_ipmi_properties', spec_set=True,
|
||||
autospec=True)
|
||||
@@ -41,7 +41,7 @@ class IloConsoleInterfaceTestCase(test_common.BaseIloTest):
|
||||
update_ipmi_mock.assert_called_once_with(task)
|
||||
ipmi_validate_mock.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
@mock.patch.object(ipmitool.IPMIShellinaboxConsole, 'validate',
|
||||
@mock.patch.object(ipmitool.IPMISocatConsole, 'validate',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(ilo_common, 'update_ipmi_properties', spec_set=True,
|
||||
autospec=True)
|
||||
|
||||
@@ -68,23 +68,6 @@ class IntelIPMIHardwareTestCase(db_base.DbTestCase):
|
||||
with task_manager.acquire(self.context, node.id) as task:
|
||||
self._validate_interfaces(task)
|
||||
|
||||
def test_override_with_shellinabox(self):
|
||||
self.config(enabled_console_interfaces=['ipmitool-shellinabox',
|
||||
'ipmitool-socat'])
|
||||
node = obj_utils.create_test_node(
|
||||
self.context, driver='intel-ipmi',
|
||||
deploy_interface='direct',
|
||||
raid_interface='agent',
|
||||
console_interface='ipmitool-shellinabox',
|
||||
vendor_interface='no-vendor')
|
||||
with task_manager.acquire(self.context, node.id) as task:
|
||||
self._validate_interfaces(
|
||||
task,
|
||||
deploy=agent.AgentDeploy,
|
||||
console=ipmitool.IPMIShellinaboxConsole,
|
||||
raid=agent.AgentRAID,
|
||||
vendor=noop.NoVendor)
|
||||
|
||||
def test_override_with_cinder_storage(self):
|
||||
self.config(enabled_storage_interfaces=['noop', 'cinder'])
|
||||
node = obj_utils.create_test_node(
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
|
||||
"""Test class for console_utils driver module."""
|
||||
|
||||
import errno
|
||||
import fcntl
|
||||
import ipaddress
|
||||
import os
|
||||
import random
|
||||
import signal
|
||||
@@ -27,7 +24,6 @@ import socket
|
||||
import string
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
@@ -196,22 +192,6 @@ class ConsoleUtilsTestCase(db_base.DbTestCase):
|
||||
self.assertFalse(mock_kill.called)
|
||||
mock_unlink.assert_called_once_with(pid_file)
|
||||
|
||||
@mock.patch.object(utils, 'unlink_without_raise', autospec=True)
|
||||
@mock.patch.object(os, 'kill', autospec=True)
|
||||
@mock.patch.object(console_utils, '_get_console_pid', autospec=True)
|
||||
def test__stop_console_shellinabox_not_running(self, mock_pid,
|
||||
mock_kill, mock_unlink):
|
||||
pid_file = console_utils._get_console_pid_file(self.info['uuid'])
|
||||
mock_pid.return_value = 12345
|
||||
mock_kill.side_effect = OSError(errno.ESRCH, 'message')
|
||||
|
||||
console_utils._stop_console(self.info['uuid'])
|
||||
|
||||
mock_pid.assert_called_once_with(self.info['uuid'])
|
||||
mock_kill.assert_called_once_with(mock_pid.return_value,
|
||||
signal.SIGTERM)
|
||||
mock_unlink.assert_called_once_with(pid_file)
|
||||
|
||||
@mock.patch.object(utils, 'unlink_without_raise', autospec=True)
|
||||
@mock.patch.object(os, 'kill', autospec=True)
|
||||
@mock.patch.object(console_utils, '_get_console_pid', autospec=True)
|
||||
@@ -229,24 +209,6 @@ class ConsoleUtilsTestCase(db_base.DbTestCase):
|
||||
signal.SIGTERM)
|
||||
mock_unlink.assert_called_once_with(pid_file)
|
||||
|
||||
def _get_shellinabox_console(self, scheme):
|
||||
generated_url = (
|
||||
console_utils.get_shellinabox_console_url(self.info['port']))
|
||||
console_host = CONF.my_ip
|
||||
if ipaddress.ip_address(console_host).version == 6:
|
||||
console_host = '[%s]' % console_host
|
||||
http_url = "%s://%s:%s" % (scheme, console_host, self.info['port'])
|
||||
self.assertEqual(http_url, generated_url)
|
||||
|
||||
def test_get_shellinabox_console_url(self):
|
||||
self._get_shellinabox_console('http')
|
||||
|
||||
def test_get_shellinabox_console_https_url(self):
|
||||
# specify terminal_cert_dir in /etc/ironic/ironic.conf
|
||||
self.config(terminal_cert_dir='/tmp', group='console')
|
||||
# use https
|
||||
self._get_shellinabox_console('https')
|
||||
|
||||
def test_make_persistent_password_file(self):
|
||||
filepath = '%(tempdir)s/%(node_uuid)s' % {
|
||||
'tempdir': tempfile.gettempdir(),
|
||||
@@ -274,281 +236,6 @@ class ConsoleUtilsTestCase(db_base.DbTestCase):
|
||||
filepath,
|
||||
'password')
|
||||
|
||||
@mock.patch.object(fcntl, 'fcntl', autospec=True)
|
||||
@mock.patch.object(console_utils, 'open',
|
||||
mock.mock_open(read_data='12345\n'))
|
||||
@mock.patch.object(os.path, 'exists', autospec=True)
|
||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
||||
@mock.patch.object(psutil, 'pid_exists', autospec=True)
|
||||
@mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
|
||||
autospec=True)
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_start_shellinabox_console(self, mock_stop,
|
||||
mock_dir_exists,
|
||||
mock_pid_exists,
|
||||
mock_popen,
|
||||
mock_path_exists, mock_fcntl):
|
||||
mock_popen.return_value.poll.return_value = 0
|
||||
|
||||
self.mock_stdout.write(b'0')
|
||||
self.mock_stdout.seek(0)
|
||||
mock_popen.return_value.stdout = self.mock_stdout
|
||||
|
||||
self.mock_stderr.write(b'1')
|
||||
self.mock_stderr.seek(0)
|
||||
mock_popen.return_value.stderr = self.mock_stderr
|
||||
|
||||
mock_pid_exists.return_value = True
|
||||
mock_path_exists.return_value = True
|
||||
|
||||
console_utils.start_shellinabox_console(self.info['uuid'],
|
||||
self.info['port'],
|
||||
'ls&')
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
mock_dir_exists.assert_called_once_with()
|
||||
mock_pid_exists.assert_called_once_with(12345)
|
||||
mock_popen.assert_called_once_with(mock.ANY,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
mock_popen.return_value.poll.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(console_utils, 'open',
|
||||
mock.mock_open(read_data='12345\n'))
|
||||
@mock.patch.object(os.path, 'exists', autospec=True)
|
||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
||||
@mock.patch.object(psutil, 'pid_exists', autospec=True)
|
||||
@mock.patch.object(console_utils, '_get_console_pid_file', autospec=True)
|
||||
@mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
|
||||
autospec=True)
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
@mock.patch.object(loopingcall.FixedIntervalLoopingCall, 'start',
|
||||
autospec=True)
|
||||
def test_start_shellinabox_console_with_env_variables(
|
||||
self, mock_timer_start, mock_stop, mock_dir_exists,
|
||||
mock_get_pid, mock_pid_exists, mock_popen, mock_path_exists):
|
||||
"""Test start_shellinabox_console passes env_variables to Popen."""
|
||||
mock_timer_start.return_value = mock.Mock()
|
||||
mock_get_pid.return_value = '/tmp/%s.pid' % self.info['uuid']
|
||||
mock_popen.return_value.poll.return_value = 0
|
||||
mock_popen.return_value.stdout = self.mock_stdout
|
||||
self.mock_stdout.seek(0)
|
||||
mock_popen.return_value.stderr = self.mock_stderr
|
||||
self.mock_stderr.seek(0)
|
||||
mock_pid_exists.return_value = True
|
||||
mock_path_exists.return_value = True
|
||||
|
||||
console_utils.start_shellinabox_console(
|
||||
self.info['uuid'], self.info['port'], 'ls&',
|
||||
env_variables={'SOME_VAR': 'value'})
|
||||
|
||||
mock_popen.assert_called_once()
|
||||
call_kwargs = mock_popen.call_args[1]
|
||||
self.assertIn('env', call_kwargs)
|
||||
env = call_kwargs['env']
|
||||
self.assertEqual(env.get('SOME_VAR'), 'value')
|
||||
# Should be a copy of os.environ plus our var
|
||||
for key, value in os.environ.items():
|
||||
self.assertEqual(env.get(key), value,
|
||||
'env should contain os.environ')
|
||||
|
||||
@mock.patch.object(fcntl, 'fcntl', autospec=True)
|
||||
@mock.patch.object(console_utils, 'open',
|
||||
mock.mock_open(read_data='12345\n'))
|
||||
@mock.patch.object(os.path, 'exists', autospec=True)
|
||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
||||
@mock.patch.object(psutil, 'pid_exists', autospec=True)
|
||||
@mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
|
||||
autospec=True)
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_start_shellinabox_console_nopid(self, mock_stop,
|
||||
mock_dir_exists,
|
||||
mock_pid_exists,
|
||||
mock_popen,
|
||||
mock_path_exists, mock_fcntl):
|
||||
# no existing PID file before starting
|
||||
mock_stop.side_effect = exception.NoConsolePid('/tmp/blah')
|
||||
mock_popen.return_value.poll.return_value = 0
|
||||
|
||||
self.mock_stdout.write(b'0')
|
||||
self.mock_stdout.seek(0)
|
||||
mock_popen.return_value.stdout = self.mock_stdout
|
||||
|
||||
self.mock_stderr.write(b'1')
|
||||
self.mock_stderr.seek(0)
|
||||
mock_popen.return_value.stderr = self.mock_stderr
|
||||
|
||||
mock_pid_exists.return_value = True
|
||||
mock_path_exists.return_value = True
|
||||
|
||||
console_utils.start_shellinabox_console(self.info['uuid'],
|
||||
self.info['port'],
|
||||
'ls&')
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
mock_dir_exists.assert_called_once_with()
|
||||
mock_pid_exists.assert_called_once_with(12345)
|
||||
mock_popen.assert_called_once_with(mock.ANY,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
mock_popen.return_value.poll.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(time, 'sleep', autospec=True)
|
||||
@mock.patch.object(os, 'read', autospec=True)
|
||||
@mock.patch.object(fcntl, 'fcntl', autospec=True)
|
||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
||||
@mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
|
||||
autospec=True)
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_start_shellinabox_console_fail(
|
||||
self, mock_stop, mock_dir_exists, mock_popen, mock_fcntl,
|
||||
mock_os_read, mock_sleep):
|
||||
mock_popen.return_value.poll.return_value = 1
|
||||
|
||||
self.mock_stdout.write(b'0')
|
||||
self.mock_stdout.seek(0)
|
||||
mock_popen.return_value.stdout = self.mock_stdout
|
||||
|
||||
self.mock_stderr.write(b'1')
|
||||
self.mock_stderr.seek(0)
|
||||
mock_popen.return_value.stderr = self.mock_stderr
|
||||
|
||||
err_output = b'error output'
|
||||
mock_os_read.side_effect = [err_output] * 2 + [OSError] * 2
|
||||
mock_fcntl.side_effect = [1, mock.Mock()] * 2
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exception.ConsoleSubprocessFailed, "Stdout: %r" % err_output,
|
||||
console_utils.start_shellinabox_console, self.info['uuid'],
|
||||
self.info['port'], 'ls&')
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
mock_sleep.assert_has_calls([mock.call(1), mock.call(1)])
|
||||
mock_dir_exists.assert_called_once_with()
|
||||
for obj in (self.mock_stdout, self.mock_stderr):
|
||||
mock_fcntl.assert_has_calls([
|
||||
mock.call(obj, fcntl.F_GETFL),
|
||||
mock.call(obj, fcntl.F_SETFL, 1 | os.O_NONBLOCK)])
|
||||
mock_popen.assert_called_once_with(mock.ANY,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
mock_popen.return_value.poll.assert_called_with()
|
||||
|
||||
@mock.patch.object(fcntl, 'fcntl', autospec=True)
|
||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
||||
@mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
|
||||
autospec=True)
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_start_shellinabox_console_timeout(
|
||||
self, mock_stop, mock_dir_exists, mock_popen, mock_fcntl):
|
||||
self.config(subprocess_timeout=0, group='console')
|
||||
self.config(subprocess_checking_interval=0, group='console')
|
||||
mock_popen.return_value.poll.return_value = None
|
||||
|
||||
self.mock_stdout.write(b'0')
|
||||
self.mock_stdout.seek(0)
|
||||
mock_popen.return_value.stdout = self.mock_stdout
|
||||
|
||||
self.mock_stderr.write(b'1')
|
||||
self.mock_stderr.seek(0)
|
||||
mock_popen.return_value.stderr = self.mock_stderr
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exception.ConsoleSubprocessFailed, 'Timeout or error',
|
||||
console_utils.start_shellinabox_console, self.info['uuid'],
|
||||
self.info['port'], 'ls&')
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
mock_dir_exists.assert_called_once_with()
|
||||
mock_popen.assert_called_once_with(mock.ANY,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
mock_popen.return_value.poll.assert_called_with()
|
||||
self.assertEqual(0, mock_popen.return_value.communicate.call_count)
|
||||
|
||||
@mock.patch.object(time, 'sleep', autospec=True)
|
||||
@mock.patch.object(os, 'read', autospec=True)
|
||||
@mock.patch.object(fcntl, 'fcntl', autospec=True)
|
||||
@mock.patch.object(console_utils, 'open',
|
||||
mock.mock_open(read_data='12345\n'))
|
||||
@mock.patch.object(os.path, 'exists', autospec=True)
|
||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
||||
@mock.patch.object(psutil, 'pid_exists', autospec=True)
|
||||
@mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
|
||||
autospec=True)
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_start_shellinabox_console_fail_no_pid(
|
||||
self, mock_stop, mock_dir_exists, mock_pid_exists, mock_popen,
|
||||
mock_path_exists, mock_fcntl, mock_os_read, mock_sleep):
|
||||
mock_popen.return_value.poll.return_value = 0
|
||||
|
||||
self.mock_stdout.write(b'0')
|
||||
self.mock_stdout.seek(0)
|
||||
mock_popen.return_value.stdout = self.mock_stdout
|
||||
|
||||
self.mock_stderr.write(b'1')
|
||||
self.mock_stderr.seek(0)
|
||||
mock_popen.return_value.stderr = self.mock_stderr
|
||||
|
||||
mock_pid_exists.return_value = False
|
||||
mock_os_read.side_effect = [b'error output'] * 2 + [OSError] * 2
|
||||
mock_fcntl.side_effect = [1, mock.Mock()] * 2
|
||||
mock_path_exists.return_value = True
|
||||
|
||||
self.assertRaises(exception.ConsoleSubprocessFailed,
|
||||
console_utils.start_shellinabox_console,
|
||||
self.info['uuid'],
|
||||
self.info['port'],
|
||||
'ls&')
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
mock_sleep.assert_has_calls([mock.call(1), mock.call(1)])
|
||||
mock_dir_exists.assert_called_once_with()
|
||||
for obj in (self.mock_stdout, self.mock_stderr):
|
||||
mock_fcntl.assert_has_calls([
|
||||
mock.call(obj, fcntl.F_GETFL),
|
||||
mock.call(obj, fcntl.F_SETFL, 1 | os.O_NONBLOCK)])
|
||||
mock_pid_exists.assert_called_with(12345)
|
||||
mock_popen.assert_called_once_with(mock.ANY,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
mock_popen.return_value.poll.assert_called_with()
|
||||
|
||||
@mock.patch.object(subprocess, 'Popen', autospec=True)
|
||||
@mock.patch.object(console_utils, '_ensure_console_pid_dir_exists',
|
||||
autospec=True)
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_start_shellinabox_console_fail_nopiddir(self, mock_stop,
|
||||
mock_dir_exists,
|
||||
mock_popen):
|
||||
mock_dir_exists.side_effect = exception.ConsoleError(message='fail')
|
||||
mock_popen.return_value.poll.return_value = 0
|
||||
|
||||
self.assertRaises(exception.ConsoleError,
|
||||
console_utils.start_shellinabox_console,
|
||||
self.info['uuid'],
|
||||
self.info['port'],
|
||||
'ls&')
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
mock_dir_exists.assert_called_once_with()
|
||||
self.assertFalse(mock_popen.called)
|
||||
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_stop_shellinabox_console(self, mock_stop):
|
||||
|
||||
console_utils.stop_shellinabox_console(self.info['uuid'])
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
|
||||
@mock.patch.object(console_utils, '_stop_console', autospec=True)
|
||||
def test_stop_shellinabox_console_fail_nopid(self, mock_stop):
|
||||
mock_stop.side_effect = exception.NoConsolePid('/tmp/blah')
|
||||
|
||||
console_utils.stop_shellinabox_console(self.info['uuid'])
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
|
||||
def test_get_socat_console_url_tcp(self):
|
||||
self.config(my_ip="10.0.0.1")
|
||||
url = console_utils.get_socat_console_url(self.info['port'])
|
||||
|
||||
@@ -180,7 +180,7 @@ class IPMIToolCheckInitTestCase(base.TestCase):
|
||||
def test_console_init_calls(self, mock_check_dir, mock_support):
|
||||
mock_support.return_value = True
|
||||
ipmi.TMP_DIR_CHECKED = None
|
||||
ipmi.IPMIShellinaboxConsole()
|
||||
ipmi.IPMISocatConsole()
|
||||
mock_support.assert_called_with(mock.ANY)
|
||||
mock_check_dir.assert_called_once_with()
|
||||
|
||||
@@ -191,7 +191,7 @@ class IPMIToolCheckInitTestCase(base.TestCase):
|
||||
mock_support):
|
||||
mock_support.return_value = True
|
||||
ipmi.TMP_DIR_CHECKED = True
|
||||
ipmi.IPMIShellinaboxConsole()
|
||||
ipmi.IPMISocatConsole()
|
||||
mock_support.assert_called_with(mock.ANY)
|
||||
self.assertFalse(mock_check_dir.called)
|
||||
|
||||
@@ -486,7 +486,6 @@ class Base(db_base.DbTestCase):
|
||||
enabled_vendor_interfaces=['fake', 'ipmitool',
|
||||
'no-vendor'],
|
||||
enabled_console_interfaces=['fake', 'ipmitool-socat',
|
||||
'ipmitool-shellinabox',
|
||||
'no-console'])
|
||||
self.config(debug=True, group="ipmi")
|
||||
self.node = obj_utils.create_test_node(
|
||||
@@ -4022,304 +4021,21 @@ class IPMIToolDriverTestCase(Base):
|
||||
self.assertIsNone(info.get('allocated_ipmi_terminal_port'))
|
||||
|
||||
|
||||
class IPMIToolShellinaboxTestCase(db_base.DbTestCase):
|
||||
console_interface = 'ipmitool-shellinabox'
|
||||
console_class = ipmi.IPMIShellinaboxConsole
|
||||
class IPMIToolSocatDriverTestCase(db_base.DbTestCase):
|
||||
console_interface = 'ipmitool-socat'
|
||||
console_class = ipmi.IPMISocatConsole
|
||||
|
||||
def setUp(self):
|
||||
super(IPMIToolShellinaboxTestCase, self).setUp()
|
||||
super(IPMIToolSocatDriverTestCase, self).setUp()
|
||||
self.config(enabled_console_interfaces=[self.console_interface,
|
||||
'no-console'])
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
console_interface=self.console_interface,
|
||||
driver_info=INFO_DICT)
|
||||
self.context,
|
||||
console_interface=self.console_interface,
|
||||
driver_info=INFO_DICT)
|
||||
self.info = ipmi._parse_driver_info(self.node)
|
||||
self.console = self.console_class()
|
||||
|
||||
def test_console_validate(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=True) as task:
|
||||
task.node.driver_info['ipmi_terminal_port'] = 123
|
||||
task.driver.console.validate(task)
|
||||
|
||||
def test_console_validate_missing_port(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=True) as task:
|
||||
task.node.driver_info.pop('ipmi_terminal_port', None)
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
task.driver.console.validate, task)
|
||||
|
||||
def test_console_validate_missing_port_auto_allocate(self):
|
||||
self.config(port_range='10000:20000', group='console')
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=True) as task:
|
||||
task.node.driver_info.pop('ipmi_terminal_port', None)
|
||||
task.driver.console.validate(task)
|
||||
|
||||
def test_console_validate_invalid_port(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=True) as task:
|
||||
task.node.driver_info['ipmi_terminal_port'] = ''
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.console.validate, task)
|
||||
|
||||
def test_console_validate_wrong_ipmi_protocol_version(self):
|
||||
with task_manager.acquire(
|
||||
self.context, self.node.uuid, shared=True) as task:
|
||||
task.node.driver_info['ipmi_terminal_port'] = 123
|
||||
task.node.driver_info['ipmi_protocol_version'] = '1.5'
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.console.validate, task)
|
||||
|
||||
def test__get_ipmi_cmd(self):
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
ipmi_cmd = self.console._get_ipmi_cmd(driver_info, 'pw_file')
|
||||
expected_ipmi_cmd = ("/:%(uid)s:%(gid)s:HOME:ipmitool "
|
||||
"-I lanplus -H %(address)s -L ADMINISTRATOR "
|
||||
"-U %(user)s -f pw_file" %
|
||||
{'uid': os.getuid(), 'gid': os.getgid(),
|
||||
'address': driver_info['address'],
|
||||
'user': driver_info['username']})
|
||||
self.assertEqual(expected_ipmi_cmd, ipmi_cmd)
|
||||
|
||||
def test__get_ipmi_cmd_without_user(self):
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
driver_info['username'] = None
|
||||
ipmi_cmd = self.console._get_ipmi_cmd(driver_info, 'pw_file')
|
||||
expected_ipmi_cmd = ("/:%(uid)s:%(gid)s:HOME:ipmitool "
|
||||
"-I lanplus -H %(address)s -L ADMINISTRATOR "
|
||||
"-f pw_file" %
|
||||
{'uid': os.getuid(), 'gid': os.getgid(),
|
||||
'address': driver_info['address']})
|
||||
self.assertEqual(expected_ipmi_cmd, ipmi_cmd)
|
||||
|
||||
@mock.patch.object(ipmi, '_allocate_port', autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIConsole, '_start_console', autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIShellinaboxConsole, "_exec_stop_console",
|
||||
autospec=True)
|
||||
def test_start_console(self, mock_exec_stop, mock_start, mock_alloc):
|
||||
mock_start.return_value = None
|
||||
mock_alloc.return_value = 10000
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.console.start_console(task)
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
driver_info.update(port=10000)
|
||||
mock_exec_stop.assert_called_once_with(self.console, driver_info)
|
||||
mock_start.assert_called_once_with(
|
||||
self.console, driver_info,
|
||||
console_utils.start_shellinabox_console)
|
||||
|
||||
@mock.patch.object(ipmi, '_allocate_port', autospec=True)
|
||||
@mock.patch.object(ipmi, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIShellinaboxConsole, "_exec_stop_console",
|
||||
autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIConsole, '_start_console', autospec=True)
|
||||
def test_start_console_with_port(self, mock_start, mock_exec_stop,
|
||||
mock_info, mock_alloc):
|
||||
mock_start.return_value = None
|
||||
mock_info.return_value = {'port': 10000}
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.console.start_console(task)
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
mock_start.assert_called_once_with(
|
||||
self.console, {'port': 10000},
|
||||
console_utils.start_shellinabox_console)
|
||||
mock_exec_stop.assert_called_once_with(self.console, driver_info)
|
||||
mock_alloc.assert_not_called()
|
||||
|
||||
@mock.patch.object(ipmi, '_allocate_port', autospec=True)
|
||||
@mock.patch.object(ipmi, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIShellinaboxConsole, "_exec_stop_console",
|
||||
autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIConsole, '_start_console', autospec=True)
|
||||
def test_start_console_alloc_port(self, mock_start, mock_exec_stop,
|
||||
mock_info, mock_alloc):
|
||||
mock_start.return_value = None
|
||||
mock_info.return_value = {'port': None}
|
||||
mock_alloc.return_value = 1234
|
||||
# Ensure allocated port is not reused
|
||||
dii = self.node.driver_internal_info
|
||||
dii['allocated_ipmi_terminal_port'] = 4321
|
||||
self.node.driver_internal_info = dii
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.console.start_console(task)
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
mock_start.assert_called_once_with(
|
||||
self.console, {'port': 1234},
|
||||
console_utils.start_shellinabox_console)
|
||||
mock_exec_stop.assert_called_once_with(self.console, driver_info)
|
||||
mock_alloc.assert_called_once_with(mock.ANY)
|
||||
|
||||
@mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
|
||||
def test__exec_stop_console(self, mock_exec):
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
self.console._exec_stop_console(driver_info)
|
||||
|
||||
mock_exec.assert_called_once_with(
|
||||
driver_info, 'sol deactivate', check_exit_code=[0, 1])
|
||||
|
||||
@mock.patch.object(ipmi, '_persist_ipmi_password', autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIConsole, '_get_ipmi_cmd', autospec=True)
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test__start_console(self, mock_start, mock_ipmi_cmd, mock_persist):
|
||||
mock_start.return_value = None
|
||||
mock_persist.return_value = ('-f', '/tmp/console.pw')
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
self.console._start_console(
|
||||
driver_info, console_utils.start_shellinabox_console)
|
||||
|
||||
mock_persist.assert_called_once_with(driver_info)
|
||||
mock_start.assert_called_once_with(self.info['uuid'],
|
||||
self.info['port'],
|
||||
mock.ANY)
|
||||
mock_ipmi_cmd.assert_called_once_with(
|
||||
self.console, driver_info, pw_file='/tmp/console.pw',
|
||||
use_pwd_env=False)
|
||||
|
||||
@mock.patch.object(ipmi, '_persist_ipmi_password', autospec=True)
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test__start_console_fail(self, mock_start, mock_persist):
|
||||
mock_start.side_effect = exception.ConsoleSubprocessFailed(
|
||||
error='error')
|
||||
mock_persist.return_value = ('-f', '/tmp/console.pw')
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
self.assertRaises(exception.ConsoleSubprocessFailed,
|
||||
self.console._start_console,
|
||||
driver_info,
|
||||
console_utils.start_shellinabox_console)
|
||||
|
||||
@mock.patch.object(ipmi, '_persist_ipmi_password', autospec=True)
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test__start_console_fail_nodir(self, mock_start, mock_persist):
|
||||
mock_start.side_effect = exception.ConsoleError()
|
||||
mock_persist.return_value = ('-f', '/tmp/console.pw')
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
self.assertRaises(exception.ConsoleError,
|
||||
self.console._start_console,
|
||||
driver_info,
|
||||
console_utils.start_shellinabox_console)
|
||||
mock_start.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch.object(ipmi, '_persist_ipmi_password', autospec=True)
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test__start_console_empty_password(self, mock_start, mock_persist):
|
||||
mock_persist.return_value = ('-f', '/tmp/console.pw')
|
||||
driver_info = self.node.driver_info
|
||||
del driver_info['ipmi_password']
|
||||
self.node.driver_info = driver_info
|
||||
self.node.save()
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
self.console._start_console(
|
||||
driver_info,
|
||||
console_utils.start_shellinabox_console)
|
||||
|
||||
mock_start.assert_called_once_with(self.info['uuid'],
|
||||
self.info['port'],
|
||||
mock.ANY)
|
||||
|
||||
@mock.patch.object(ipmi, '_persist_ipmi_password', autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIConsole, '_get_ipmi_cmd', autospec=True)
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test__start_console_store_cred_in_env(self, mock_start, mock_ipmi_cmd,
|
||||
mock_persist):
|
||||
"""Test _start_console with store_cred_in_env=True uses env."""
|
||||
mock_start.return_value = None
|
||||
mock_persist.return_value = ('-E', {'IPMI_PASSWORD': 'secret'})
|
||||
|
||||
self.config(store_cred_in_env=True, group='ipmi')
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
self.console._start_console(
|
||||
driver_info, console_utils.start_shellinabox_console)
|
||||
|
||||
mock_persist.assert_called_once_with(driver_info)
|
||||
mock_ipmi_cmd.assert_called_once_with(
|
||||
self.console, driver_info, pw_file=None,
|
||||
use_pwd_env=True)
|
||||
mock_start.assert_called_once_with(
|
||||
self.info['uuid'], self.info['port'], mock.ANY,
|
||||
env_variables={'IPMI_PASSWORD': 'secret'})
|
||||
|
||||
@mock.patch.object(ipmi, '_release_allocated_port', autospec=True)
|
||||
@mock.patch.object(console_utils, 'stop_shellinabox_console',
|
||||
autospec=True)
|
||||
def test_stop_console(self, mock_stop, mock_release):
|
||||
mock_stop.return_value = None
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.console.stop_console(task)
|
||||
|
||||
mock_stop.assert_called_once_with(self.info['uuid'])
|
||||
mock_release.assert_called_once_with(mock.ANY)
|
||||
|
||||
@mock.patch.object(console_utils, 'stop_shellinabox_console',
|
||||
autospec=True)
|
||||
def test_stop_console_fail(self, mock_stop):
|
||||
mock_stop.side_effect = exception.ConsoleError()
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.assertRaises(exception.ConsoleError,
|
||||
self.console.stop_console,
|
||||
task)
|
||||
|
||||
mock_stop.assert_called_once_with(self.node.uuid)
|
||||
|
||||
@mock.patch.object(console_utils, 'get_shellinabox_console_url',
|
||||
autospec=True)
|
||||
@mock.patch.object(ipmi.IPMIShellinaboxConsole, "_exec_stop_console",
|
||||
autospec=True)
|
||||
def test_get_console(self, mock_exec_stop, mock_get):
|
||||
url = 'http://localhost:4201'
|
||||
mock_get.return_value = url
|
||||
expected = {'type': 'shellinabox', 'url': url}
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
console_info = self.console.get_console(task)
|
||||
driver_info = ipmi._parse_driver_info(task.node)
|
||||
|
||||
mock_exec_stop.assert_called_once_with(self.console, driver_info)
|
||||
self.assertEqual(expected, console_info)
|
||||
mock_get.assert_called_once_with(self.info['port'])
|
||||
|
||||
|
||||
class IPMIToolSocatDriverTestCase(IPMIToolShellinaboxTestCase):
|
||||
console_interface = 'ipmitool-shellinabox'
|
||||
console_class = ipmi.IPMISocatConsole
|
||||
|
||||
def test__get_ipmi_cmd(self):
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
|
||||
@@ -67,18 +67,18 @@ class IPMIHardwareTestCase(db_base.DbTestCase):
|
||||
with task_manager.acquire(self.context, node.id) as task:
|
||||
self._validate_interfaces(task)
|
||||
|
||||
def test_override_with_shellinabox(self):
|
||||
self.config(enabled_console_interfaces=['ipmitool-shellinabox',
|
||||
def test_override_with_socat(self):
|
||||
self.config(enabled_console_interfaces=['fake',
|
||||
'ipmitool-socat'])
|
||||
node = obj_utils.create_test_node(
|
||||
self.context, driver='ipmi',
|
||||
raid_interface='agent',
|
||||
console_interface='ipmitool-shellinabox',
|
||||
console_interface='ipmitool-socat',
|
||||
vendor_interface='no-vendor')
|
||||
with task_manager.acquire(self.context, node.id) as task:
|
||||
self._validate_interfaces(
|
||||
task,
|
||||
console=ipmitool.IPMIShellinaboxConsole,
|
||||
console=ipmitool.IPMISocatConsole,
|
||||
raid=agent.AgentRAID,
|
||||
vendor=noop.NoVendor)
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ http-ipxe = "ironic.drivers.modules.ipxe:iPXEHttpBoot"
|
||||
fake = "ironic.drivers.modules.fake:FakeConsole"
|
||||
fake-graphical = "ironic.drivers.modules.fake:FakeGraphicalConsole"
|
||||
ilo = "ironic.drivers.modules.ilo.console:IloConsoleInterface"
|
||||
ipmitool-shellinabox = "ironic.drivers.modules.ipmitool:IPMIShellinaboxConsole"
|
||||
ipmitool-socat = "ironic.drivers.modules.ipmitool:IPMISocatConsole"
|
||||
no-console = "ironic.drivers.modules.noop:NoConsole"
|
||||
redfish-graphical = "ironic.drivers.modules.redfish.graphical_console:RedfishGraphicalConsole"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Removes all functionality related to shellinabox. This includes
|
||||
ipmitool-shellinabox console interface, and node web console support since
|
||||
it relied on shellinabox. Note that shellinabox has not been maintained
|
||||
in years and is considered abandoned.
|
||||
Reference in New Issue
Block a user