Remove SSH-based driver interfaces and drivers
SSH drivers are being unsupported for about a year now. All current stable branches of ironic are officially supporting IPMI-capable HW simulation via virtualbmc. All ironic-related gate jobs have already been switched to not use or enable those drivers. This patch finally removes SSH-based power and managemtnt driver interfaces and all classic drivers using those from ironic code and documentation. Related exceptions and `ssh_connect` function, together with dependency on `paramiko` package are removed as well. Change-Id: Ieda7249b9cd78e3be1eff37804996295fc8d3969 Closes-Bug: #1570301 Depends-On: I9b60c9fa24652e9e64e787cd4e5b0152f51e7a28
This commit is contained in:
parent
4ff2120f3b
commit
3d9895cc0b
@ -113,17 +113,8 @@ IRONIC_NODE_UUID=${IRONIC_NODE_UUID:-`uuidgen`}
|
||||
IRONIC_SCRIPTS_DIR=${IRONIC_SCRIPTS_DIR:-$IRONIC_DEVSTACK_DIR/tools/ironic/scripts}
|
||||
IRONIC_TEMPLATES_DIR=${IRONIC_TEMPLATES_DIR:-$IRONIC_DEVSTACK_DIR/tools/ironic/templates}
|
||||
IRONIC_BAREMETAL_BASIC_OPS=$(trueorfalse False IRONIC_BAREMETAL_BASIC_OPS)
|
||||
IRONIC_SSH_USERNAME=${IRONIC_SSH_USERNAME:-`whoami`}
|
||||
IRONIC_SSH_TIMEOUT=${IRONIC_SSH_TIMEOUT:-15}
|
||||
IRONIC_SSH_ATTEMPTS=${IRONIC_SSH_ATTEMPTS:-5}
|
||||
IRONIC_SSH_KEY_DIR=${IRONIC_SSH_KEY_DIR:-$IRONIC_DATA_DIR/ssh_keys}
|
||||
IRONIC_SSH_KEY_FILENAME=${IRONIC_SSH_KEY_FILENAME:-ironic_key}
|
||||
IRONIC_KEY_FILE=${IRONIC_KEY_FILE:-$IRONIC_SSH_KEY_DIR/$IRONIC_SSH_KEY_FILENAME}
|
||||
IRONIC_SSH_VIRT_TYPE=${IRONIC_SSH_VIRT_TYPE:-virsh}
|
||||
IRONIC_TFTPBOOT_DIR=${IRONIC_TFTPBOOT_DIR:-$IRONIC_DATA_DIR/tftpboot}
|
||||
IRONIC_TFTPSERVER_IP=${IRONIC_TFTPSERVER_IP:-$HOST_IP}
|
||||
IRONIC_VM_SSH_PORT=${IRONIC_VM_SSH_PORT:-22}
|
||||
IRONIC_VM_SSH_ADDRESS=${IRONIC_VM_SSH_ADDRESS:-$HOST_IP}
|
||||
IRONIC_VM_COUNT=${IRONIC_VM_COUNT:-1}
|
||||
IRONIC_VM_SPECS_CPU=${IRONIC_VM_SPECS_CPU:-1}
|
||||
IRONIC_VM_SPECS_RAM=${IRONIC_VM_SPECS_RAM:-1280}
|
||||
@ -137,7 +128,6 @@ IRONIC_VM_NETWORK_BRIDGE=${IRONIC_VM_NETWORK_BRIDGE:-brbm}
|
||||
IRONIC_VM_NETWORK_RANGE=${IRONIC_VM_NETWORK_RANGE:-192.0.2.0/24}
|
||||
IRONIC_VM_INTERFACE_COUNT=${IRONIC_VM_INTERFACE_COUNT:-2}
|
||||
IRONIC_VM_MACS_CSV_FILE=${IRONIC_VM_MACS_CSV_FILE:-$IRONIC_DATA_DIR/ironic_macs.csv}
|
||||
IRONIC_AUTHORIZED_KEYS_FILE=${IRONIC_AUTHORIZED_KEYS_FILE:-$HOME/.ssh/authorized_keys}
|
||||
IRONIC_CLEAN_NET_NAME=${IRONIC_CLEAN_NET_NAME:-${IRONIC_PROVISION_NETWORK_NAME:-${PRIVATE_NETWORK_NAME}}}
|
||||
IRONIC_EXTRA_PXE_PARAMS=${IRONIC_EXTRA_PXE_PARAMS:-}
|
||||
IRONIC_TTY_DEV=${IRONIC_TTY_DEV:-ttyS0}
|
||||
@ -263,8 +253,7 @@ if [[ "$IRONIC_DEPLOY_ISO_REQUIRED" = "True" \
|
||||
or set IRONIC_BUILD_DEPLOY_RAMDISK=True to use ISOs"
|
||||
fi
|
||||
# Which deploy driver to use - valid choices right now
|
||||
# are ``pxe_ssh``, ``pxe_ipmitool``, ``agent_ssh``, ``agent_ipmitool``,
|
||||
# ``pxe_snmp`` and ``ipmi``.
|
||||
# are ``pxe_ipmitool``, ``agent_ipmitool``, ``pxe_snmp`` and ``ipmi``.
|
||||
#
|
||||
# Additional valid choices if IRONIC_IS_HARDWARE == true are:
|
||||
# ``pxe_iscsi_cimc``, ``pxe_agent_cimc``, ``pxe_ucs``, ``pxe_cimc``,
|
||||
@ -1603,13 +1592,6 @@ function enroll_nodes {
|
||||
-i redfish_address=http://${HOST_IP}:${IRONIC_REDFISH_EMULATOR_PORT} \
|
||||
-i redfish_username=admin \
|
||||
-i redfish_password=password"
|
||||
else
|
||||
local node_options="\
|
||||
-i ssh_virt_type=$IRONIC_SSH_VIRT_TYPE \
|
||||
-i ssh_address=$IRONIC_VM_SSH_ADDRESS \
|
||||
-i ssh_port=$IRONIC_VM_SSH_PORT \
|
||||
-i ssh_username=$IRONIC_SSH_USERNAME \
|
||||
-i ssh_key_filename=$IRONIC_KEY_FILE"
|
||||
fi
|
||||
node_options="\
|
||||
$node_options \
|
||||
@ -1913,54 +1895,6 @@ function configure_tftpd {
|
||||
restart_service xinetd
|
||||
}
|
||||
|
||||
function configure_ironic_ssh_keypair {
|
||||
if [[ ! -d $HOME/.ssh ]]; then
|
||||
mkdir -p $HOME/.ssh
|
||||
chmod 700 $HOME/.ssh
|
||||
fi
|
||||
# recreate ssh if any part is missing
|
||||
if [[ ! -e $IRONIC_KEY_FILE ]] || [[ ! -e $IRONIC_KEY_FILE.pub ]]; then
|
||||
if [[ ! -d $(dirname $IRONIC_KEY_FILE) ]]; then
|
||||
mkdir -p $(dirname $IRONIC_KEY_FILE)
|
||||
fi
|
||||
echo -e 'n\n' | ssh-keygen -q -t rsa -P '' -f $IRONIC_KEY_FILE
|
||||
fi
|
||||
# NOTE(vsaienko) check for new line character, add if doesn't exist.
|
||||
if [[ "$(tail -c1 $IRONIC_AUTHORIZED_KEYS_FILE | wc -l)" == "0" ]]; then
|
||||
echo "" >> $IRONIC_AUTHORIZED_KEYS_FILE
|
||||
fi
|
||||
cat $IRONIC_KEY_FILE.pub | tee -a $IRONIC_AUTHORIZED_KEYS_FILE
|
||||
# remove duplicate keys.
|
||||
sort -u -o $IRONIC_AUTHORIZED_KEYS_FILE $IRONIC_AUTHORIZED_KEYS_FILE
|
||||
}
|
||||
|
||||
function ironic_ssh_check {
|
||||
local key_file=$1
|
||||
local floating_ip=$2
|
||||
local port=$3
|
||||
local default_instance_user=$4
|
||||
local attempt=$5
|
||||
local status=false
|
||||
local ssh_options="-o BatchMode=yes -o ConnectTimeout=$IRONIC_SSH_TIMEOUT -o StrictHostKeyChecking=no"
|
||||
while [[ $attempt -gt 0 ]]; do
|
||||
ssh -p $port $ssh_options -i $key_file ${default_instance_user}@$floating_ip exit
|
||||
if [[ "$?" == "0" ]]; then
|
||||
status=true
|
||||
break
|
||||
fi
|
||||
attempt=$((attempt - 1))
|
||||
echo "SSH connection failed. $attempt attempts left."
|
||||
done
|
||||
if ! $status; then
|
||||
die $LINENO "server didn't become ssh-able!"
|
||||
fi
|
||||
}
|
||||
|
||||
function configure_ironic_auxiliary {
|
||||
configure_ironic_ssh_keypair
|
||||
ironic_ssh_check $IRONIC_KEY_FILE $IRONIC_VM_SSH_ADDRESS $IRONIC_VM_SSH_PORT $IRONIC_SSH_USERNAME $IRONIC_SSH_ATTEMPTS
|
||||
}
|
||||
|
||||
function build_ipa_ramdisk {
|
||||
local kernel_path=$1
|
||||
local ramdisk_path=$2
|
||||
@ -2125,10 +2059,6 @@ function prepare_baremetal_basic_ops {
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then
|
||||
configure_ironic_auxiliary
|
||||
fi
|
||||
|
||||
if ! is_service_enabled nova && [[ "$IRONIC_IPXE_ENABLED" == "True" ]] ; then
|
||||
sudo install -g $LIBVIRT_GROUP -o $STACK_USER -m 644 $FILES/${IRONIC_WHOLEDISK_IMAGE_NAME}.img $IRONIC_HTTP_DIR
|
||||
fi
|
||||
@ -2144,13 +2074,6 @@ function cleanup_baremetal_basic_ops {
|
||||
return 0
|
||||
fi
|
||||
rm -f $IRONIC_VM_MACS_CSV_FILE
|
||||
if [ -f $IRONIC_KEY_FILE ]; then
|
||||
local key
|
||||
key=$(cat $IRONIC_KEY_FILE.pub)
|
||||
# remove public key from authorized_keys
|
||||
grep -v "$key" $IRONIC_AUTHORIZED_KEYS_FILE > temp && mv temp $IRONIC_AUTHORIZED_KEYS_FILE
|
||||
chmod 0600 $IRONIC_AUTHORIZED_KEYS_FILE
|
||||
fi
|
||||
sudo rm -rf $IRONIC_DATA_DIR $IRONIC_STATE_PATH
|
||||
|
||||
local vm_name
|
||||
|
@ -83,15 +83,6 @@ OneView driver
|
||||
drivers/oneview
|
||||
|
||||
|
||||
XenServer ssh driver
|
||||
--------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
drivers/xenserver
|
||||
|
||||
|
||||
Redfish driver
|
||||
--------------
|
||||
|
||||
|
@ -177,7 +177,7 @@ Enable driver
|
||||
4. Add the driver name to the list of ``enabled_drivers`` in
|
||||
``/etc/ironic/ironic.conf``. For example, for `iscsi_ilo` driver::
|
||||
|
||||
enabled_drivers = fake,pxe_ssh,pxe_ipmitool,iscsi_ilo
|
||||
enabled_drivers = fake,pxe_ipmitool,iscsi_ilo
|
||||
|
||||
Similarly it can be added for ``agent_ilo`` and ``pxe_ilo`` drivers.
|
||||
|
||||
@ -570,7 +570,7 @@ Configuring and Enabling the driver
|
||||
3. Add ``pxe_ilo`` to the list of ``enabled_drivers`` in
|
||||
``/etc/ironic/ironic.conf``. For example:::
|
||||
|
||||
enabled_drivers = fake,pxe_ssh,pxe_ipmitool,pxe_ilo
|
||||
enabled_drivers = fake,pxe_ipmitool,pxe_ilo
|
||||
|
||||
4. Restart the ironic conductor service::
|
||||
|
||||
|
@ -163,7 +163,7 @@ Configuring and enabling the driver
|
||||
1. Add ``agent_pxe_oneview`` to the list of ``enabled_drivers`` in your
|
||||
``ironic.conf``. For example::
|
||||
|
||||
enabled_drivers = fake,pxe_ssh,pxe_ipmitool,agent_pxe_oneview
|
||||
enabled_drivers = fake,pxe_ipmitool,agent_pxe_oneview
|
||||
|
||||
2. Update the [oneview] section of your ``ironic.conf`` file with your
|
||||
OneView credentials and CA certificate files information.
|
||||
|
@ -1,41 +0,0 @@
|
||||
.. _xenserver:
|
||||
.. _bug 1498576: https://bugs.launchpad.net/diskimage-builder/+bug/1498576
|
||||
|
||||
=================
|
||||
XenServer drivers
|
||||
=================
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
XenServer drivers can be used to deploy hosts with Ironic by using XenServer
|
||||
VMs to simulate bare metal nodes.
|
||||
|
||||
Ironic provides support via the ``pxe_ssh`` and ``agent_ssh`` drivers for
|
||||
using a XenServer VM as a bare metal target and do provisioning on it. It
|
||||
works by connecting via SSH into the XenServer host and running commands using
|
||||
the 'xe' command.
|
||||
|
||||
This is particularly useful for deploying overclouds that use XenServer for VM
|
||||
hosting as the Compute node must be run as a virtual machine on the XenServer
|
||||
host it will be controlling. In this case, one VM per hypervisor needs to be
|
||||
installed.
|
||||
|
||||
This support has been tested with XenServer 6.5.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
* Install the VMs using the "Other Install Media" template, which will ensure
|
||||
that they are HVM guests
|
||||
|
||||
* Set the HVM guests to boot from network first
|
||||
|
||||
* If your generated initramfs does not have the fix for `bug 1498576`_,
|
||||
disable the Xen PV drivers as a work around
|
||||
|
||||
::
|
||||
|
||||
xe vm-param-set uuid=<uuid> xenstore-data:vm-data="vm_data/disable_pf: 1"
|
||||
|
||||
|
@ -85,10 +85,8 @@ Currently it is supported by the following generic drivers::
|
||||
|
||||
pxe_ipmitool
|
||||
pxe_ipminative
|
||||
pxe_ssh
|
||||
agent_ipmitool
|
||||
agent_ipminative
|
||||
agent_ssh
|
||||
fake_inspector
|
||||
|
||||
It is also the default inspection approach for the following vendor drivers::
|
||||
|
@ -264,10 +264,8 @@ default:
|
||||
iscsi
|
||||
enabled_drivers =
|
||||
agent_ipmitool
|
||||
agent_ssh
|
||||
fake
|
||||
pxe_ipmitool
|
||||
pxe_ssh
|
||||
enabled_hardware_types =
|
||||
ipmi
|
||||
redfish
|
||||
@ -643,11 +641,6 @@ snmp:
|
||||
power_timeout = 10
|
||||
reboot_delay = 0
|
||||
|
||||
ssh:
|
||||
get_vm_name_attempts = 3
|
||||
get_vm_name_retry_interval = 3
|
||||
libvirt_uri = qemu:///system
|
||||
|
||||
swift:
|
||||
auth_section = None
|
||||
auth_type = password
|
||||
|
@ -481,7 +481,6 @@ and uses the ``agent_ipmitool`` driver by default::
|
||||
|
||||
# Create 3 virtual machines to pose as Ironic's baremetal nodes.
|
||||
IRONIC_VM_COUNT=3
|
||||
IRONIC_VM_SSH_PORT=22
|
||||
IRONIC_BAREMETAL_BASIC_OPS=True
|
||||
DEFAULT_INSTANCE_TYPE=baremetal
|
||||
|
||||
@ -612,8 +611,8 @@ It should be powered on and in a 'wait call-back' provisioning state::
|
||||
| 4099e31c-576c-48f8-b460-75e1b14e497f | node-2 | a2c7f812-e386-4a22-b393-fe1802abd56e | power on | wait call-back | False |
|
||||
+--------------------------------------+--------+--------------------------------------+-------------+--------------------+-------------+
|
||||
|
||||
At this point, Ironic conductor has called to libvirt via SSH to power on a
|
||||
virtual machine, which will PXE + TFTP boot from the conductor node and
|
||||
At this point, Ironic conductor has called to libvirt (via virtualbmc) to
|
||||
power on a virtual machine, which will PXE + TFTP boot from the conductor node and
|
||||
progress through the Ironic provisioning workflow. One libvirt domain should
|
||||
be active now::
|
||||
|
||||
|
@ -100,15 +100,14 @@ configured in Neutron.
|
||||
|
||||
# Create 3 virtual machines to pose as Ironic's baremetal nodes.
|
||||
IRONIC_VM_COUNT=3
|
||||
IRONIC_VM_SSH_PORT=22
|
||||
IRONIC_BAREMETAL_BASIC_OPS=True
|
||||
|
||||
# Enable Ironic drivers.
|
||||
IRONIC_ENABLED_DRIVERS=fake,agent_ssh,agent_ipmitool,pxe_ssh,pxe_ipmitool
|
||||
IRONIC_ENABLED_DRIVERS=fake,agent_ipmitool,pxe_ipmitool
|
||||
|
||||
# Change this to alter the default driver for nodes created by devstack.
|
||||
# This driver should be in the enabled list above.
|
||||
IRONIC_DEPLOY_DRIVER=agent_ssh
|
||||
IRONIC_DEPLOY_DRIVER=agent_ipmitool
|
||||
|
||||
# The parameters below represent the minimum possible values to create
|
||||
# functional nodes.
|
||||
|
@ -33,7 +33,8 @@ ironic-api
|
||||
ironic-conductor over `remote procedure call (RPC)`_.
|
||||
|
||||
ironic-conductor
|
||||
Adds/edits/deletes nodes; powers on/off nodes with ipmi or ssh;
|
||||
Adds/edits/deletes nodes; powers on/off nodes with ipmi or other
|
||||
vendor-specific protocol;
|
||||
provisions/deploys/cleans bare metal nodes.
|
||||
|
||||
ironic-python-agent
|
||||
|
@ -41,7 +41,7 @@ service via hrefs.
|
||||
There are however some limitations for different drivers:
|
||||
|
||||
* If you're using one of the drivers that use agent deploy method (namely,
|
||||
``agent_ilo``, ``agent_ipmitool``, or ``agent_ssh``)
|
||||
``agent_ilo`` or ``agent_ipmitool``)
|
||||
you have to know MD5 checksum for your instance image. To
|
||||
compute it, you can use the following command::
|
||||
|
||||
|
@ -3520,25 +3520,6 @@
|
||||
#reboot_delay = 0
|
||||
|
||||
|
||||
[ssh]
|
||||
|
||||
#
|
||||
# From ironic
|
||||
#
|
||||
|
||||
# libvirt URI. (string value)
|
||||
#libvirt_uri = qemu:///system
|
||||
|
||||
# Number of attempts to try to get VM name used by the host
|
||||
# that corresponds to a node's MAC address. (integer value)
|
||||
#get_vm_name_attempts = 3
|
||||
|
||||
# Number of seconds to wait between attempts to get VM name
|
||||
# used by the host that corresponds to a node's MAC address.
|
||||
# (integer value)
|
||||
#get_vm_name_retry_interval = 3
|
||||
|
||||
|
||||
[ssl]
|
||||
|
||||
#
|
||||
|
@ -481,14 +481,6 @@ class IPMIFailure(IronicException):
|
||||
_msg_fmt = _("IPMI call failed: %(cmd)s.")
|
||||
|
||||
|
||||
class SSHConnectFailed(IronicException):
|
||||
_msg_fmt = _("Failed to establish SSH connection to host %(host)s.")
|
||||
|
||||
|
||||
class SSHCommandFailed(IronicException):
|
||||
_msg_fmt = _("Failed to execute command via SSH: %(cmd)s.")
|
||||
|
||||
|
||||
class UnsupportedDriverExtension(Invalid):
|
||||
_msg_fmt = _('Driver %(driver)s does not support %(extension)s '
|
||||
'(disabled or not implemented).')
|
||||
|
@ -32,7 +32,6 @@ from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
from oslo_utils import timeutils
|
||||
import paramiko
|
||||
import pytz
|
||||
import six
|
||||
|
||||
@ -79,46 +78,6 @@ def execute(*cmd, **kwargs):
|
||||
return result
|
||||
|
||||
|
||||
def ssh_connect(connection):
|
||||
"""Method to connect to a remote system using ssh protocol.
|
||||
|
||||
:param connection: a dict of connection parameters.
|
||||
:returns: paramiko.SSHClient -- an active ssh connection.
|
||||
:raises: SSHConnectFailed
|
||||
|
||||
"""
|
||||
try:
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
key_contents = connection.get('key_contents')
|
||||
if key_contents:
|
||||
data = six.StringIO(key_contents)
|
||||
if "BEGIN RSA PRIVATE" in key_contents:
|
||||
pkey = paramiko.RSAKey.from_private_key(data)
|
||||
elif "BEGIN DSA PRIVATE" in key_contents:
|
||||
pkey = paramiko.DSSKey.from_private_key(data)
|
||||
else:
|
||||
# Can't include the key contents - secure material.
|
||||
raise ValueError(_("Invalid private key"))
|
||||
else:
|
||||
pkey = None
|
||||
ssh.connect(connection.get('host'),
|
||||
username=connection.get('username'),
|
||||
password=connection.get('password'),
|
||||
port=connection.get('port', 22),
|
||||
pkey=pkey,
|
||||
key_filename=connection.get('key_filename'),
|
||||
timeout=connection.get('timeout', 10))
|
||||
|
||||
# send TCP keepalive packets every 20 seconds
|
||||
ssh.get_transport().set_keepalive(20)
|
||||
except Exception as e:
|
||||
LOG.debug("SSH connect failed: %s", e)
|
||||
raise exception.SSHConnectFailed(host=connection.get('host'))
|
||||
|
||||
return ssh
|
||||
|
||||
|
||||
def is_valid_datapath_id(datapath_id):
|
||||
"""Verify the format of an OpenFlow datapath_id.
|
||||
|
||||
|
@ -42,7 +42,6 @@ from ironic.conf import pxe
|
||||
from ironic.conf import redfish
|
||||
from ironic.conf import service_catalog
|
||||
from ironic.conf import snmp
|
||||
from ironic.conf import ssh
|
||||
from ironic.conf import swift
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -74,5 +73,4 @@ pxe.register_opts(CONF)
|
||||
redfish.register_opts(CONF)
|
||||
service_catalog.register_opts(CONF)
|
||||
snmp.register_opts(CONF)
|
||||
ssh.register_opts(CONF)
|
||||
swift.register_opts(CONF)
|
||||
|
@ -59,7 +59,6 @@ _opts = [
|
||||
('pxe', ironic.conf.pxe.opts),
|
||||
('service_catalog', ironic.conf.service_catalog.list_opts()),
|
||||
('snmp', ironic.conf.snmp.opts),
|
||||
('ssh', ironic.conf.ssh.opts),
|
||||
('swift', ironic.conf.swift.list_opts()),
|
||||
]
|
||||
|
||||
@ -96,7 +95,6 @@ def update_opt_defaults():
|
||||
'stevedore=INFO',
|
||||
'eventlet.wsgi.server=INFO',
|
||||
'iso8601=WARNING',
|
||||
'paramiko=WARNING',
|
||||
'requests=WARNING',
|
||||
'neutronclient=WARNING',
|
||||
'glanceclient=WARNING',
|
||||
|
@ -1,38 +0,0 @@
|
||||
# Copyright 2016 Intel Corporation
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic.common.i18n import _
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('libvirt_uri',
|
||||
default='qemu:///system',
|
||||
help=_('libvirt URI.')),
|
||||
cfg.IntOpt('get_vm_name_attempts',
|
||||
default=3,
|
||||
help=_("Number of attempts to try to get VM name used by the "
|
||||
"host that corresponds to a node's MAC address.")),
|
||||
cfg.IntOpt('get_vm_name_retry_interval',
|
||||
default=3,
|
||||
help=_("Number of seconds to wait between attempts to get "
|
||||
"VM name used by the host that corresponds to a "
|
||||
"node's MAC address.")),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opts(opts, group='ssh')
|
@ -23,7 +23,6 @@ from ironic.drivers.modules.cimc import management as cimc_mgmt
|
||||
from ironic.drivers.modules.cimc import power as cimc_power
|
||||
from ironic.drivers.modules import inspector
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import ssh
|
||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||
from ironic.drivers.modules.ucs import power as ucs_power
|
||||
|
||||
@ -33,32 +32,6 @@ AgentAndIPMIToolDriver = ipmi.AgentAndIPMIToolDriver
|
||||
AgentAndIPMIToolAndSocatDriver = ipmi.AgentAndIPMIToolAndSocatDriver
|
||||
|
||||
|
||||
class AgentAndSSHDriver(base.BaseDriver):
|
||||
"""Agent + SSH driver.
|
||||
|
||||
NOTE: This driver is meant only for testing environments.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.modules.ssh.SSH` (for power on/off and reboot of
|
||||
virtual machines tunneled over SSH), with
|
||||
:class:`ironic.drivers.modules.agent.AgentDeploy` (for image
|
||||
deployment). Implementations are in those respective classes; this class
|
||||
is merely the glue between them.
|
||||
"""
|
||||
|
||||
supported = False
|
||||
|
||||
def __init__(self):
|
||||
self.power = ssh.SSHPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = agent.AgentDeploy()
|
||||
self.management = ssh.SSHManagement()
|
||||
self.raid = agent.AgentRAID()
|
||||
self.inspect = inspector.Inspector.create_if_enabled(
|
||||
'AgentAndSSHDriver')
|
||||
self.console = ssh.ShellinaboxConsole()
|
||||
|
||||
|
||||
class AgentAndUcsDriver(base.BaseDriver):
|
||||
"""Agent + Cisco UCSM driver.
|
||||
|
||||
|
@ -46,7 +46,6 @@ from ironic.drivers.modules.oneview import management as oneview_management
|
||||
from ironic.drivers.modules.oneview import power as oneview_power
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import snmp
|
||||
from ironic.drivers.modules import ssh
|
||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||
from ironic.drivers.modules.ucs import power as ucs_power
|
||||
from ironic.drivers import utils
|
||||
@ -112,18 +111,6 @@ class FakePXEDriver(base.BaseDriver):
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
|
||||
|
||||
class FakeSSHDriver(base.BaseDriver):
|
||||
"""Example implementation of a Driver."""
|
||||
|
||||
supported = False
|
||||
|
||||
def __init__(self):
|
||||
self.power = ssh.SSHPower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.management = ssh.SSHManagement()
|
||||
self.console = ssh.ShellinaboxConsole()
|
||||
|
||||
|
||||
class FakeAgentDriver(base.BaseDriver):
|
||||
"""Example implementation of an AgentDriver."""
|
||||
|
||||
|
@ -1,902 +0,0 @@
|
||||
# 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)
|
||||
VMware (vmware)
|
||||
Parallels (parallels)
|
||||
XenServer (xenserver)
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import strutils
|
||||
|
||||
import retrying
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import console_utils
|
||||
from ironic.drivers import utils as driver_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
REQUIRED_PROPERTIES = {
|
||||
'ssh_address': _("IP address or hostname of the node to ssh into. "
|
||||
"Required."),
|
||||
'ssh_username': _("username to authenticate as. Required."),
|
||||
'ssh_virt_type': _("virtualization software to use; one of vbox, virsh, "
|
||||
"vmware, parallels, xenserver. Required.")
|
||||
}
|
||||
OTHER_PROPERTIES = {
|
||||
'ssh_key_contents': _("private key(s). If ssh_password is also specified "
|
||||
"it will be used for unlocking the private key. Do "
|
||||
"not specify ssh_key_filename when this property is "
|
||||
"specified."),
|
||||
'ssh_key_filename': _("(list of) filename(s) of optional private key(s) "
|
||||
"for authentication. If ssh_password is also "
|
||||
"specified it will be used for unlocking the "
|
||||
"private key. Do not specify ssh_key_contents when "
|
||||
"this property is specified."),
|
||||
'ssh_password': _("password to use for authentication or for unlocking a "
|
||||
"private key. At least one of this, ssh_key_contents, "
|
||||
"or ssh_key_filename must be specified."),
|
||||
'ssh_port': _("port on the node to connect to; default is 22. Optional."),
|
||||
'vbox_use_headless': _("True or False (Default). Optional. "
|
||||
"In the case of VirtualBox 3.2 and above, allows "
|
||||
"the user to use a headless remote VirtualBox "
|
||||
"machine.")
|
||||
}
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||
COMMON_PROPERTIES.update(OTHER_PROPERTIES)
|
||||
CONSOLE_PROPERTIES = {
|
||||
'ssh_terminal_port': _("node's UDP port to connect to. Only required for "
|
||||
"console access and only applicable for 'virsh'.")
|
||||
}
|
||||
|
||||
# NOTE(dguerri) Generic boot device map. Virtualisation types that don't define
|
||||
# a more specific one, will use this.
|
||||
# This is left for compatibility with other modules and is still valid for
|
||||
# virsh and vmware.
|
||||
_BOOT_DEVICES_MAP = {
|
||||
boot_devices.DISK: 'hd',
|
||||
boot_devices.PXE: 'network',
|
||||
boot_devices.CDROM: 'cdrom',
|
||||
}
|
||||
|
||||
|
||||
def _get_boot_device_map(virt_type):
|
||||
if virt_type in ('virsh', 'vmware'):
|
||||
return _BOOT_DEVICES_MAP
|
||||
elif virt_type == 'vbox':
|
||||
return {
|
||||
boot_devices.DISK: 'disk',
|
||||
boot_devices.PXE: 'net',
|
||||
boot_devices.CDROM: 'dvd',
|
||||
}
|
||||
elif virt_type == 'xenserver':
|
||||
return {
|
||||
boot_devices.DISK: 'c',
|
||||
boot_devices.PXE: 'n',
|
||||
boot_devices.CDROM: 'd',
|
||||
}
|
||||
elif virt_type == 'parallels':
|
||||
return {
|
||||
boot_devices.DISK: 'hdd0',
|
||||
boot_devices.PXE: 'net0',
|
||||
boot_devices.CDROM: 'cdrom0',
|
||||
}
|
||||
else:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver '%(virt_type)s' is not a valid virt_type.") %
|
||||
{'virt_type': virt_type})
|
||||
|
||||
|
||||
def _get_command_sets(virt_type, use_headless=False):
|
||||
"""Retrieves the virt_type-specific commands to control power
|
||||
|
||||
:param virt_type: Hypervisor type (virsh, vmware, vbox, parallels,
|
||||
xenserver)
|
||||
:param use_headless: boolean value, defaults to False.
|
||||
use_headless is used by some Hypervisors (only VBox v3.2 and above)
|
||||
to determine if the hypervisor is being used on a headless box.
|
||||
This is only relevant to Desktop Hypervisors that have different
|
||||
CLI settings depending upon the availability of a graphical
|
||||
environment working on the hypervisor itself. Again, only VBox
|
||||
makes this distinction and allows "--type headless" to some of
|
||||
its sub-commands. This is needed for support of tripleo with
|
||||
VBox as the Hypervisor but some other Hypervisors could make
|
||||
use of it in the future (Parallels, VMWare Workstation, etc...)
|
||||
|
||||
Required commands are as follows:
|
||||
|
||||
base_cmd: Used by most sub-commands as the primary executable
|
||||
list_all: Lists all VMs (by virt_type identifier) that can be managed.
|
||||
One name per line, must not be quoted.
|
||||
list_running: Lists all running VMs (by virt_type identifier).
|
||||
One name per line, can be quoted.
|
||||
start_cmd / stop_cmd: Starts or stops the identified VM
|
||||
get_node_macs: Retrieves all MACs for an identified VM.
|
||||
One MAC per line, any standard format (see driver_utils.normalize_mac)
|
||||
get_boot_device / set_boot_device: Gets or sets the primary boot device
|
||||
"""
|
||||
if virt_type == 'vbox':
|
||||
vbox_headless_str = ''
|
||||
if use_headless:
|
||||
vbox_headless_str = ' --type headless'
|
||||
return {
|
||||
'base_cmd': 'LC_ALL=C /usr/bin/VBoxManage',
|
||||
'start_cmd': 'startvm {_NodeName_}%s' % vbox_headless_str,
|
||||
'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_} | "
|
||||
"awk -F '\"' '/macaddress/{print $2}'"),
|
||||
'set_boot_device': (
|
||||
'{_BaseCmd_} modifyvm {_NodeName_} '
|
||||
'--boot1 {_BootDevice_}'),
|
||||
'get_boot_device': (
|
||||
"{_BaseCmd_} showvminfo "
|
||||
"--machinereadable {_NodeName_} | "
|
||||
"awk -F '\"' '/boot1/{print $2}'"),
|
||||
}
|
||||
elif virt_type == 'vmware':
|
||||
return {
|
||||
'base_cmd': 'LC_ALL=C /bin/vim-cmd',
|
||||
'start_cmd': 'vmsvc/power.on {_NodeName_}',
|
||||
'stop_cmd': 'vmsvc/power.off {_NodeName_}',
|
||||
'reboot_cmd': 'vmsvc/power.reboot {_NodeName_}',
|
||||
'list_all': "vmsvc/getallvms | awk '$1 ~ /^[0-9]+$/ {print $1}'",
|
||||
# NOTE(arata): In spite of its name, list_running_cmd shows a
|
||||
# single vmid, not a list. But it is OK.
|
||||
'list_running': (
|
||||
"vmsvc/power.getstate {_NodeName_} | "
|
||||
"grep 'Powered on' >/dev/null && "
|
||||
"echo '\"{_NodeName_}\"' || true"),
|
||||
# NOTE(arata): `true` is needed to handle a false vmid, which can
|
||||
# be returned by list_cmd. In that case, get_node_macs
|
||||
# returns an empty list rather than fails with
|
||||
# non-zero status code.
|
||||
'get_node_macs': (
|
||||
"vmsvc/device.getdevices {_NodeName_} | "
|
||||
"grep macAddress | awk -F '\"' '{print $2}' || true"),
|
||||
}
|
||||
elif virt_type == "virsh":
|
||||
# NOTE(NobodyCam): changes to the virsh commands will impact CI
|
||||
# see https://review.openstack.org/83906
|
||||
# Change-Id: I160e4202952b7551b855dc7d91784d6a184cb0ed
|
||||
# for more detail.
|
||||
virsh_cmds = {
|
||||
'base_cmd': 'LC_ALL=C /usr/bin/virsh',
|
||||
'start_cmd': 'start {_NodeName_}',
|
||||
'stop_cmd': 'destroy {_NodeName_}',
|
||||
'reboot_cmd': 'reset {_NodeName_}',
|
||||
'list_all': 'list --all --name',
|
||||
'list_running': 'list --name',
|
||||
'get_node_macs': (
|
||||
"dumpxml {_NodeName_} | "
|
||||
"awk -F \"'\" '/mac address/{print $2}'| tr -d ':'"),
|
||||
'set_boot_device': (
|
||||
"EDITOR=\"sed -i '/<boot \(dev\|order\)=*\>/d;"
|
||||
"/<\/os>/i\<boot dev=\\\"{_BootDevice_}\\\"/>'\" "
|
||||
"{_BaseCmd_} edit {_NodeName_}"),
|
||||
'get_boot_device': (
|
||||
"{_BaseCmd_} dumpxml {_NodeName_} | "
|
||||
"awk '/boot dev=/ { gsub( \".*dev=\" Q, \"\" ); "
|
||||
"gsub( Q \".*\", \"\" ); print; }' "
|
||||
"Q=\"'\" RS=\"[<>]\" | "
|
||||
"head -1"),
|
||||
}
|
||||
|
||||
if CONF.ssh.libvirt_uri:
|
||||
virsh_cmds['base_cmd'] += ' --connect %s' % CONF.ssh.libvirt_uri
|
||||
|
||||
return virsh_cmds
|
||||
elif virt_type == 'parallels':
|
||||
return {
|
||||
'base_cmd': 'LC_ALL=C /usr/bin/prlctl',
|
||||
'start_cmd': 'start {_NodeName_}',
|
||||
'stop_cmd': 'stop {_NodeName_} --kill',
|
||||
'reboot_cmd': 'reset {_NodeName_}',
|
||||
'list_all': "list -a -o name |tail -n +2",
|
||||
'list_running': 'list -o name |tail -n +2',
|
||||
'get_node_macs': (
|
||||
"list -j -i \"{_NodeName_}\" | "
|
||||
"awk -F'\"' '/\"mac\":/ {print $4}' | "
|
||||
"sed 's/\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)/"
|
||||
"\\1:\\2:\\3:\\4:\\5\\6/' | "
|
||||
"tr '[:upper:]' '[:lower:]'"),
|
||||
'set_boot_device': (
|
||||
"{_BaseCmd_} set {_NodeName_} "
|
||||
"--device-bootorder \"{_BootDevice_}\""),
|
||||
'get_boot_device': (
|
||||
"{_BaseCmd_} list -i {_NodeName_} | "
|
||||
"awk '/^Boot order:/ {print $3}'"),
|
||||
}
|
||||
elif virt_type == 'xenserver':
|
||||
return {
|
||||
'base_cmd': 'LC_ALL=C /opt/xensource/bin/xe',
|
||||
# Note(bobba): XenServer appears to have a condition where
|
||||
# vm-start can return before the power-state
|
||||
# has been updated to 'running'. Ironic
|
||||
# expects the power-state to be updated
|
||||
# immediately, so may find that power-state
|
||||
# is still 'halted' and attempt to start the
|
||||
# VM a second time. Sleep to avoid the race.
|
||||
'start_cmd': 'vm-start uuid={_NodeName_} && sleep 10s',
|
||||
'stop_cmd': 'vm-shutdown uuid={_NodeName_} force=true',
|
||||
'list_all': "vm-list --minimal | tr ',' '\n'",
|
||||
'list_running': (
|
||||
"vm-list power-state=running --minimal |"
|
||||
" tr ',' '\n'"),
|
||||
'get_node_macs': (
|
||||
"vif-list vm-uuid={_NodeName_}"
|
||||
" params=MAC --minimal | tr ',' '\n'"),
|
||||
'set_boot_device': (
|
||||
"{_BaseCmd_} vm-param-set uuid={_NodeName_}"
|
||||
" HVM-boot-params:order='{_BootDevice_}'"),
|
||||
'get_boot_device': (
|
||||
"{_BaseCmd_} vm-param-get uuid={_NodeName_}"
|
||||
" --param-name=HVM-boot-params param-key=order | cut -b 1"),
|
||||
}
|
||||
else:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver '%(virt_type)s' is not a valid virt_type, ") %
|
||||
{'virt_type': virt_type})
|
||||
|
||||
|
||||
def _get_boot_device(ssh_obj, driver_info):
|
||||
"""Get the current boot device.
|
||||
|
||||
:param ssh_obj: paramiko.SSHClient, an active ssh connection.
|
||||
:param driver_info: information for accessing the node.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
:raises: NotImplementedError if the virt_type does not support
|
||||
getting the boot device.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
|
||||
"""
|
||||
cmd_to_exec = driver_info['cmd_set'].get('get_boot_device')
|
||||
if cmd_to_exec:
|
||||
boot_device_map = _get_boot_device_map(driver_info['virt_type'])
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
base_cmd = driver_info['cmd_set']['base_cmd']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', node_name)
|
||||
cmd_to_exec = cmd_to_exec.replace('{_BaseCmd_}', base_cmd)
|
||||
stdout, stderr = _ssh_execute(ssh_obj, cmd_to_exec)
|
||||
return next((dev for dev, hdev in boot_device_map.items()
|
||||
if hdev == stdout), None)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _set_boot_device(ssh_obj, driver_info, device):
|
||||
"""Set the boot device.
|
||||
|
||||
:param ssh_obj: paramiko.SSHClient, an active ssh connection.
|
||||
:param driver_info: information for accessing the node.
|
||||
:param device: the boot device.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
:raises: NotImplementedError if the virt_type does not support
|
||||
setting the boot device.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
|
||||
"""
|
||||
cmd_to_exec = driver_info['cmd_set'].get('set_boot_device')
|
||||
if cmd_to_exec:
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
base_cmd = driver_info['cmd_set']['base_cmd']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', node_name)
|
||||
cmd_to_exec = cmd_to_exec.replace('{_BootDevice_}', device)
|
||||
cmd_to_exec = cmd_to_exec.replace('{_BaseCmd_}', base_cmd)
|
||||
_ssh_execute(ssh_obj, cmd_to_exec)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _ssh_execute(ssh_obj, cmd_to_exec):
|
||||
"""Executes a command via ssh.
|
||||
|
||||
Executes a command via ssh and returns a list of the lines of the
|
||||
output from the command.
|
||||
|
||||
:param ssh_obj: paramiko.SSHClient, an active ssh connection.
|
||||
:param cmd_to_exec: command to execute.
|
||||
:returns: list of the lines of output from the command.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
|
||||
"""
|
||||
try:
|
||||
output_list = processutils.ssh_execute(ssh_obj,
|
||||
cmd_to_exec)[0].split('\n')
|
||||
except Exception as e:
|
||||
LOG.error("Cannot execute SSH cmd %(cmd)s. Reason: %(err)s.",
|
||||
{'cmd': cmd_to_exec, 'err': e})
|
||||
raise exception.SSHCommandFailed(cmd=cmd_to_exec)
|
||||
|
||||
return output_list
|
||||
|
||||
|
||||
def _parse_driver_info(node):
|
||||
"""Gets the information needed for accessing the node.
|
||||
|
||||
:param node: the Node of interest.
|
||||
:returns: dictionary of information.
|
||||
:raises: InvalidParameterValue if any required parameters are incorrect.
|
||||
:raises: MissingParameterValue if any required parameters are missing.
|
||||
|
||||
"""
|
||||
info = node.driver_info or {}
|
||||
missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)]
|
||||
if missing_info:
|
||||
raise exception.MissingParameterValue(_(
|
||||
"SSHPowerDriver requires the following parameters to be set in "
|
||||
"node's driver_info: %s.") % missing_info)
|
||||
|
||||
address = info.get('ssh_address')
|
||||
username = info.get('ssh_username')
|
||||
password = info.get('ssh_password')
|
||||
port = info.get('ssh_port', 22)
|
||||
port = utils.validate_network_port(port, 'ssh_port')
|
||||
key_contents = info.get('ssh_key_contents')
|
||||
key_filename = info.get('ssh_key_filename')
|
||||
use_headless = strutils.bool_from_string(info.get('vbox_use_headless',
|
||||
False))
|
||||
virt_type = info.get('ssh_virt_type')
|
||||
terminal_port = info.get('ssh_terminal_port')
|
||||
|
||||
if terminal_port is not None:
|
||||
terminal_port = utils.validate_network_port(terminal_port,
|
||||
'ssh_terminal_port')
|
||||
|
||||
# NOTE(deva): we map 'address' from API to 'host' for common utils
|
||||
res = {
|
||||
'host': address,
|
||||
'username': username,
|
||||
'port': port,
|
||||
'use_headless': use_headless,
|
||||
'virt_type': virt_type,
|
||||
'uuid': node.uuid,
|
||||
'terminal_port': terminal_port
|
||||
}
|
||||
|
||||
cmd_set = _get_command_sets(virt_type, use_headless)
|
||||
res['cmd_set'] = cmd_set
|
||||
|
||||
# Set at least one credential method.
|
||||
if len([v for v in (password, key_filename, key_contents) if v]) == 0:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver requires at least one of ssh_password, "
|
||||
"ssh_key_contents and ssh_key_filename to be set."))
|
||||
|
||||
# Set only credential file or content but not both
|
||||
if key_filename and key_contents:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver requires one and only one of "
|
||||
"ssh_key_contents and ssh_key_filename to be set."))
|
||||
|
||||
if password:
|
||||
res['password'] = password
|
||||
|
||||
if key_contents:
|
||||
res['key_contents'] = key_contents
|
||||
|
||||
if key_filename:
|
||||
if not os.path.isfile(key_filename):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSH key file %s not found.") % key_filename)
|
||||
res['key_filename'] = key_filename
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _get_power_status(ssh_obj, driver_info):
|
||||
"""Returns a node's current power state.
|
||||
|
||||
:param ssh_obj: paramiko.SSHClient, an active ssh connection.
|
||||
:param driver_info: information for accessing the node.
|
||||
:returns: one of ironic.common.states POWER_OFF, POWER_ON.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
|
||||
"""
|
||||
power_state = None
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
# Get a list of vms running on the host. If the command supports
|
||||
# it, explicitly specify the desired node."
|
||||
cmd_to_exec = "%s %s" % (driver_info['cmd_set']['base_cmd'],
|
||||
driver_info['cmd_set']['list_running'])
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', node_name)
|
||||
running_list = _ssh_execute(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.
|
||||
quoted_node_name = '"%s"' % node_name
|
||||
for node in running_list:
|
||||
if not node:
|
||||
continue
|
||||
# 'node' here is a formatted output from the virt cli's. The
|
||||
# node name is either an exact match or quoted (optionally with
|
||||
# other information, e.g. vbox returns '"NodeName" {<uuid>}')
|
||||
if (quoted_node_name in node) or (node_name == node):
|
||||
power_state = states.POWER_ON
|
||||
break
|
||||
if not power_state:
|
||||
power_state = states.POWER_OFF
|
||||
|
||||
return power_state
|
||||
|
||||
|
||||
def _get_connection(node):
|
||||
"""Returns an SSH client connected to a node.
|
||||
|
||||
:param node: the Node.
|
||||
:returns: paramiko.SSHClient, an active ssh connection.
|
||||
|
||||
"""
|
||||
return utils.ssh_connect(_parse_driver_info(node))
|
||||
|
||||
|
||||
def _get_hosts_name_for_node(ssh_obj, driver_info):
|
||||
"""Get the name the host uses to reference the node.
|
||||
|
||||
:param ssh_obj: paramiko.SSHClient, an active ssh connection.
|
||||
:param driver_info: information for accessing the node.
|
||||
:returns: the name of the node.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any of
|
||||
the provided MACs
|
||||
|
||||
"""
|
||||
|
||||
@retrying.retry(
|
||||
retry_on_result=lambda v: v is None,
|
||||
retry_on_exception=lambda _: False, # Do not retry on SSHCommandFailed
|
||||
stop_max_attempt_number=CONF.ssh.get_vm_name_attempts,
|
||||
wait_fixed=CONF.ssh.get_vm_name_retry_interval * 1000)
|
||||
def _with_retries():
|
||||
matched_name = None
|
||||
cmd_to_exec = "%s %s" % (driver_info['cmd_set']['base_cmd'],
|
||||
driver_info['cmd_set']['list_all'])
|
||||
full_node_list = _ssh_execute(ssh_obj, cmd_to_exec)
|
||||
LOG.debug("Retrieved Node List: %s", repr(full_node_list))
|
||||
# for each node check Mac Addresses
|
||||
for node in full_node_list:
|
||||
if not node:
|
||||
continue
|
||||
LOG.debug("Checking Node: %s's Mac address.", node)
|
||||
cmd_to_exec = "%s %s" % (driver_info['cmd_set']['base_cmd'],
|
||||
driver_info['cmd_set']['get_node_macs'])
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', node)
|
||||
hosts_node_mac_list = _ssh_execute(ssh_obj, cmd_to_exec)
|
||||
|
||||
for host_mac in hosts_node_mac_list:
|
||||
if not host_mac:
|
||||
continue
|
||||
for node_mac in driver_info['macs']:
|
||||
if (driver_utils.normalize_mac(host_mac)
|
||||
in driver_utils.normalize_mac(node_mac)):
|
||||
LOG.debug("Found Mac address: %s", node_mac)
|
||||
matched_name = node
|
||||
break
|
||||
|
||||
if matched_name:
|
||||
break
|
||||
if matched_name:
|
||||
break
|
||||
|
||||
return matched_name
|
||||
|
||||
try:
|
||||
return _with_retries()
|
||||
except retrying.RetryError:
|
||||
raise exception.NodeNotFound(
|
||||
_("SSH driver was not able to find a VM with any of the "
|
||||
"specified MACs: %(macs)s for node %(node)s.") %
|
||||
{'macs': driver_info['macs'], 'node': driver_info['uuid']})
|
||||
|
||||
|
||||
def _power_on(ssh_obj, driver_info):
|
||||
"""Power ON this node.
|
||||
|
||||
:param ssh_obj: paramiko.SSHClient, an active ssh connection.
|
||||
:param driver_info: information for accessing the node.
|
||||
:returns: one of ironic.common.states POWER_ON or ERROR.
|
||||
|
||||
"""
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
_power_off(ssh_obj, driver_info)
|
||||
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
cmd_to_power_on = "%s %s" % (driver_info['cmd_set']['base_cmd'],
|
||||
driver_info['cmd_set']['start_cmd'])
|
||||
cmd_to_power_on = cmd_to_power_on.replace('{_NodeName_}', node_name)
|
||||
|
||||
_ssh_execute(ssh_obj, cmd_to_power_on)
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
return current_pstate
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
def _power_off(ssh_obj, driver_info):
|
||||
"""Power OFF this node.
|
||||
|
||||
:param ssh_obj: paramiko.SSHClient, an active ssh connection.
|
||||
:param driver_info: information for accessing the node.
|
||||
:returns: one of ironic.common.states POWER_OFF or ERROR.
|
||||
|
||||
"""
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
cmd_to_power_off = "%s %s" % (driver_info['cmd_set']['base_cmd'],
|
||||
driver_info['cmd_set']['stop_cmd'])
|
||||
cmd_to_power_off = cmd_to_power_off.replace('{_NodeName_}', node_name)
|
||||
|
||||
_ssh_execute(ssh_obj, cmd_to_power_off)
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
class SSHPower(base.PowerInterface):
|
||||
"""SSH Power Interface.
|
||||
|
||||
This PowerInterface 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 get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check that the node's 'driver_info' is valid.
|
||||
|
||||
Check that the node's 'driver_info' contains the requisite fields
|
||||
and that an SSH connection to the node can be established.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect or if ssh failed to connect to the node.
|
||||
:raises: MissingParameterValue if no ports are enrolled for the given
|
||||
node.
|
||||
"""
|
||||
if not driver_utils.get_node_mac_addresses(task):
|
||||
raise exception.MissingParameterValue(
|
||||
_("Node %s does not have any port associated with it."
|
||||
) % task.node.uuid)
|
||||
try:
|
||||
_get_connection(task.node)
|
||||
except exception.SSHConnectFailed as e:
|
||||
raise exception.InvalidParameterValue(_("SSH connection cannot"
|
||||
" be established: %s") % e)
|
||||
|
||||
def get_power_state(self, task):
|
||||
"""Get the current power state of the task's node.
|
||||
|
||||
Poll the host for the current power state of the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:returns: power state. One of :class:`ironic.common.states`.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
:raises: SSHConnectFailed if ssh failed to connect to the node.
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
driver_info['macs'] = driver_utils.get_node_mac_addresses(task)
|
||||
ssh_obj = _get_connection(task.node)
|
||||
return _get_power_status(ssh_obj, driver_info)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, pstate):
|
||||
"""Turn the power on or off.
|
||||
|
||||
Set the power state of the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param pstate: Either POWER_ON or POWER_OFF from :class:
|
||||
`ironic.common.states`.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect, or if the desired power state is invalid.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: PowerStateFailure if it failed to set power state to pstate.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
:raises: SSHConnectFailed if ssh failed to connect to the node.
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
driver_info['macs'] = driver_utils.get_node_mac_addresses(task)
|
||||
ssh_obj = _get_connection(task.node)
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(ssh_obj, driver_info)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(ssh_obj, driver_info)
|
||||
else:
|
||||
raise exception.InvalidParameterValue(
|
||||
_("set_power_state called with invalid power state %s."
|
||||
) % pstate)
|
||||
|
||||
if state != pstate:
|
||||
raise exception.PowerStateFailure(pstate=pstate)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task):
|
||||
"""Cycles the power to the task's node.
|
||||
|
||||
Power cycles a node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: PowerStateFailure if it failed to set power state to POWER_ON.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
:raises: SSHConnectFailed if ssh failed to connect to the node.
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
driver_info['macs'] = driver_utils.get_node_mac_addresses(task)
|
||||
ssh_obj = _get_connection(task.node)
|
||||
|
||||
# _power_on will turn the power off if it's already on.
|
||||
state = _power_on(ssh_obj, driver_info)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
|
||||
class SSHManagement(base.ManagementInterface):
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check that 'driver_info' contains SSH credentials.
|
||||
|
||||
Validates whether the 'driver_info' property of the supplied
|
||||
task's node contains the required credentials information.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect.
|
||||
:raises: MissingParameterValue if a required parameter is missing
|
||||
"""
|
||||
_parse_driver_info(task.node)
|
||||
|
||||
def get_supported_boot_devices(self, task):
|
||||
"""Get a list of the supported boot devices.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: A list with the supported boot devices defined
|
||||
in :mod:`ironic.common.boot_devices`.
|
||||
|
||||
"""
|
||||
return list(_BOOT_DEVICES_MAP.keys())
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_boot_device(self, task, device, persistent=False):
|
||||
"""Set the boot device for the task's node.
|
||||
|
||||
Set the boot device to use on next reboot of the node.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:param device: the boot device, one of
|
||||
:mod:`ironic.common.boot_devices`.
|
||||
:param persistent: Boolean value. True if the boot device will
|
||||
persist to all future boots, False if not.
|
||||
Default: False. Ignored by this driver.
|
||||
:raises: InvalidParameterValue if an invalid boot device is
|
||||
specified or if any connection parameters are incorrect.
|
||||
:raises: MissingParameterValue if a required parameter is missing
|
||||
:raises: SSHConnectFailed if ssh failed to connect to the node.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
:raises: NotImplementedError if the virt_type does not support
|
||||
setting the boot device.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
|
||||
"""
|
||||
node = task.node
|
||||
driver_info = _parse_driver_info(node)
|
||||
if device not in self.get_supported_boot_devices(task):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid boot device %s specified.") % device)
|
||||
driver_info['macs'] = driver_utils.get_node_mac_addresses(task)
|
||||
ssh_obj = _get_connection(node)
|
||||
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
virt_type = driver_info['virt_type']
|
||||
use_headless = driver_info['use_headless']
|
||||
|
||||
if virt_type == 'vbox':
|
||||
if use_headless:
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
LOG.debug("Forcing VBox VM %s to power off "
|
||||
"in order to set the boot device.",
|
||||
node_name)
|
||||
_power_off(ssh_obj, driver_info)
|
||||
|
||||
boot_device_map = _get_boot_device_map(driver_info['virt_type'])
|
||||
try:
|
||||
_set_boot_device(ssh_obj, driver_info, boot_device_map[device])
|
||||
except NotImplementedError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error("Failed to set boot device for node %(node)s, "
|
||||
"virt_type %(vtype)s does not support this "
|
||||
"operation", {'node': node.uuid,
|
||||
'vtype': driver_info['virt_type']})
|
||||
|
||||
def get_boot_device(self, task):
|
||||
"""Get the current boot device for the task's node.
|
||||
|
||||
Provides the current boot device of the node. Be aware that not
|
||||
all drivers support this.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect.
|
||||
:raises: MissingParameterValue if a required parameter is missing
|
||||
:raises: SSHConnectFailed if ssh failed to connect to the node.
|
||||
:raises: SSHCommandFailed on an error from ssh.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:returns: a dictionary containing:
|
||||
|
||||
:boot_device: the boot device, one of
|
||||
:mod:`ironic.common.boot_devices` or None if it is unknown.
|
||||
:persistent: Whether the boot device will persist to all
|
||||
future boots or not, None if it is unknown.
|
||||
|
||||
"""
|
||||
node = task.node
|
||||
driver_info = _parse_driver_info(node)
|
||||
driver_info['macs'] = driver_utils.get_node_mac_addresses(task)
|
||||
ssh_obj = _get_connection(node)
|
||||
response = {'boot_device': None, 'persistent': None}
|
||||
try:
|
||||
response['boot_device'] = _get_boot_device(ssh_obj, driver_info)
|
||||
except NotImplementedError:
|
||||
LOG.warning("Failed to get boot device for node %(node)s, "
|
||||
"virt_type %(vtype)s does not support this "
|
||||
"operation",
|
||||
{'node': node.uuid, 'vtype': driver_info['virt_type']})
|
||||
return response
|
||||
|
||||
def get_sensors_data(self, task):
|
||||
"""Get sensors data.
|
||||
|
||||
Not implemented by this driver.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ShellinaboxConsole(base.ConsoleInterface):
|
||||
"""A ConsoleInterface that uses ssh and shellinabox."""
|
||||
|
||||
def get_properties(self):
|
||||
properties = COMMON_PROPERTIES.copy()
|
||||
properties.update(CONSOLE_PROPERTIES)
|
||||
return properties
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the Node console info.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: MissingParameterValue if required ssh parameters are
|
||||
missing
|
||||
:raises: InvalidParameterValue if required parameters are invalid.
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
|
||||
if driver_info['virt_type'] != 'virsh':
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"not supported for non-virsh types"))
|
||||
|
||||
if not driver_info['terminal_port']:
|
||||
raise exception.MissingParameterValue(_(
|
||||
"Missing 'ssh_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 ssh 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 parameters are invalid.
|
||||
"""
|
||||
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
driver_info['macs'] = driver_utils.get_node_mac_addresses(task)
|
||||
ssh_obj = _get_connection(task.node)
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
|
||||
ssh_cmd = ("/:%(uid)s:%(gid)s:HOME:virsh console %(node)s"
|
||||
% {'uid': os.getuid(),
|
||||
'gid': os.getgid(),
|
||||
'node': node_name})
|
||||
|
||||
console_utils.start_shellinabox_console(driver_info['uuid'],
|
||||
driver_info['terminal_port'],
|
||||
ssh_cmd)
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
console_utils.stop_shellinabox_console(task.node.uuid)
|
||||
|
||||
def get_console(self, task):
|
||||
"""Get the type and connection information about the console.
|
||||
|
||||
:param task: a task from TaskManager
|
||||
:raises: MissingParameterValue if required ssh 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['terminal_port'])
|
||||
return {'type': 'shellinabox', 'url': url}
|
@ -41,7 +41,6 @@ from ironic.drivers.modules.irmc import power as irmc_power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import snmp
|
||||
from ironic.drivers.modules import ssh
|
||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||
from ironic.drivers.modules.ucs import power as ucs_power
|
||||
|
||||
@ -51,32 +50,6 @@ PXEAndIPMIToolDriver = ipmi.PXEAndIPMIToolDriver
|
||||
PXEAndIPMIToolAndSocatDriver = ipmi.PXEAndIPMIToolAndSocatDriver
|
||||
|
||||
|
||||
class PXEAndSSHDriver(base.BaseDriver):
|
||||
"""PXE + SSH driver.
|
||||
|
||||
NOTE: This driver is meant only for testing environments.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.modules.ssh.SSH` for power on/off and
|
||||
reboot of virtual machines tunneled over SSH, with
|
||||
:class:`ironic.drivers.modules.iscsi_deploy.ISCSIDeploy` for
|
||||
image deployment. Implementations are in those respective
|
||||
classes; this class is merely the glue between them.
|
||||
"""
|
||||
|
||||
supported = False
|
||||
|
||||
def __init__(self):
|
||||
self.power = ssh.SSHPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
self.management = ssh.SSHManagement()
|
||||
self.inspect = inspector.Inspector.create_if_enabled(
|
||||
'PXEAndSSHDriver')
|
||||
self.raid = agent.AgentRAID()
|
||||
self.console = ssh.ShellinaboxConsole()
|
||||
|
||||
|
||||
class PXEAndIloDriver(base.BaseDriver):
|
||||
"""PXE + Ilo Driver using IloClient interface.
|
||||
|
||||
|
@ -1045,12 +1045,12 @@ class TestListNodes(test_api_base.BaseApiTest):
|
||||
def test_get_nodes_by_driver(self):
|
||||
node = obj_utils.create_test_node(self.context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
driver='pxe_ssh')
|
||||
driver='pxe_ipmitool')
|
||||
node1 = obj_utils.create_test_node(self.context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
driver='fake')
|
||||
|
||||
data = self.get_json('/nodes?driver=pxe_ssh',
|
||||
data = self.get_json('/nodes?driver=pxe_ipmitool',
|
||||
headers={api_base.Version.string: "1.16"})
|
||||
uuids = [n['uuid'] for n in data['nodes']]
|
||||
self.assertIn(node.uuid, uuids)
|
||||
|
@ -4979,13 +4979,6 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
|
||||
]
|
||||
self._check_driver_properties("fake_ipmitool", expected)
|
||||
|
||||
def test_driver_properties_fake_ssh(self):
|
||||
expected = ['ssh_address', 'ssh_username',
|
||||
'vbox_use_headless', 'ssh_virt_type',
|
||||
'ssh_key_contents', 'ssh_key_filename',
|
||||
'ssh_password', 'ssh_port', 'ssh_terminal_port']
|
||||
self._check_driver_properties("fake_ssh", expected)
|
||||
|
||||
def test_driver_properties_fake_pxe(self):
|
||||
expected = ['deploy_kernel', 'deploy_ramdisk',
|
||||
'deploy_forces_oob_reboot']
|
||||
@ -5006,15 +4999,6 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
|
||||
'ipmi_force_boot_device', 'deploy_forces_oob_reboot']
|
||||
self._check_driver_properties("pxe_ipmitool", expected)
|
||||
|
||||
def test_driver_properties_pxe_ssh(self):
|
||||
expected = ['deploy_kernel', 'deploy_ramdisk',
|
||||
'ssh_address', 'ssh_username',
|
||||
'vbox_use_headless', 'ssh_virt_type',
|
||||
'ssh_key_contents', 'ssh_key_filename',
|
||||
'ssh_password', 'ssh_port', 'ssh_terminal_port',
|
||||
'deploy_forces_oob_reboot']
|
||||
self._check_driver_properties("pxe_ssh", expected)
|
||||
|
||||
def test_driver_properties_pxe_snmp(self):
|
||||
expected = ['deploy_kernel', 'deploy_ramdisk',
|
||||
'snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
|
||||
|
@ -48,34 +48,6 @@ def get_test_ipmi_bridging_parameters():
|
||||
}
|
||||
|
||||
|
||||
def get_test_ssh_info(auth_type='password', virt_type='virsh'):
|
||||
result = {
|
||||
"ssh_address": "1.2.3.4",
|
||||
"ssh_username": "admin",
|
||||
"ssh_port": 22,
|
||||
"ssh_virt_type": virt_type,
|
||||
}
|
||||
if 'password' == auth_type:
|
||||
result['ssh_password'] = 'fake'
|
||||
elif 'file' == auth_type:
|
||||
result['ssh_key_filename'] = '/not/real/file'
|
||||
elif 'key' == auth_type:
|
||||
result['ssh_key_contents'] = '--BEGIN PRIVATE ...blah'
|
||||
elif 'file_with_passphrase' == auth_type:
|
||||
result['ssh_password'] = 'fake'
|
||||
result['ssh_key_filename'] = '/not/real/file'
|
||||
elif 'key_with_passphrase' == auth_type:
|
||||
result['ssh_password'] = 'fake'
|
||||
result['ssh_key_contents'] = '--BEGIN PRIVATE ...blah'
|
||||
elif 'too_many' == auth_type:
|
||||
result['ssh_key_contents'] = '--BEGIN PRIVATE ...blah'
|
||||
result['ssh_key_filename'] = '/not/real/file'
|
||||
else:
|
||||
# No auth details (is invalid)
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
def get_test_pxe_driver_info():
|
||||
return {
|
||||
"deploy_kernel": "glance://deploy_kernel_uuid",
|
||||
|
@ -27,8 +27,8 @@ from ironic.tests.unit.objects import utils as obj_utils
|
||||
class DisabledTestCase(db_base.DbTestCase):
|
||||
def _do_mock(self):
|
||||
# NOTE(dtantsur): fake driver always has inspection, using another one
|
||||
mgr_utils.mock_the_extension_manager("pxe_ssh")
|
||||
self.driver = driver_factory.get_driver("pxe_ssh")
|
||||
mgr_utils.mock_the_extension_manager("pxe_ipmitool")
|
||||
self.driver = driver_factory.get_driver("pxe_ipmitool")
|
||||
|
||||
def test_disabled(self):
|
||||
self.config(enabled=False, group='inspector')
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -35,7 +35,6 @@ from ironic.drivers.modules.irmc import power as irmc_power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules import pxe as pxe_module
|
||||
from ironic.drivers.modules import snmp
|
||||
from ironic.drivers.modules import ssh
|
||||
from ironic.drivers.modules.ucs import management as ucs_management
|
||||
from ironic.drivers.modules.ucs import power as ucs_power
|
||||
from ironic.drivers import pxe
|
||||
@ -43,16 +42,6 @@ from ironic.drivers import pxe
|
||||
|
||||
class PXEDriversTestCase(testtools.TestCase):
|
||||
|
||||
def test_pxe_ssh_driver(self):
|
||||
driver = pxe.PXEAndSSHDriver()
|
||||
|
||||
self.assertIsInstance(driver.power, ssh.SSHPower)
|
||||
self.assertIsInstance(driver.boot, pxe_module.PXEBoot)
|
||||
self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy)
|
||||
self.assertIsInstance(driver.management, ssh.SSHManagement)
|
||||
self.assertIsNone(driver.inspect)
|
||||
self.assertIsInstance(driver.raid, agent.AgentRAID)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_ilo_driver(self, try_import_mock):
|
||||
|
9
releasenotes/notes/no-ssh-drivers-6ee5ff4c3ecdd3fb.yaml
Normal file
9
releasenotes/notes/no-ssh-drivers-6ee5ff4c3ecdd3fb.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
SSH-based power and management driver interfaces were removed from ironic.
|
||||
The drivers ``pxe_ssh``, ``agent_ssh`` and ``fake_ssh`` are no longer
|
||||
available.
|
||||
Operators are required to ensure that these drivers are not used
|
||||
or enabled (in ``[DEFAULT]enabled_drivers`` configuration file option)
|
||||
in their ironic installation before upgrade.
|
@ -7,7 +7,6 @@ alembic>=0.8.10 # MIT
|
||||
automaton>=0.5.0 # Apache-2.0
|
||||
eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT
|
||||
WebOb>=1.7.1 # MIT
|
||||
paramiko>=2.0 # LGPLv2.1+
|
||||
python-cinderclient>=3.1.0 # Apache-2.0
|
||||
python-neutronclient>=6.3.0 # Apache-2.0
|
||||
python-glanceclient>=2.7.0 # Apache-2.0
|
||||
|
@ -50,7 +50,6 @@ ironic.drivers =
|
||||
agent_ipmitool_socat = ironic.drivers.ipmi:AgentAndIPMIToolAndSocatDriver
|
||||
agent_irmc = ironic.drivers.irmc:IRMCVirtualMediaAgentDriver
|
||||
agent_pxe_oneview = ironic.drivers.oneview:AgentPXEOneViewDriver
|
||||
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
|
||||
agent_ucs = ironic.drivers.agent:AgentAndUcsDriver
|
||||
fake = ironic.drivers.fake:FakeDriver
|
||||
fake_soft_power = ironic.drivers.fake:FakeSoftPowerDriver
|
||||
@ -58,7 +57,6 @@ ironic.drivers =
|
||||
fake_inspector = ironic.drivers.fake:FakeIPMIToolInspectorDriver
|
||||
fake_ipmitool = ironic.drivers.fake:FakeIPMIToolDriver
|
||||
fake_ipmitool_socat = ironic.drivers.fake:FakeIPMIToolSocatDriver
|
||||
fake_ssh = ironic.drivers.fake:FakeSSHDriver
|
||||
fake_pxe = ironic.drivers.fake:FakePXEDriver
|
||||
fake_ilo = ironic.drivers.fake:FakeIloDriver
|
||||
fake_drac = ironic.drivers.fake:FakeDracDriver
|
||||
@ -72,7 +70,6 @@ ironic.drivers =
|
||||
iscsi_pxe_oneview = ironic.drivers.oneview:ISCSIPXEOneViewDriver
|
||||
pxe_ipmitool = ironic.drivers.ipmi:PXEAndIPMIToolDriver
|
||||
pxe_ipmitool_socat = ironic.drivers.ipmi:PXEAndIPMIToolAndSocatDriver
|
||||
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
|
||||
pxe_ilo = ironic.drivers.pxe:PXEAndIloDriver
|
||||
pxe_drac = ironic.drivers.drac:PXEDracDriver
|
||||
pxe_drac_inspector = ironic.drivers.drac:PXEDracInspectorDriver
|
||||
|
@ -116,7 +116,7 @@
|
||||
}
|
||||
- {
|
||||
section: 'DEFAULT',
|
||||
option: 'enabled_drivers', value: 'pxe_ssh, agent_ssh, fake'
|
||||
option: 'enabled_drivers', value: 'pxe_ipmitool, agent_ipmitool, fake'
|
||||
# All other testing drivers require add'l packages
|
||||
# and should be enabled locally, if desired
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user