Merge "Implement drivers redfish-graphical, fake-graphical"
This commit is contained in:
commit
b973aeeb79
@ -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
|
||||
----------------
|
||||
|
@ -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):
|
||||
|
@ -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."""
|
||||
|
||||
|
94
ironic/drivers/modules/graphical_console.py
Normal file
94
ironic/drivers/modules/graphical_console.py
Normal 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
|
73
ironic/drivers/modules/redfish/graphical_console.py
Normal file
73
ironic/drivers/modules/redfish/graphical_console.py
Normal 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)
|
@ -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]
|
||||
|
83
ironic/tests/unit/drivers/modules/test_graphical_console.py
Normal file
83
ironic/tests/unit/drivers/modules/test_graphical_console.py
Normal 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)
|
@ -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``.
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user