Merge "Add serial console feature to seamicro driver"

This commit is contained in:
Jenkins 2014-11-21 03:08:27 +00:00 committed by Gerrit Code Review
commit cb4bc1e54a
5 changed files with 210 additions and 4 deletions

View File

@ -109,6 +109,7 @@ class FakeSeaMicroDriver(base.BaseDriver):
self.deploy = fake.FakeDeploy()
self.management = seamicro.Management()
self.vendor = seamicro.VendorPassthru()
self.console = seamicro.ShellinaboxConsole()
class FakeAgentDriver(base.BaseDriver):

View File

@ -18,9 +18,12 @@ python-seamicroclient.
Provides vendor passthru methods for SeaMicro specific functionality.
"""
import os
import re
from oslo.config import cfg
from oslo.utils import importutils
from six.moves.urllib import parse as urlparse
from ironic.common import boot_devices
from ironic.common import exception
@ -30,6 +33,7 @@ from ironic.common.i18n import _LW
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules import console_utils
from ironic.openstack.common import log as logging
from ironic.openstack.common import loopingcall
@ -72,6 +76,11 @@ OPTIONAL_PROPERTIES = {
}
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
CONSOLE_PROPERTIES = {
'seamicro_terminal_port': _("node's UDP port to connect to. "
"Only required for console access.")
}
PORT_BASE = 2000
def _get_client(*args, **kwargs):
@ -99,6 +108,7 @@ def _parse_driver_info(node):
:param node: An Ironic node object.
:returns: SeaMicro driver info.
:raises: MissingParameterValue if any required parameters are missing.
:raises: InvalidParameterValue if required parameter are invalid.
"""
info = node.driver_info or {}
@ -113,13 +123,35 @@ def _parse_driver_info(node):
password = info.get('seamicro_password')
server_id = info.get('seamicro_server_id')
api_version = info.get('seamicro_api_version', "2")
port = info.get('seamicro_terminal_port')
if port:
try:
port = int(port)
except ValueError:
raise exception.InvalidParameterValue(_(
"SeaMicro terminal port is not an integer."))
r = re.compile(r"(^[0-9]+)/([0-9]+$)")
if not r.match(server_id):
raise exception.InvalidParameterValue(_(
"Invalid 'seamicro_server_id' parameter in node's "
"driver_info. Expected format of 'seamicro_server_id' "
"is <int>/<int>"))
url = urlparse.urlparse(api_endpoint)
if (not (url.scheme == "http") or not url.netloc):
raise exception.InvalidParameterValue(_(
"Invalid 'seamicro_api_endpoint' parameter in node's "
"driver_info."))
res = {'username': username,
'password': password,
'api_endpoint': api_endpoint,
'server_id': server_id,
'api_version': api_version,
'uuid': node.uuid}
'uuid': node.uuid,
'port': port}
return res
@ -329,6 +361,12 @@ def _create_volume(driver_info, volume_size):
least_used_pool)
def get_telnet_port(driver_info):
"""Get SeaMicro telnet port to listen."""
server_id = int(driver_info['server_id'].split("/")[0])
return PORT_BASE + (10 * server_id)
class Power(base.PowerInterface):
"""SeaMicro Power Interface.
@ -572,3 +610,77 @@ class Management(base.ManagementInterface):
"""
raise NotImplementedError()
class ShellinaboxConsole(base.ConsoleInterface):
"""A ConsoleInterface that uses telnet and shellinabox."""
def get_properties(self):
d = COMMON_PROPERTIES.copy()
d.update(CONSOLE_PROPERTIES)
return d
def validate(self, task):
"""Validate the Node console info.
:param task: a task from TaskManager.
:raises: MissingParameterValue if required seamicro parameters are
missing
:raises: InvalidParameterValue if required parameter are invalid.
"""
driver_info = _parse_driver_info(task.node)
if not driver_info['port']:
raise exception.MissingParameterValue(_(
"Missing 'seamicro_terminal_port' parameter in node's "
"driver_info"))
def start_console(self, task):
"""Start a remote console for the node.
:param task: a task from TaskManager
:raises: MissingParameterValue if required seamicro parameters are
missing
:raises: ConsoleError if the directory for the PID file cannot be
created
:raises: ConsoleSubprocessFailed when invoking the subprocess failed
:raises: InvalidParameterValue if required parameter are invalid.
"""
driver_info = _parse_driver_info(task.node)
telnet_port = get_telnet_port(driver_info)
chassis_ip = urlparse.urlparse(driver_info['api_endpoint']).netloc
seamicro_cmd = ("/:%(uid)s:%(gid)s:HOME:telnet %(chassis)s %(port)s"
% {'uid': os.getuid(),
'gid': os.getgid(),
'chassis': chassis_ip,
'port': telnet_port})
console_utils.start_shellinabox_console(driver_info['uuid'],
driver_info['port'],
seamicro_cmd)
def stop_console(self, task):
"""Stop the remote console session for the node.
:param task: a task from TaskManager
:raises: MissingParameterValue if required seamicro parameters are
missing
:raises: ConsoleError if unable to stop the console
:raises: InvalidParameterValue if required parameter are invalid.
"""
driver_info = _parse_driver_info(task.node)
console_utils.stop_shellinabox_console(driver_info['uuid'])
def get_console(self, task):
"""Get the type and connection information about the console.
:raises: MissingParameterValue if required seamicro parameters are
missing
:raises: InvalidParameterValue if required parameter are invalid.
"""
driver_info = _parse_driver_info(task.node)
url = console_utils.get_shellinabox_console_url(driver_info['port'])
return {'type': 'shellinabox', 'url': url}

View File

@ -118,6 +118,7 @@ class PXEAndSeaMicroDriver(base.BaseDriver):
'attach_volume': self.seamicro_vendor,
'set_node_vlan_id': self.seamicro_vendor}
self.vendor = utils.MixinVendorInterface(self.mapping)
self.console = seamicro.ShellinaboxConsole()
class PXEAndIBootDriver(base.BaseDriver):

View File

@ -2416,7 +2416,7 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
def test_driver_properties_fake_seamicro(self):
expected = ['seamicro_api_endpoint', 'seamicro_password',
'seamicro_server_id', 'seamicro_username',
'seamicro_api_version']
'seamicro_api_version', 'seamicro_terminal_port']
self._check_driver_properties("fake_seamicro", expected)
def test_driver_properties_fake_snmp(self):
@ -2451,7 +2451,7 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
expected = ['pxe_deploy_kernel', 'pxe_deploy_ramdisk',
'seamicro_api_endpoint', 'seamicro_password',
'seamicro_server_id', 'seamicro_username',
'seamicro_api_version']
'seamicro_api_version', 'seamicro_terminal_port']
self._check_driver_properties("pxe_seamicro", expected)
def test_driver_properties_pxe_snmp(self):

View File

@ -24,6 +24,7 @@ from ironic.common import exception
from ironic.common import states
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.drivers.modules import console_utils
from ironic.drivers.modules import seamicro
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import base as db_base
@ -291,7 +292,14 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
expected = seamicro.COMMON_PROPERTIES
with task_manager.acquire(self.context, self.node['uuid'],
shared=True) as task:
self.assertEqual(expected, task.driver.get_properties())
self.assertEqual(expected, task.driver.power.get_properties())
expected = (list(seamicro.COMMON_PROPERTIES) +
list(seamicro.CONSOLE_PROPERTIES))
console_properties = task.driver.console.get_properties().keys()
self.assertEqual(sorted(expected), sorted(console_properties))
self.assertEqual(sorted(expected),
sorted(task.driver.get_properties().keys()))
def test_vendor_routes(self):
expected = ['set_node_vlan_id', 'attach_volume']
@ -601,3 +609,87 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, node.uuid) as task:
self.assertRaises(exception.MissingParameterValue,
task.driver.management.validate, task)
class SeaMicroDriverTestCase(db_base.DbTestCase):
def setUp(self):
super(SeaMicroDriverTestCase, self).setUp()
mgr_utils.mock_the_extension_manager(driver='fake_seamicro')
self.driver = driver_factory.get_driver('fake_seamicro')
self.node = obj_utils.create_test_node(self.context,
driver='fake_seamicro',
driver_info=INFO_DICT)
self.get_server_patcher = mock.patch.object(seamicro, '_get_server')
self.get_server_mock = None
self.Server = Fake_Server
self.Volume = Fake_Volume
self.info = seamicro._parse_driver_info(self.node)
@mock.patch.object(console_utils, 'start_shellinabox_console')
def test_start_console(self, mock_exec):
mock_exec.return_value = None
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.driver.console.start_console(task)
mock_exec.assert_called_once_with(self.info['uuid'],
self.info['port'],
mock.ANY)
@mock.patch.object(console_utils, 'start_shellinabox_console')
def test_start_console_fail(self, mock_exec):
mock_exec.side_effect = exception.ConsoleSubprocessFailed(
error='error')
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.assertRaises(exception.ConsoleSubprocessFailed,
self.driver.console.start_console,
task)
@mock.patch.object(console_utils, 'stop_shellinabox_console')
def test_stop_console(self, mock_exec):
mock_exec.return_value = None
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.driver.console.stop_console(task)
mock_exec.assert_called_once_with(self.info['uuid'])
@mock.patch.object(console_utils, 'stop_shellinabox_console')
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.driver.console.stop_console,
task)
mock_stop.assert_called_once_with(self.node.uuid)
@mock.patch.object(console_utils, 'start_shellinabox_console')
def test_start_console_fail_nodir(self, mock_exec):
mock_exec.side_effect = exception.ConsoleError()
with task_manager.acquire(self.context,
self.node.uuid) as task:
self.assertRaises(exception.ConsoleError,
self.driver.console.start_console,
task)
mock_exec.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY)
@mock.patch.object(console_utils, 'get_shellinabox_console_url')
def test_get_console(self, mock_exec):
url = 'http://localhost:4201'
mock_exec.return_value = url
expected = {'type': 'shellinabox', 'url': url}
with task_manager.acquire(self.context,
self.node.uuid) as task:
console_info = self.driver.console.get_console(task)
self.assertEqual(expected, console_info)
mock_exec.assert_called_once_with(self.info['port'])