Merge "Add ssh power manager."

This commit is contained in:
Jenkins 2013-06-05 15:58:05 +00:00 committed by Gerrit Code Review
commit 89ea41ef51
6 changed files with 973 additions and 35 deletions

View File

@ -256,3 +256,7 @@ class ExclusiveLockRequired(NotAuthorized):
class IPMIFailure(IronicException):
message = _("IPMI command failed: %(cmd)s.")
class SSHConnectFailed(IronicException):
message = _("Failed to establish SSH connection to host %(host)s.")

View File

@ -24,6 +24,7 @@ import contextlib
import errno
import hashlib
import os
import paramiko
import random
import re
import shutil
@ -195,42 +196,30 @@ def trycmd(*args, **kwargs):
return out, err
def ssh_execute(ssh, cmd, process_input=None,
addl_env=None, check_exit_code=True):
LOG.debug(_('Running cmd (SSH): %s'), cmd)
if addl_env:
raise exception.IronicException(_(
'Environment not supported over SSH'))
def ssh_connect(connection):
"""Method to connect to a remote system using ssh protocol.
if process_input:
# This is (probably) fixable if we need it...
msg = _('process_input not supported over SSH')
raise exception.IronicException(msg)
:param connection: a dict of connection parameters.
:returns: paramiko.SSHClient -- an active ssh connection.
:raises: SSHConnectFailed
stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)
channel = stdout_stream.channel
"""
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(connection.get('host'),
username=connection.get('username'),
password=connection.get('password', None),
port=connection.get('port', 22),
key_filename=connection.get('key_filename', None),
timeout=connection.get('timeout', 10))
#stdin.write('process_input would go here')
#stdin.flush()
# send TCP keepalive packets every 20 seconds
ssh.get_transport().set_keepalive(20)
except Exception:
raise exception.SSHConnectFailed(host=connection.get('host'))
# NOTE(justinsb): This seems suspicious...
# ...other SSH clients have buffering issues with this approach
stdout = stdout_stream.read()
stderr = stderr_stream.read()
stdin_stream.close()
exit_status = channel.recv_exit_status()
# exit_status == -1 if no exit code was returned
if exit_status != -1:
LOG.debug(_('Result was %s') % exit_status)
if check_exit_code and exit_status != 0:
raise exception.ProcessExecutionError(exit_code=exit_status,
stdout=stdout,
stderr=stderr,
cmd=cmd)
return (stdout, stderr)
return ssh
def generate_uid(topic, size=8):

382
ironic/drivers/ssh.py Normal file
View File

@ -0,0 +1,382 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Ironic SSH power manager.
Provides basic power control of virtual machines via SSH.
For use in dev and test environments.
Currently supported environments are:
Virtual Box (vbox)
Virsh (virsh)
"""
import os
from oslo.config import cfg
from ironic.common import exception
from ironic.common import states
from ironic.common import utils
from ironic.drivers import base
from ironic.manager import task_manager
from ironic.openstack.common import jsonutils as json
from ironic.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
COMMAND_SETS = {
'vbox': {
'base_cmd': '/usr/bin/VBoxManage',
'start_cmd': 'startvm {_NodeName_}',
'stop_cmd': 'controlvm {_NodeName_} poweroff',
'reboot_cmd': 'controlvm {_NodeName_} reset',
'list_all': "list vms|awk -F'\"' '{print $2}'",
'list_running': 'list runningvms',
'get_node_macs': ("showvminfo --machinereadable {_NodeName_} | "
"grep "
'"macaddress" | awk -F '
"'"
'"'
"' '{print $2}'")
},
"virsh": {
'base_cmd': '/usr/bin/virsh',
'start_cmd': 'start {_NodeName_}',
'stop_cmd': 'destroy {_NodeName_}',
'reboot_cmd': 'reset {_NodeName_}',
'list_all': "list --all | tail -n +2 | awk -F\" \" '{print $2}'",
'list_running':
"list --all|grep running|awk -v qc='\"' -F\" \" '{print qc$2qc}'",
'get_node_macs': ("dumpxml {_NodeName_} | grep "
'"mac address" | awk -F'
'"'
"'"
'" '
"'{print $2}' | tr -d ':'")
}
}
def _normalize_mac(mac):
return mac.translate(None, '-:').lower()
def _exec_ssh_command(ssh_obj, command):
"""Execute a SSH command on the host."""
LOG.debug(_('Running cmd (SSH): %s'), command)
stdin_stream, stdout_stream, stderr_stream = ssh_obj.exec_command(command)
channel = stdout_stream.channel
# NOTE(justinsb): This seems suspicious...
# ...other SSH clients have buffering issues with this approach
stdout = stdout_stream.read()
stderr = stderr_stream.read()
stdin_stream.close()
exit_status = channel.recv_exit_status()
# exit_status == -1 if no exit code was returned
if exit_status != -1:
LOG.debug(_('Result was %s') % exit_status)
if exit_status != 0:
raise exception.ProcessExecutionError(exit_code=exit_status,
stdout=stdout,
stderr=stderr,
cmd=command)
return (stdout, stderr)
def _parse_control_info(node):
info = json.loads(node.get('control_info', ''))
host = info.get('ssh_host', None)
username = info.get('ssh_user', None)
password = info.get('ssh_pass', None)
port = info.get('ssh_port', 22)
key_filename = info.get('ssh_key', None)
virt_type = info.get('virt_type', None)
res = {
'host': host,
'username': username,
'port': port,
'virt_type': virt_type,
'uuid': node.get('uuid')
}
if not virt_type:
raise exception.InvalidParameterValue(_(
"SSHPowerDriver requires virt_type be set."))
cmd_set = COMMAND_SETS.get(virt_type, None)
if not cmd_set:
raise exception.InvalidParameterValue(_(
"SSHPowerDriver unknown virt_type (%s).") % cmd_set)
res['cmd_set'] = cmd_set
if not host or not username:
raise exception.InvalidParameterValue(_(
"SSHPowerDriver requires both ssh_host and ssh_user be set."))
if password:
res['password'] = password
else:
if not key_filename:
raise exception.InvalidParameterValue(_(
"SSHPowerDriver requires either ssh_pass or ssh_key be set."))
if not os.path.isfile(key_filename):
raise exception.FileNotFound(file_path=key_filename)
res['key_filename'] = key_filename
return res
def _get_power_status(ssh_obj, c_info):
"""Returns a node's current power state."""
power_state = None
cmd_to_exec = c_info['cmd_set']['list_running']
running_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
# Command should return a list of running vms. If the current node is
# not listed then we can assume it is not powered on.
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
if node_name:
for node in running_list:
if node_name in node:
power_state = states.POWER_ON
break
if not power_state:
power_state = states.POWER_OFF
else:
power_state = states.ERROR
return power_state
def _get_connection(node):
return utils.ssh_connect(_parse_control_info(node))
def _get_hosts_name_for_node(ssh_obj, c_info):
"""Get the name the host uses to reference the node."""
matched_name = None
cmd_to_exec = c_info['cmd_set']['list_all']
full_node_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
# for each node check Mac Addresses
for node in full_node_list:
cmd_to_exec = c_info['cmd_set']['get_node_macs']
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', node)
hosts_node_mac_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
for host_mac in hosts_node_mac_list:
for node_mac in c_info['macs']:
if _normalize_mac(host_mac) in _normalize_mac(node_mac):
matched_name = node
break
if matched_name:
break
if matched_name:
break
return matched_name
def _power_on(ssh_obj, c_info):
"""Power ON this node."""
current_pstate = _get_power_status(ssh_obj, c_info)
if current_pstate == states.POWER_ON:
_power_off(ssh_obj, c_info)
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
cmd_to_power_on = c_info['cmd_set']['start_cmd']
cmd_to_power_on = cmd_to_power_on.replace('{_NodeName_}', node_name)
_exec_ssh_command(ssh_obj, cmd_to_power_on)
current_pstate = _get_power_status(ssh_obj, c_info)
if current_pstate == states.POWER_ON:
return current_pstate
else:
return states.ERROR
def _power_off(ssh_obj, c_info):
"""Power OFF this node."""
current_pstate = _get_power_status(ssh_obj, c_info)
if current_pstate == states.POWER_OFF:
return current_pstate
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
cmd_to_power_off = c_info['cmd_set']['stop_cmd']
cmd_to_power_off = cmd_to_power_off.replace('{_NodeName_}', node_name)
_exec_ssh_command(ssh_obj, cmd_to_power_off)
current_pstate = _get_power_status(ssh_obj, c_info)
if current_pstate == states.POWER_OFF:
return current_pstate
else:
return states.ERROR
def _get_nodes_mac_addresses(task, node):
"""Get all mac addresses for a node."""
interface_ports = task.dbapi.get_ports_by_node(node.get('id'))
macs = [p.address for p in interface_ports]
return macs
class SSHPowerDriver(base.ControlDriver):
"""SSH Power Driver.
This ControlDriver class provides a mechanism for controlling the power
state of virtual machines via SSH.
NOTE: This driver supports VirtualBox and Virsh commands.
NOTE: This driver does not currently support multi-node operations.
"""
def __init__(self):
pass
def validate_driver_info(self, node):
"""Check that node['control_info'] contains the requisite fields.
:param node: Single node object.
:returns: True / False.
"""
try:
_parse_control_info(node)
except exception.InvalidParameterValue:
return False
return True
def get_power_state(self, task, node):
"""Get the current power state.
Poll the host for the current power state of the node.
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
:param node: A single node.
:returns: power state. One of :class:`ironic.common.states`.
"""
c_info = _parse_control_info(node)
c_info['macs'] = _get_nodes_mac_addresses(task, node)
ssh_obj = _get_connection(node)
return _get_power_status(ssh_obj, c_info)
@task_manager.require_exclusive_lock
def set_power_state(self, task, node, pstate):
"""Turn the power on or off.
Set the power state of a node.
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
:param node: A single node.
:param pstate: Either POWER_ON or POWER_OFF from :class:
`ironic.common.states`.
:returns NOTHING:
Can raise exception.IronicException or exception.PowerStateFailure.
"""
c_info = _parse_control_info(node)
c_info['macs'] = _get_nodes_mac_addresses(task, node)
ssh_obj = _get_connection(node)
if pstate == states.POWER_ON:
state = _power_on(ssh_obj, c_info)
elif pstate == states.POWER_OFF:
state = _power_off(ssh_obj, c_info)
else:
raise exception.IronicException(_(
"set_power_state called with invalid power state."))
if state != pstate:
raise exception.PowerStateFailure(pstate=pstate)
@task_manager.require_exclusive_lock
def reboot(self, task, node):
"""Cycles the power to a node.
Power cycles a node.
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
:param node: A single node.
:returns NOTHING:
Can raise exception.PowerStateFailure.
"""
c_info = _parse_control_info(node)
c_info['macs'] = _get_nodes_mac_addresses(task, node)
ssh_obj = _get_connection(node)
current_pstate = _get_power_status(ssh_obj, c_info)
if current_pstate == states.POWER_ON:
_power_off(ssh_obj, c_info)
state = _power_on(ssh_obj, c_info)
if state != states.POWER_ON:
raise exception.PowerStateFailure(pstate=states.POWER_ON)
def start_console(self, task, node):
"""Starts a console connection to a node.
CURRENTLY NOT SUPPORTED.
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
:param node: A single node.
Will raise raise exception.IronicException.
"""
raise exception.IronicException(_(
"start_console is not supported by SSHPowerDriver."))
def stop_console(self, task, node):
"""Stops a console connection to a node.
CURRENTLY NOT SUPPORTED.
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
:param node: A single node.
Will raise raise exception.IronicException.
"""
raise exception.IronicException(_(
"stop_console is not supported by SSHPowerDriver."))

View File

@ -20,13 +20,22 @@ from ironic.db.sqlalchemy import models
from ironic.openstack.common import jsonutils as json
_control_info = json.dumps(
_ipmi_control_info = json.dumps(
{
"ipmi_address": "1.2.3.4",
"ipmi_username": "admin",
"ipmi_password": "fake",
})
_ssh_control_info = json.dumps(
{
"ssh_host": "1.2.3.4",
"ssh_user": "admin",
"ssh_pass": "fake",
"ssh_port": 22,
"virt_type": "vbox",
})
_deploy_info = json.dumps(
{
"image_path": "/path/to/image.qcow2",
@ -53,7 +62,12 @@ def get_test_node(**kw):
'8227348d-5f1d-4488-aad1-7c92b2d42504')
node.control_driver = kw.get('control_driver', 'ipmi')
node.control_info = kw.get('control_info', _control_info)
node.control_info = kw.get('control_info', None)
if node.control_driver == 'ipmi' and not node.control_info:
node.control_info = _ipmi_control_info
elif node.control_driver == 'ssh' and not node.control_info:
node.control_info = _ssh_control_info
node.deploy_driver = kw.get('deploy_driver', 'pxe')
node.deploy_info = kw.get('deploy_info', _deploy_info)

View File

@ -0,0 +1,549 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Test class for Ironic SSH power driver."""
import mox
import paramiko
from oslo.config import cfg
from ironic.openstack.common import jsonutils as json
from ironic.common import exception
from ironic.common import states
from ironic.db import api as dbapi
from ironic.drivers import ssh
from ironic.manager import task_manager
from ironic.tests import base
from ironic.tests.db import utils as db_utils
from ironic.tests.manager import utils as mgr_utils
CONF = cfg.CONF
class IronicSSHTestCase(base.TestCase):
def setUp(self):
super(IronicSSHTestCase, self).setUp()
(self.controller, self.deployer) = mgr_utils.get_mocked_node_manager(
control_driver="ssh")
self.node = db_utils.get_test_node(**{'control_driver': 'ssh',
'deploy_driver': 'fake'})
self.dbapi = dbapi.get_instance()
self.dbapi.create_node(self.node)
self.ifports = [db_utils.get_test_port(id=6, address='aa:bb:cc'),
db_utils.get_test_port(id=7, address='dd:ee:ff')]
self.dbapi.create_port(self.ifports[0])
self.dbapi.create_port(self.ifports[1])
self.ssh = ssh.SSHPowerDriver()
self.test_control_info_dict = {
"ssh_host": "1.2.3.4",
"ssh_user": "admin",
"ssh_pass": "fake",
"ssh_port": 22,
"virt_type": "vbox",
"ssh_key": "/not/real/file"}
def test__parse_control_info_good(self):
# make sure we get back the expected things
info = ssh._parse_control_info(self.node)
self.assertIsNotNone(info.get('host'))
self.assertIsNotNone(info.get('username'))
self.assertIsNotNone(info.get('password'))
self.assertIsNotNone(info.get('port'))
self.assertIsNotNone(info.get('virt_type'))
self.assertIsNotNone(info.get('cmd_set'))
self.assertIsNotNone(info.get('uuid'))
self.mox.VerifyAll()
def test__parse_control_info_missing_host(self):
# make sure error is raised when info is missing
tmp_dict = self.test_control_info_dict
del tmp_dict['ssh_host']
del tmp_dict['ssh_key']
_ssh_control_info = json.dumps(tmp_dict)
node = db_utils.get_test_node(control_info=_ssh_control_info)
self.assertRaises(exception.InvalidParameterValue,
ssh._parse_control_info,
node)
self.mox.VerifyAll()
def test__parse_control_info_missing_user(self):
# make sure error is raised when info is missing
tmp_dict = self.test_control_info_dict
del tmp_dict['ssh_user']
del tmp_dict['ssh_key']
_ssh_control_info = json.dumps(tmp_dict)
node = db_utils.get_test_node(control_info=_ssh_control_info)
self.assertRaises(exception.InvalidParameterValue,
ssh._parse_control_info,
node)
self.mox.VerifyAll()
def test__parse_control_info_missing_pass(self):
# make sure error is raised when info is missing
tmp_dict = self.test_control_info_dict
del tmp_dict['ssh_pass']
del tmp_dict['ssh_key']
_ssh_control_info = json.dumps(tmp_dict)
node = db_utils.get_test_node(control_info=_ssh_control_info)
self.assertRaises(exception.InvalidParameterValue,
ssh._parse_control_info,
node)
self.mox.VerifyAll()
def test__parse_control_info_missing_virt_type(self):
# make sure error is raised when info is missing
tmp_dict = self.test_control_info_dict
del tmp_dict['virt_type']
del tmp_dict['ssh_key']
_ssh_control_info = json.dumps(tmp_dict)
node = db_utils.get_test_node(control_info=_ssh_control_info)
self.assertRaises(exception.InvalidParameterValue,
ssh._parse_control_info,
node)
self.mox.VerifyAll()
def test__parse_control_info_missing_key(self):
# make sure error is raised when info is missing
tmp_dict = self.test_control_info_dict
del tmp_dict['ssh_pass']
_ssh_control_info = json.dumps(tmp_dict)
node = db_utils.get_test_node(control_info=_ssh_control_info)
self.assertRaises(exception.FileNotFound,
ssh._parse_control_info,
node)
self.mox.VerifyAll()
def test__normalize_mac(self):
mac_raw = "0A:1B-2C-3D:4F"
mac_clean = ssh._normalize_mac(mac_raw)
self.assertEqual(mac_clean, "0a1b2c3d4f")
self.mox.VerifyAll()
def test__get_power_status_on(self):
info = ssh._parse_control_info(self.node)
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
ssh._exec_ssh_command(
ssh_obj, info['cmd_set']['list_running']).AndReturn(
['"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}'])
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
self.mox.ReplayAll()
pstate = ssh._get_power_status(ssh_obj, info)
self.assertEqual(pstate, states.POWER_ON)
self.mox.VerifyAll()
def test__get_power_status_off(self):
info = ssh._parse_control_info(self.node)
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
ssh._exec_ssh_command(
ssh_obj, info['cmd_set']['list_running']).AndReturn(
['"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}'])
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NotNodeName")
self.mox.ReplayAll()
pstate = ssh._get_power_status(ssh_obj, info)
self.assertEqual(pstate, states.POWER_OFF)
self.mox.VerifyAll()
def test__get_power_status_error(self):
info = ssh._parse_control_info(self.node)
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
ssh._exec_ssh_command(
ssh_obj, info['cmd_set']['list_running']).AndReturn(
['"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}'])
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn(None)
self.mox.ReplayAll()
pstate = ssh._get_power_status(ssh_obj, info)
self.assertEqual(pstate, states.ERROR)
self.mox.VerifyAll()
def test__get_hosts_name_for_node_match(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
ssh._exec_ssh_command(ssh_obj, info['cmd_set']['list_all']).AndReturn(
['NodeName'])
cmd_to_exec = info['cmd_set']['get_node_macs']
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(
['52:54:00:cf:2d:31'])
self.mox.ReplayAll()
found_name = ssh._get_hosts_name_for_node(ssh_obj, info)
self.assertEqual(found_name, 'NodeName')
self.mox.VerifyAll()
def test__get_hosts_name_for_node_no_match(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "22:22:22:22:22:22"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
ssh._exec_ssh_command(ssh_obj, info['cmd_set']['list_all']).AndReturn(
['NodeName'])
cmd_to_exec = info['cmd_set']['get_node_macs']
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(
['52:54:00:cf:2d:31'])
self.mox.ReplayAll()
found_name = ssh._get_hosts_name_for_node(ssh_obj, info)
self.assertEqual(found_name, None)
self.mox.VerifyAll()
def test__power_on_good(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_get_power_status')
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
cmd_to_exec = info['cmd_set']['start_cmd']
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
self.mox.ReplayAll()
current_state = ssh._power_on(ssh_obj, info)
self.assertEqual(current_state, states.POWER_ON)
self.mox.VerifyAll()
def test__power_on_fail(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_get_power_status')
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
cmd_to_exec = info['cmd_set']['start_cmd']
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
self.mox.ReplayAll()
current_state = ssh._power_on(ssh_obj, info)
self.assertEqual(current_state, states.ERROR)
self.mox.VerifyAll()
def test__power_off_good(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_get_power_status')
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
cmd_to_exec = info['cmd_set']['stop_cmd']
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
self.mox.ReplayAll()
current_state = ssh._power_off(ssh_obj, info)
self.assertEqual(current_state, states.POWER_OFF)
self.mox.VerifyAll()
def test__power_off_fail(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh, '_get_power_status')
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
cmd_to_exec = info['cmd_set']['stop_cmd']
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
self.mox.ReplayAll()
current_state = ssh._power_off(ssh_obj, info)
self.assertEqual(current_state, states.ERROR)
self.mox.VerifyAll()
def test_reboot_good(self):
ssh_obj = paramiko.SSHClient()
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
self.mox.StubOutWithMock(ssh, '_parse_control_info')
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
self.mox.StubOutWithMock(ssh, '_get_connection')
self.mox.StubOutWithMock(ssh, '_get_power_status')
self.mox.StubOutWithMock(ssh, '_power_off')
self.mox.StubOutWithMock(ssh, '_power_on')
ssh._parse_control_info(self.node).AndReturn(info)
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
info['macs'])
ssh._get_connection(self.node).AndReturn(ssh_obj)
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
ssh._power_off(ssh_obj, info).AndReturn(None)
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_ON)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']], shared=False) as task:
task.resources[0].controller.reboot(task, self.node)
self.mox.VerifyAll()
def test_reboot_fail(self):
ssh_obj = paramiko.SSHClient()
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
self.mox.StubOutWithMock(ssh, '_parse_control_info')
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
self.mox.StubOutWithMock(ssh, '_get_connection')
self.mox.StubOutWithMock(ssh, '_get_power_status')
self.mox.StubOutWithMock(ssh, '_power_off')
self.mox.StubOutWithMock(ssh, '_power_on')
ssh._parse_control_info(self.node).AndReturn(info)
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
info['macs'])
ssh._get_connection(self.node).AndReturn(ssh_obj)
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
ssh._power_off(ssh_obj, info).AndReturn(None)
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_OFF)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']], shared=False) as task:
self.assertRaises(exception.PowerStateFailure,
task.resources[0].controller.reboot,
task,
self.node)
self.mox.VerifyAll()
def test_exec_ssh_command_good(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh_obj, 'exec_command')
class Channel(object):
def recv_exit_status(self):
return 0
class Stream(object):
def __init__(self, buffer=''):
self.buffer = buffer
self.channel = Channel()
def read(self):
return self.buffer
def close(self):
pass
ssh_obj.exec_command("command").AndReturn(
(Stream(), Stream('hello'), Stream()))
self.mox.ReplayAll()
stdout, stderr = ssh._exec_ssh_command(ssh_obj, "command")
self.assertEqual(stdout, 'hello')
self.mox.VerifyAll()
def test_exec_ssh_command_fail(self):
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
ssh_obj = paramiko.SSHClient()
self.mox.StubOutWithMock(ssh_obj, 'exec_command')
class Channel(object):
def recv_exit_status(self):
return 127
class Stream(object):
def __init__(self, buffer=''):
self.buffer = buffer
self.channel = Channel()
def read(self):
return self.buffer
def close(self):
pass
ssh_obj.exec_command("command").AndReturn(
(Stream(), Stream('hello'), Stream()))
self.mox.ReplayAll()
self.assertRaises(exception.ProcessExecutionError,
ssh._exec_ssh_command,
ssh_obj,
"command")
self.mox.VerifyAll()
def test_start_console(self):
self.assertRaises(exception.IronicException,
self.ssh.start_console,
None,
self.node)
self.mox.VerifyAll()
def test_stop_console(self):
self.assertRaises(exception.IronicException,
self.ssh.start_console,
None,
self.node)
self.mox.VerifyAll()
def test_set_power_state_bad_state(self):
ssh_obj = paramiko.SSHClient()
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
self.mox.StubOutWithMock(ssh, '_parse_control_info')
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
self.mox.StubOutWithMock(ssh, '_get_connection')
ssh._parse_control_info(self.node).AndReturn(info)
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
info['macs'])
ssh._get_connection(self.node).AndReturn(ssh_obj)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']], shared=False) as task:
self.assertRaises(exception.IronicException,
task.resources[0].controller.set_power_state,
task,
self.node,
"BAD_PSTATE")
self.mox.VerifyAll()
def test_set_power_state_on_good(self):
ssh_obj = paramiko.SSHClient()
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
self.mox.StubOutWithMock(ssh, '_parse_control_info')
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
self.mox.StubOutWithMock(ssh, '_get_connection')
self.mox.StubOutWithMock(ssh, '_power_on')
ssh._parse_control_info(self.node).AndReturn(info)
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
info['macs'])
ssh._get_connection(self.node).AndReturn(ssh_obj)
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_ON)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']], shared=False) as task:
task.resources[0].controller.set_power_state(
task,
self.node,
states.POWER_ON)
self.assert_(True)
self.mox.VerifyAll()
def test_set_power_state_on_fail(self):
ssh_obj = paramiko.SSHClient()
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
self.mox.StubOutWithMock(ssh, '_parse_control_info')
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
self.mox.StubOutWithMock(ssh, '_get_connection')
self.mox.StubOutWithMock(ssh, '_power_on')
ssh._parse_control_info(self.node).AndReturn(info)
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
info['macs'])
ssh._get_connection(self.node).AndReturn(ssh_obj)
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_OFF)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']], shared=False) as task:
self.assertRaises(exception.PowerStateFailure,
task.resources[0].controller.set_power_state,
task,
self.node,
states.POWER_ON)
self.mox.VerifyAll()
def test_set_power_state_off_good(self):
ssh_obj = paramiko.SSHClient()
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
self.mox.StubOutWithMock(ssh, '_parse_control_info')
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
self.mox.StubOutWithMock(ssh, '_get_connection')
self.mox.StubOutWithMock(ssh, '_power_off')
ssh._parse_control_info(self.node).AndReturn(info)
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
info['macs'])
ssh._get_connection(self.node).AndReturn(ssh_obj)
ssh._power_off(ssh_obj, info).AndReturn(states.POWER_OFF)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']], shared=False) as task:
task.resources[0].controller.set_power_state(
task,
self.node,
states.POWER_OFF)
self.assert_(True)
self.mox.VerifyAll()
def test_set_power_state_off_fail(self):
ssh_obj = paramiko.SSHClient()
info = ssh._parse_control_info(self.node)
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
self.mox.StubOutWithMock(ssh, '_parse_control_info')
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
self.mox.StubOutWithMock(ssh, '_get_connection')
self.mox.StubOutWithMock(ssh, '_power_off')
ssh._parse_control_info(self.node).AndReturn(info)
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
info['macs'])
ssh._get_connection(self.node).AndReturn(ssh_obj)
ssh._power_off(ssh_obj, info).AndReturn(states.POWER_ON)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']], shared=False) as task:
self.assertRaises(exception.PowerStateFailure,
task.resources[0].controller.set_power_state,
task,
self.node,
states.POWER_OFF)
self.mox.VerifyAll()
def test__get_nodes_mac_addresses(self):
info = ssh._parse_control_info(self.node)
self.mox.StubOutWithMock(self.dbapi, 'get_ports_by_node')
self.dbapi.get_ports_by_node(self.node.get('uuid')).\
AndReturn(self.ifports)
self.dbapi.get_ports_by_node(self.node.get('id')).\
AndReturn(self.ifports)
self.mox.ReplayAll()
with task_manager.acquire([info['uuid']]) as task:
node_macs = ssh._get_nodes_mac_addresses(task, self.node)
self.assertEqual(node_macs, ['aa:bb:cc', 'dd:ee:ff'])
self.mox.VerifyAll()

View File

@ -35,7 +35,7 @@ console_scripts =
ironic.controllers =
fake = ironic.drivers.fake:FakeControlDriver
ipmi = ironic.drivers.ipmi:IPMIPowerDriver
# vpd = ironic.drivers.vpd:VirtualPowerDriver
ssh = ironic.drivers.ssh:SSHPowerDriver
ironic.deployers =
fake = ironic.drivers.fake:FakeDeployDriver