Merge "Implement drivers redfish-graphical, fake-graphical"

This commit is contained in:
Zuul 2025-03-05 04:22:12 +00:00 committed by Gerrit Code Review
commit b973aeeb79
9 changed files with 344 additions and 7 deletions

View File

@ -1,15 +1,55 @@
.. _console:
=================================
Configuring Web or Serial Console
=================================
====================
Configuring Consoles
====================
Overview
--------
There are two types of consoles which are available in Bare Metal service,
one is web console (`Node web console`_) which is available directly from web
browser, and another is serial console (`Node serial console`_).
There are three 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
----------------------
Graphical console drivers require a configured and running ``ironic-novncproxy``
service. Each supported driver is described below.
redfish-graphical
~~~~~~~~~~~~~~~~~
A driver for a subset of Redfish hosts. Starting the console will start a
container which exposes a VNC server for ``ironic-novncproxy`` to attach to.
When attached, a browser will start which displays an HTML5 based console on
the following supported hosts:
* Dell iDRAC
* HPE iLO
* Supermicro
.. code-block:: ini
[DEFAULT]
enabled_hardware_types = redfish
enabled_console_interfaces = redfish-graphical,no-console
fake-graphical
~~~~~~~~~~~~~~~~~
A driver for demonstrating working graphical console infrastructure. Starting
the console will start a container which exposes a VNC server for
``ironic-novncproxy`` to attach to. When attached, a browser will start which
displays an animation.
.. code-block:: ini
[DEFAULT]
enabled_hardware_types = fake-hardware
enabled_console_interfaces = fake-graphical,no-console
Node web console
----------------

View File

@ -43,7 +43,9 @@ class FakeHardware(generic.GenericHardware):
@property
def supported_console_interfaces(self):
"""List of classes of supported console interfaces."""
return [fake.FakeConsole] + super().supported_console_interfaces
return [
fake.FakeConsole, fake.FakeGraphicalConsole
] + super().supported_console_interfaces
@property
def supported_deploy_interfaces(self):

View File

@ -35,8 +35,10 @@ from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import indicator_states
from ironic.common import states
from ironic.conductor import periodics
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import graphical_console
from ironic import objects
@ -259,6 +261,33 @@ class FakeConsole(base.ConsoleInterface):
return {}
class FakeGraphicalConsole(graphical_console.GraphicalConsole):
"""Console which displays the console container 'fake' app."""
def get_properties(self):
return {}
def validate(self, task):
pass
def get_app_name(self):
return 'fake'
@periodics.node_periodic(
purpose='checking active console sessions',
spacing=CONF.vnc.expire_console_session_interval,
filters={'console_enabled': True},
predicate_extra_fields=['console_interface', 'driver_internal_info'],
predicate=lambda n: (
'fake-graphical' == n.console_interface
and n.driver_internal_info.get('novnc_secret_token')
),
)
def _expire_fake_console_sessions(self, task, manager, context):
"""Periodic task to close expired console sessions"""
self._expire_console_sessions(task)
class FakeManagement(base.ManagementInterface):
"""Example implementation of a simple management interface."""

View File

@ -0,0 +1,94 @@
#
# 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.
import abc
from oslo_log import log as logging
from oslo_utils import timeutils
from ironic.common import console_factory
from ironic.common import metrics_utils
from ironic.common import vnc as vnc_utils
from ironic.drivers import base
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
class GraphicalConsole(base.ConsoleInterface):
@abc.abstractmethod
def get_app_name(self):
"""Get the name of the app passed to the console container.
A single console container image is expected to support all known
graphical consoles and each implementation is referred to as an app.
Each graphical console driver will specify what app to load in the
container.
:returns: String representing the app name to load in the console
container
"""
def get_app_info(self, task):
"""Information required by the app to connect to the console
:returns: dict containing app-specific values
"""
return {}
@METRICS.timer('GraphicalConsole.start_console')
def start_console(self, task):
node = task.node
provider = console_factory.ConsoleContainerFactory().provider
host, port = provider.start_container(
task, self.get_app_name(), self.get_app_info(task))
node.set_driver_internal_info('vnc_host', host)
node.set_driver_internal_info('vnc_port', port)
node.console_enabled = True
vnc_utils.novnc_authorize(node)
node.save()
@METRICS.timer('GraphicalConsole.stop_console')
def stop_console(self, task):
node = task.node
provider = console_factory.ConsoleContainerFactory().provider
provider.stop_container(task)
node.del_driver_internal_info('vnc_port')
node.del_driver_internal_info('vnc_host')
node.console_enabled = False
vnc_utils.novnc_unauthorize(node)
def get_console(self, task):
"""Get the type and connection information about the console."""
return vnc_utils.get_console(task.node)
def _expire_console_sessions(self, task):
"""Expire the session if it is no longer valid.
This is called by a periodic task.
:returns: True if the console was stopped
"""
node = task.node
LOG.debug('Checking graphical console session for node: %s', node.uuid)
if vnc_utils.token_valid_until(node) < timeutils.utcnow():
LOG.info('Graphical console session has expired for %s, closing.',
node.uuid)
self.stop_console(task)
return True

View File

@ -0,0 +1,73 @@
#
# 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.
from oslo_log import log as logging
from ironic.common import metrics_utils
from ironic.conductor import periodics
from ironic import conf
from ironic.drivers.modules import graphical_console
from ironic.drivers.modules.redfish import utils as redfish_utils
LOG = logging.getLogger(__name__)
CONF = conf.CONF
METRICS = metrics_utils.get_metrics_logger(__name__)
class RedfishGraphicalConsole(graphical_console.GraphicalConsole):
def get_app_name(self):
return 'redfish-graphical'
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return redfish_utils.COMMON_PROPERTIES.copy()
def validate(self, task):
"""Validates the driver information needed by the redfish driver.
:param task: a TaskManager instance containing the node to act on.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
"""
node = task.node
redfish_utils.parse_driver_info(node)
def get_app_info(self, task):
"""Information required by the app to connect to the console
For redfish based consoles the app info will be the parsed driver
info.
:returns: dict containing parsed driver info
"""
return redfish_utils.parse_driver_info(task.node)
@METRICS.timer('RedfishGraphicalConsole._expire_console_sessions')
@periodics.node_periodic(
purpose='checking active console sessions',
spacing=CONF.vnc.expire_console_session_interval,
filters={'console_enabled': True},
predicate_extra_fields=['console_interface', 'driver_internal_info'],
predicate=lambda n: (
'redfish-graphical' == n.console_interface
and n.driver_internal_info.get('novnc_secret_token')
),
)
def _expire_redfish_console_sessions(self, task, manager, context):
"""Periodic task to close expired console sessions"""
self._expire_console_sessions(task)

View File

@ -20,6 +20,7 @@ from ironic.drivers.modules import noop_mgmt
from ironic.drivers.modules.redfish import bios as redfish_bios
from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import firmware as redfish_firmware
from ironic.drivers.modules.redfish import graphical_console
from ironic.drivers.modules.redfish import inspect as redfish_inspect
from ironic.drivers.modules.redfish import management as redfish_mgmt
from ironic.drivers.modules.redfish import power as redfish_power
@ -73,3 +74,8 @@ class RedfishHardware(generic.GenericHardware):
@property
def supported_firmware_interfaces(self):
return [redfish_firmware.RedfishFirmware, noop.NoFirmware]
@property
def supported_console_interfaces(self):
"""List of supported console interfaces."""
return [graphical_console.RedfishGraphicalConsole, noop.NoConsole]

View File

@ -0,0 +1,83 @@
#
# 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.
import datetime
from unittest import mock
from oslo_config import cfg
from oslo_utils import timeutils
from ironic.drivers.modules import fake
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
CONF = cfg.CONF
class TestGraphicalConsole(db_base.DbTestCase):
def setUp(self):
super(TestGraphicalConsole, self).setUp()
self.node = obj_utils.create_test_node(
self.context, driver='fake-hardware')
self.task = mock.Mock(node=self.node)
self.console = fake.FakeGraphicalConsole()
def test_start_console(self):
self.assertFalse(self.node.console_enabled)
self.console.start_console(self.task)
dii = self.node.driver_internal_info
# assert start_console has set internal info
self.assertEqual('192.0.2.1', dii['vnc_host'])
self.assertEqual(5900, dii['vnc_port'])
self.assertIn('novnc_secret_token', dii)
self.assertIn('novnc_secret_token_created', dii)
self.assertTrue(self.node.console_enabled)
def test_stop_console(self):
self.console.start_console(self.task)
dii = self.node.driver_internal_info
self.assertIn('vnc_host', dii)
self.assertIn('vnc_port', dii)
self.assertIn('novnc_secret_token', dii)
self.assertIn('novnc_secret_token_created', dii)
self.assertTrue(self.node.console_enabled)
# assert stop_console has cleared internal info
self.console.stop_console(self.task)
self.assertNotIn('vnc_host', dii)
self.assertNotIn('vnc_port', dii)
self.assertNotIn('novnc_secret_token', dii)
self.assertNotIn('novnc_secret_token_created', dii)
self.assertFalse(self.node.console_enabled)
def test__expire_console_sessions(self):
self.console.start_console(self.task)
dii = self.node.driver_internal_info
# assert active session
self.assertFalse(self.console._expire_console_sessions(self.task))
self.assertTrue(self.node.console_enabled)
timeout = CONF.vnc.token_timeout + 10
time_delta = datetime.timedelta(seconds=timeout)
created_time_in_past = timeutils.utcnow() - time_delta
self.node.set_driver_internal_info('novnc_secret_token_created',
created_time_in_past.isoformat())
# assert expired, console is closed
self.assertTrue(self.console._expire_console_sessions(self.task))
self.assertNotIn('novnc_secret_token', dii)
self.assertFalse(self.node.console_enabled)

View File

@ -0,0 +1,8 @@
---
features:
- |
New ``console`` drivers ``redfish-graphical`` and ``fake-graphical`` have
been added. This allows the graphical console to be accessed for Dell
iDRAC, HPE iLO, and Supermicro hosts. The ``fake-graphical`` driver is
useful for demonstrating the full integration of ``ironic-novncproxy`` and
the ``systemd`` provider of ``ironic.console.container``.

View File

@ -87,10 +87,12 @@ ironic.hardware.interfaces.boot =
ironic.hardware.interfaces.console =
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
ironic.hardware.interfaces.deploy =
anaconda = ironic.drivers.modules.pxe:PXEAnacondaDeploy