From 3d9895cc0b6b9e41cdc5cc389d10504cd31ffa9d Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Fri, 7 Jul 2017 18:23:02 +0300 Subject: [PATCH] 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 --- devstack/lib/ironic | 79 +- doc/source/admin/drivers.rst | 9 - doc/source/admin/drivers/ilo.rst | 4 +- doc/source/admin/drivers/oneview.rst | 2 +- doc/source/admin/drivers/xenserver.rst | 41 - doc/source/admin/inspection.rst | 2 - doc/source/admin/report.txt | 7 - doc/source/contributor/dev-quickstart.rst | 5 +- .../ironic-multitenant-networking.rst | 5 +- doc/source/install/get_started.rst | 3 +- doc/source/install/standalone.rst | 2 +- etc/ironic/ironic.conf.sample | 19 - ironic/common/exception.py | 8 - ironic/common/utils.py | 41 - ironic/conf/__init__.py | 2 - ironic/conf/opts.py | 2 - ironic/conf/ssh.py | 38 - ironic/drivers/agent.py | 27 - ironic/drivers/fake.py | 13 - ironic/drivers/modules/ssh.py | 902 ------------ ironic/drivers/pxe.py | 27 - ironic/tests/unit/api/v1/test_nodes.py | 4 +- ironic/tests/unit/conductor/test_manager.py | 16 - ironic/tests/unit/db/utils.py | 28 - .../unit/drivers/modules/test_inspector.py | 4 +- ironic/tests/unit/drivers/modules/test_ssh.py | 1247 ----------------- ironic/tests/unit/drivers/test_pxe.py | 11 - .../no-ssh-drivers-6ee5ff4c3ecdd3fb.yaml | 9 + requirements.txt | 1 - setup.cfg | 3 - vagrant.yaml | 2 +- 31 files changed, 25 insertions(+), 2538 deletions(-) delete mode 100644 doc/source/admin/drivers/xenserver.rst delete mode 100644 ironic/conf/ssh.py delete mode 100644 ironic/drivers/modules/ssh.py delete mode 100644 ironic/tests/unit/drivers/modules/test_ssh.py create mode 100644 releasenotes/notes/no-ssh-drivers-6ee5ff4c3ecdd3fb.yaml diff --git a/devstack/lib/ironic b/devstack/lib/ironic index e6e2645854..b9dfada646 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -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 diff --git a/doc/source/admin/drivers.rst b/doc/source/admin/drivers.rst index eef6e503ec..ac1d3a6544 100644 --- a/doc/source/admin/drivers.rst +++ b/doc/source/admin/drivers.rst @@ -83,15 +83,6 @@ OneView driver drivers/oneview -XenServer ssh driver --------------------- - -.. toctree:: - :maxdepth: 1 - - drivers/xenserver - - Redfish driver -------------- diff --git a/doc/source/admin/drivers/ilo.rst b/doc/source/admin/drivers/ilo.rst index 295aa418ce..7e945b6843 100644 --- a/doc/source/admin/drivers/ilo.rst +++ b/doc/source/admin/drivers/ilo.rst @@ -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:: diff --git a/doc/source/admin/drivers/oneview.rst b/doc/source/admin/drivers/oneview.rst index 07e2b1e6b8..3a9e0da443 100644 --- a/doc/source/admin/drivers/oneview.rst +++ b/doc/source/admin/drivers/oneview.rst @@ -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. diff --git a/doc/source/admin/drivers/xenserver.rst b/doc/source/admin/drivers/xenserver.rst deleted file mode 100644 index e866059d34..0000000000 --- a/doc/source/admin/drivers/xenserver.rst +++ /dev/null @@ -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= xenstore-data:vm-data="vm_data/disable_pf: 1" - - diff --git a/doc/source/admin/inspection.rst b/doc/source/admin/inspection.rst index f517b3476b..1611d60ae8 100644 --- a/doc/source/admin/inspection.rst +++ b/doc/source/admin/inspection.rst @@ -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:: diff --git a/doc/source/admin/report.txt b/doc/source/admin/report.txt index 6160705793..fb5aa8ad8b 100644 --- a/doc/source/admin/report.txt +++ b/doc/source/admin/report.txt @@ -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 diff --git a/doc/source/contributor/dev-quickstart.rst b/doc/source/contributor/dev-quickstart.rst index f9c0566dca..f9f48ca896 100644 --- a/doc/source/contributor/dev-quickstart.rst +++ b/doc/source/contributor/dev-quickstart.rst @@ -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:: diff --git a/doc/source/contributor/ironic-multitenant-networking.rst b/doc/source/contributor/ironic-multitenant-networking.rst index afbaa8ce86..df091c0b84 100644 --- a/doc/source/contributor/ironic-multitenant-networking.rst +++ b/doc/source/contributor/ironic-multitenant-networking.rst @@ -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. diff --git a/doc/source/install/get_started.rst b/doc/source/install/get_started.rst index c394b297a9..3ab9d7b5e4 100644 --- a/doc/source/install/get_started.rst +++ b/doc/source/install/get_started.rst @@ -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 diff --git a/doc/source/install/standalone.rst b/doc/source/install/standalone.rst index 16c6bbc22e..7a12d159cd 100644 --- a/doc/source/install/standalone.rst +++ b/doc/source/install/standalone.rst @@ -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:: diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index e82e15fd5e..be3c1327a5 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -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] # diff --git a/ironic/common/exception.py b/ironic/common/exception.py index 745d01b1cd..f619b5b12a 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -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).') diff --git a/ironic/common/utils.py b/ironic/common/utils.py index c6085d74ed..963e1d2f86 100644 --- a/ironic/common/utils.py +++ b/ironic/common/utils.py @@ -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. diff --git a/ironic/conf/__init__.py b/ironic/conf/__init__.py index d490ecb7e3..d6f85efce6 100644 --- a/ironic/conf/__init__.py +++ b/ironic/conf/__init__.py @@ -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) diff --git a/ironic/conf/opts.py b/ironic/conf/opts.py index bf522c17b6..b8e2c9d033 100644 --- a/ironic/conf/opts.py +++ b/ironic/conf/opts.py @@ -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', diff --git a/ironic/conf/ssh.py b/ironic/conf/ssh.py deleted file mode 100644 index 4ae4aabc13..0000000000 --- a/ironic/conf/ssh.py +++ /dev/null @@ -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') diff --git a/ironic/drivers/agent.py b/ironic/drivers/agent.py index 6a34723569..75502fd5dd 100644 --- a/ironic/drivers/agent.py +++ b/ironic/drivers/agent.py @@ -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. diff --git a/ironic/drivers/fake.py b/ironic/drivers/fake.py index 441ed4a16f..d43f83def9 100644 --- a/ironic/drivers/fake.py +++ b/ironic/drivers/fake.py @@ -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.""" diff --git a/ironic/drivers/modules/ssh.py b/ironic/drivers/modules/ssh.py deleted file mode 100644 index 5b4aa0627a..0000000000 --- a/ironic/drivers/modules/ssh.py +++ /dev/null @@ -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 '//d;" - "/<\/os>/i\'\" " - "{_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" {}') - 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} diff --git a/ironic/drivers/pxe.py b/ironic/drivers/pxe.py index f21db501e4..3fff137e46 100644 --- a/ironic/drivers/pxe.py +++ b/ironic/drivers/pxe.py @@ -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. diff --git a/ironic/tests/unit/api/v1/test_nodes.py b/ironic/tests/unit/api/v1/test_nodes.py index 02c2be9ba2..9ded998ea4 100644 --- a/ironic/tests/unit/api/v1/test_nodes.py +++ b/ironic/tests/unit/api/v1/test_nodes.py @@ -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) diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index dda03594cf..7873b4ff76 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -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', diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index 75f6469a11..7c5244d3e2 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -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", diff --git a/ironic/tests/unit/drivers/modules/test_inspector.py b/ironic/tests/unit/drivers/modules/test_inspector.py index bcd197b4c8..73b1abcb07 100644 --- a/ironic/tests/unit/drivers/modules/test_inspector.py +++ b/ironic/tests/unit/drivers/modules/test_inspector.py @@ -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') diff --git a/ironic/tests/unit/drivers/modules/test_ssh.py b/ironic/tests/unit/drivers/modules/test_ssh.py deleted file mode 100644 index 008c0a8b07..0000000000 --- a/ironic/tests/unit/drivers/modules/test_ssh.py +++ /dev/null @@ -1,1247 +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. - -"""Test class for Ironic SSH power driver.""" - -import tempfile - -import mock -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_utils import uuidutils -import paramiko - -from ironic.common import boot_devices -from ironic.common import driver_factory -from ironic.common import exception -from ironic.common import states -from ironic.common import utils -from ironic.conductor import task_manager -from ironic.drivers.modules import console_utils -from ironic.drivers.modules import ssh -from ironic.drivers import utils as driver_utils -from ironic.tests.unit.conductor import mgr_utils -from ironic.tests.unit.db import base as db_base -from ironic.tests.unit.db import utils as db_utils -from ironic.tests.unit.objects import utils as obj_utils - - -CONF = cfg.CONF - - -class SSHValidateParametersTestCase(db_base.DbTestCase): - - def test__parse_driver_info_good_password(self): - # make sure we get back the expected things - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info('password')) - info = ssh._parse_driver_info(node) - self.assertEqual('1.2.3.4', info['host']) - self.assertEqual('admin', info['username']) - self.assertEqual('fake', info['password']) - self.assertEqual(22, info['port']) - self.assertEqual('virsh', info['virt_type']) - self.assertIsNotNone(info['cmd_set']) - self.assertEqual('1be26c0b-03f2-4d2e-ae87-c02d7f33c123', - info['uuid']) - - def test__parse_driver_info_good_key(self): - # make sure we get back the expected things - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info('key')) - info = ssh._parse_driver_info(node) - self.assertEqual('1.2.3.4', info['host']) - self.assertEqual('admin', info['username']) - self.assertEqual('--BEGIN PRIVATE ...blah', info['key_contents']) - self.assertEqual(22, info['port']) - self.assertEqual('virsh', info['virt_type']) - self.assertIsNotNone(info['cmd_set']) - self.assertEqual('1be26c0b-03f2-4d2e-ae87-c02d7f33c123', - info['uuid']) - - def test__parse_driver_info_good_file(self): - # make sure we get back the expected things - d_info = db_utils.get_test_ssh_info('file') - tempdir = tempfile.mkdtemp() - key_path = tempdir + '/foo' - open(key_path, 'wt').close() - d_info['ssh_key_filename'] = key_path - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=d_info) - info = ssh._parse_driver_info(node) - self.assertEqual('1.2.3.4', info['host']) - self.assertEqual('admin', info['username']) - self.assertEqual(key_path, info['key_filename']) - self.assertEqual(22, info['port']) - self.assertEqual('virsh', info['virt_type']) - self.assertIsNotNone(info['cmd_set']) - self.assertEqual('1be26c0b-03f2-4d2e-ae87-c02d7f33c123', - info['uuid']) - - def test__parse_driver_info_good_file_with_passphrase(self): - # make sure we get back the expected things - d_info = db_utils.get_test_ssh_info('file_with_passphrase') - tempdir = tempfile.mkdtemp() - key_path = tempdir + '/foo' - open(key_path, 'wt').close() - d_info['ssh_key_filename'] = key_path - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=d_info) - info = ssh._parse_driver_info(node) - self.assertEqual('1.2.3.4', info['host']) - self.assertEqual('admin', info['username']) - self.assertEqual('fake', info['password']) - self.assertEqual(key_path, info['key_filename']) - self.assertEqual(22, info['port']) - self.assertEqual('virsh', info['virt_type']) - self.assertIsNotNone(info['cmd_set']) - self.assertEqual('1be26c0b-03f2-4d2e-ae87-c02d7f33c123', - info['uuid']) - - def test__parse_driver_info_good_key_with_passphrase(self): - # make sure we get back the expected things - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info('key_with_passphrase')) - info = ssh._parse_driver_info(node) - self.assertEqual('1.2.3.4', info['host']) - self.assertEqual('admin', info['username']) - self.assertEqual('fake', info['password']) - self.assertEqual('--BEGIN PRIVATE ...blah', info['key_contents']) - self.assertEqual(22, info['port']) - self.assertEqual('virsh', info['virt_type']) - self.assertIsNotNone(info['cmd_set']) - self.assertEqual('1be26c0b-03f2-4d2e-ae87-c02d7f33c123', - info['uuid']) - - def test__parse_driver_info_bad_file(self): - # A filename that doesn't exist errors. - info = db_utils.get_test_ssh_info('file') - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=info) - self.assertRaises( - exception.InvalidParameterValue, ssh._parse_driver_info, node) - - def test__parse_driver_info_too_many(self): - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info('too_many')) - self.assertRaises( - exception.InvalidParameterValue, ssh._parse_driver_info, node) - - def test__parse_driver_info_missing_host(self): - # make sure error is raised when info is missing - info = db_utils.get_test_ssh_info() - del info['ssh_address'] - node = obj_utils.get_test_node(self.context, driver_info=info) - self.assertRaises(exception.MissingParameterValue, - ssh._parse_driver_info, - node) - - def test__parse_driver_info_missing_user(self): - # make sure error is raised when info is missing - info = db_utils.get_test_ssh_info() - del info['ssh_username'] - node = obj_utils.get_test_node(self.context, driver_info=info) - self.assertRaises(exception.MissingParameterValue, - ssh._parse_driver_info, - node) - - def test__parse_driver_info_invalid_creds(self): - # make sure error is raised when info is missing - info = db_utils.get_test_ssh_info('no-creds') - node = obj_utils.get_test_node(self.context, driver_info=info) - self.assertRaises(exception.InvalidParameterValue, - ssh._parse_driver_info, - node) - - def test__parse_driver_info_missing_virt_type(self): - # make sure error is raised when info is missing - info = db_utils.get_test_ssh_info() - del info['ssh_virt_type'] - node = obj_utils.get_test_node(self.context, driver_info=info) - self.assertRaises(exception.MissingParameterValue, - ssh._parse_driver_info, - node) - - def test__parse_driver_info_ssh_port_wrong_type(self): - # make sure error is raised when ssh_port is not integer - info = db_utils.get_test_ssh_info() - info['ssh_port'] = 'wrong_port_value' - node = obj_utils.get_test_node(self.context, driver_info=info) - self.assertRaises(exception.InvalidParameterValue, - ssh._parse_driver_info, - node) - - def test__parse_driver_info_with_custom_libvirt_uri(self): - CONF.set_override('libvirt_uri', 'qemu:///foo', 'ssh') - expected_base_cmd = "LC_ALL=C /usr/bin/virsh --connect qemu:///foo" - - node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info()) - node['driver_info']['ssh_virt_type'] = 'virsh' - info = ssh._parse_driver_info(node) - self.assertEqual(expected_base_cmd, info['cmd_set']['base_cmd']) - - def test__get_boot_device_map_parallels(self): - boot_map = ssh._get_boot_device_map('parallels') - self.assertEqual('net0', boot_map[boot_devices.PXE]) - - def test__get_boot_device_map_vbox(self): - boot_map = ssh._get_boot_device_map('vbox') - self.assertEqual('net', boot_map[boot_devices.PXE]) - - def test__get_boot_device_map_xenserver(self): - boot_map = ssh._get_boot_device_map('xenserver') - self.assertEqual('n', boot_map[boot_devices.PXE]) - - def test__get_boot_device_map_exception(self): - self.assertRaises(exception.InvalidParameterValue, - ssh._get_boot_device_map, - 'this_doesn_t_exist') - - -class SSHPrivateMethodsTestCase(db_base.DbTestCase): - - def setUp(self): - super(SSHPrivateMethodsTestCase, self).setUp() - self.node = obj_utils.get_test_node( - self.context, - driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info()) - self.sshclient = paramiko.SSHClient() - - @mock.patch.object(utils, 'ssh_connect', autospec=True) - def test__get_connection_client(self, ssh_connect_mock): - ssh_connect_mock.return_value = self.sshclient - client = ssh._get_connection(self.node) - self.assertEqual(self.sshclient, client) - driver_info = ssh._parse_driver_info(self.node) - ssh_connect_mock.assert_called_once_with(driver_info) - - @mock.patch.object(utils, 'ssh_connect', autospec=True) - def test__get_connection_exception(self, ssh_connect_mock): - ssh_connect_mock.side_effect = exception.SSHConnectFailed(host='fake') - self.assertRaises(exception.SSHConnectFailed, - ssh._get_connection, - self.node) - driver_info = ssh._parse_driver_info(self.node) - ssh_connect_mock.assert_called_once_with(driver_info) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - def test__ssh_execute(self, exec_ssh_mock): - ssh_cmd = "somecmd" - expected = ['a', 'b', 'c'] - exec_ssh_mock.return_value = ('\n'.join(expected), '') - lst = ssh._ssh_execute(self.sshclient, ssh_cmd) - exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd) - self.assertEqual(expected, lst) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - def test__ssh_execute_exception(self, exec_ssh_mock): - ssh_cmd = "somecmd" - exec_ssh_mock.side_effect = processutils.ProcessExecutionError - self.assertRaises(exception.SSHCommandFailed, - ssh._ssh_execute, - self.sshclient, - ssh_cmd) - exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__get_power_status_on_unquoted(self, get_hosts_name_mock, - exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - exec_ssh_mock.return_value = ( - 'ExactNodeName', '') - get_hosts_name_mock.return_value = "ExactNodeName" - - pstate = ssh._get_power_status(self.sshclient, info) - - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_running']) - self.assertEqual(states.POWER_ON, pstate) - exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__get_power_status_on(self, get_hosts_name_mock, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - exec_ssh_mock.return_value = ( - '"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}', '') - get_hosts_name_mock.return_value = "NodeName" - - pstate = ssh._get_power_status(self.sshclient, info) - - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_running']) - self.assertEqual(states.POWER_ON, pstate) - exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__get_power_status_off(self, get_hosts_name_mock, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - exec_ssh_mock.return_value = ( - '"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}', '') - get_hosts_name_mock.return_value = "NotNodeName" - - pstate = ssh._get_power_status(self.sshclient, info) - - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_running']) - self.assertEqual(states.POWER_OFF, pstate) - exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - def test__get_power_status_exception(self, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - exec_ssh_mock.side_effect = processutils.ProcessExecutionError - - self.assertRaises(exception.SSHCommandFailed, - ssh._get_power_status, - self.sshclient, - info) - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_all']) - exec_ssh_mock.assert_called_once_with( - self.sshclient, ssh_cmd) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__get_power_status_correct_node(self, get_hosts_name_mock, - exec_ssh_mock): - # Bug: #1397834 test that get_power_status return status of - # baremeta_1 (off) and not baremetal_11 (on) - info = ssh._parse_driver_info(self.node) - exec_ssh_mock.return_value = ('"baremetal_11"\n"seed"\n', '') - get_hosts_name_mock.return_value = "baremetal_1" - - pstate = ssh._get_power_status(self.sshclient, info) - self.assertEqual(states.POWER_OFF, pstate) - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_running']) - exec_ssh_mock.assert_called_once_with(self.sshclient, ssh_cmd) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - def test__get_hosts_name_for_node_match(self, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_all']) - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['get_node_macs']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - exec_ssh_mock.side_effect = [('NodeName', ''), - ('52:54:00:cf:2d:31', '')] - expected = [mock.call(self.sshclient, ssh_cmd), - mock.call(self.sshclient, cmd_to_exec)] - - found_name = ssh._get_hosts_name_for_node(self.sshclient, info) - - self.assertEqual('NodeName', found_name) - self.assertEqual(expected, exec_ssh_mock.call_args_list) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - def test__get_hosts_name_for_node_no_match(self, exec_ssh_mock): - self.config(group='ssh', get_vm_name_attempts=2) - self.config(group='ssh', get_vm_name_retry_interval=0) - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "22:22:22:22:22:22"] - exec_ssh_mock.side_effect = ([('NodeName', ''), - ('52:54:00:cf:2d:31', '')] * 2) - - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_all']) - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['get_node_macs']) - - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - expected = [mock.call(self.sshclient, ssh_cmd), - mock.call(self.sshclient, cmd_to_exec)] * 2 - - self.assertRaises(exception.NodeNotFound, - ssh._get_hosts_name_for_node, self.sshclient, info) - self.assertEqual(expected, exec_ssh_mock.call_args_list) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - def test__get_hosts_name_for_node_match_after_retry(self, exec_ssh_mock): - self.config(group='ssh', get_vm_name_attempts=2) - self.config(group='ssh', get_vm_name_retry_interval=0) - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "22:22:22:22:22:22"] - exec_ssh_mock.side_effect = [('NodeName', ''), - ('', ''), - ('NodeName', ''), - ('11:11:11:11:11:11', '')] - - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_all']) - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['get_node_macs']) - - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - expected = [mock.call(self.sshclient, ssh_cmd), - mock.call(self.sshclient, cmd_to_exec)] * 2 - - found_name = ssh._get_hosts_name_for_node(self.sshclient, info) - - self.assertEqual('NodeName', found_name) - self.assertEqual(expected, exec_ssh_mock.call_args_list) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - def test__get_hosts_name_for_node_exception(self, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - ssh_cmd = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['list_all']) - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['get_node_macs']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - - exec_ssh_mock.side_effect = [('NodeName', ''), - processutils.ProcessExecutionError] - expected = [mock.call(self.sshclient, ssh_cmd), - mock.call(self.sshclient, cmd_to_exec)] - - self.assertRaises(exception.SSHCommandFailed, - ssh._get_hosts_name_for_node, - self.sshclient, - info) - self.assertEqual(expected, exec_ssh_mock.call_args_list) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__power_on_good(self, get_hosts_name_mock, get_power_status_mock, - exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - - get_power_status_mock.side_effect = [states.POWER_OFF, - states.POWER_ON] - get_hosts_name_mock.return_value = "NodeName" - expected = [mock.call(self.sshclient, info), - mock.call(self.sshclient, info)] - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['start_cmd']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - current_state = ssh._power_on(self.sshclient, info) - - self.assertEqual(states.POWER_ON, current_state) - self.assertEqual(expected, get_power_status_mock.call_args_list) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__power_on_fail(self, get_hosts_name_mock, get_power_status_mock, - exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_power_status_mock.side_effect = ([states.POWER_OFF, - states.POWER_OFF]) - get_hosts_name_mock.return_value = "NodeName" - expected = [mock.call(self.sshclient, info), - mock.call(self.sshclient, info)] - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['start_cmd']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - current_state = ssh._power_on(self.sshclient, info) - - self.assertEqual(states.ERROR, current_state) - self.assertEqual(expected, get_power_status_mock.call_args_list) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__power_on_exception(self, get_hosts_name_mock, - get_power_status_mock, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - - exec_ssh_mock.side_effect = processutils.ProcessExecutionError - get_power_status_mock.side_effect = ([states.POWER_OFF, - states.POWER_ON]) - get_hosts_name_mock.return_value = "NodeName" - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['start_cmd']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - - self.assertRaises(exception.SSHCommandFailed, - ssh._power_on, - self.sshclient, - info) - get_power_status_mock.assert_called_once_with(self.sshclient, info) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__power_off_good(self, get_hosts_name_mock, - get_power_status_mock, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_power_status_mock.side_effect = [states.POWER_ON, - states.POWER_OFF] - get_hosts_name_mock.return_value = "NodeName" - expected = [mock.call(self.sshclient, info), - mock.call(self.sshclient, info)] - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['stop_cmd']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - current_state = ssh._power_off(self.sshclient, info) - - self.assertEqual(states.POWER_OFF, current_state) - self.assertEqual(expected, get_power_status_mock.call_args_list) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__power_off_fail(self, get_hosts_name_mock, - get_power_status_mock, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_power_status_mock.side_effect = [states.POWER_ON, - states.POWER_ON] - get_hosts_name_mock.return_value = "NodeName" - expected = [mock.call(self.sshclient, info), - mock.call(self.sshclient, info)] - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['stop_cmd']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - current_state = ssh._power_off(self.sshclient, info) - - self.assertEqual(states.ERROR, current_state) - self.assertEqual(expected, get_power_status_mock.call_args_list) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec) - - @mock.patch.object(processutils, 'ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test__power_off_exception(self, get_hosts_name_mock, - get_power_status_mock, exec_ssh_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - exec_ssh_mock.side_effect = processutils.ProcessExecutionError - get_power_status_mock.side_effect = [states.POWER_ON, - states.POWER_OFF] - get_hosts_name_mock.return_value = "NodeName" - - cmd_to_exec = "%s %s" % (info['cmd_set']['base_cmd'], - info['cmd_set']['stop_cmd']) - cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName') - - self.assertRaises(exception.SSHCommandFailed, ssh._power_off, - self.sshclient, info) - get_power_status_mock.assert_called_once_with(self.sshclient, info) - get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - exec_ssh_mock.assert_called_once_with(self.sshclient, cmd_to_exec) - - -class SSHDriverTestCase(db_base.DbTestCase): - - def setUp(self): - super(SSHDriverTestCase, self).setUp() - mgr_utils.mock_the_extension_manager(driver="fake_ssh") - self.driver = driver_factory.get_driver("fake_ssh") - self.node = obj_utils.create_test_node( - self.context, driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info()) - self.port = obj_utils.create_test_port(self.context, - node_id=self.node.id) - self.sshclient = paramiko.SSHClient() - - @mock.patch.object(utils, 'ssh_connect', autospec=True) - def test__validate_info_ssh_connect_failed(self, ssh_connect_mock): - ssh_connect_mock.side_effect = exception.SSHConnectFailed(host='fake') - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertRaises(exception.InvalidParameterValue, - task.driver.power.validate, task) - driver_info = ssh._parse_driver_info(task.node) - ssh_connect_mock.assert_called_once_with(driver_info) - - def test_get_properties(self): - expected = ssh.COMMON_PROPERTIES - expected2 = list(ssh.COMMON_PROPERTIES) + list(ssh.CONSOLE_PROPERTIES) - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertEqual(expected, task.driver.power.get_properties()) - self.assertEqual(expected, task.driver.management.get_properties()) - self.assertEqual( - sorted(expected2), - sorted(task.driver.console.get_properties().keys())) - self.assertEqual( - sorted(expected2), - sorted(task.driver.get_properties().keys())) - - def test_validate_fail_no_port(self): - new_node = obj_utils.create_test_node( - self.context, - uuid='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - driver='fake_ssh', - driver_info=db_utils.get_test_ssh_info()) - with task_manager.acquire(self.context, new_node.uuid, - shared=True) as task: - self.assertRaises(exception.MissingParameterValue, - task.driver.power.validate, - task) - - @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_power_on', autospec=True) - def test_reboot_good(self, power_on_mock, get_conn_mock, - get_mac_addr_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_mac_addr_mock.return_value = info['macs'] - get_conn_mock.return_value = self.sshclient - power_on_mock.return_value = states.POWER_ON - with mock.patch.object(ssh, '_parse_driver_info', - autospec=True) as parse_drv_info_mock: - parse_drv_info_mock.return_value = info - with task_manager.acquire(self.context, info['uuid'], - shared=False) as task: - task.driver.power.reboot(task) - - parse_drv_info_mock.assert_called_once_with(task.node) - get_mac_addr_mock.assert_called_once_with(mock.ANY) - get_conn_mock.assert_called_once_with(task.node) - power_on_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_power_on', autospec=True) - def test_reboot_fail(self, power_on_mock, get_conn_mock, - get_mac_addr_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_mac_addr_mock.return_value = info['macs'] - get_conn_mock.return_value = self.sshclient - power_on_mock.return_value = states.POWER_OFF - with mock.patch.object(ssh, '_parse_driver_info', - autospec=True) as parse_drv_info_mock: - parse_drv_info_mock.return_value = info - with task_manager.acquire(self.context, info['uuid'], - shared=False) as task: - self.assertRaises(exception.PowerStateFailure, - task.driver.power.reboot, task) - parse_drv_info_mock.assert_called_once_with(task.node) - get_mac_addr_mock.assert_called_once_with(mock.ANY) - get_conn_mock.assert_called_once_with(task.node) - power_on_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - def test_set_power_state_bad_state(self, get_conn_mock, - get_mac_addr_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_mac_addr_mock.return_value = info['macs'] - get_conn_mock.return_value = self.sshclient - with mock.patch.object(ssh, '_parse_driver_info', - autospec=True) as parse_drv_info_mock: - parse_drv_info_mock.return_value = info - with task_manager.acquire(self.context, info['uuid'], - shared=False) as task: - self.assertRaises( - exception.InvalidParameterValue, - task.driver.power.set_power_state, - task, - "BAD_PSTATE") - - parse_drv_info_mock.assert_called_once_with(task.node) - get_mac_addr_mock.assert_called_once_with(mock.ANY) - get_conn_mock.assert_called_once_with(task.node) - - @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_power_on', autospec=True) - def test_set_power_state_on_good(self, power_on_mock, get_conn_mock, - get_mac_addr_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_mac_addr_mock.return_value = info['macs'] - get_conn_mock.return_value = self.sshclient - power_on_mock.return_value = states.POWER_ON - with mock.patch.object(ssh, '_parse_driver_info', - autospec=True) as parse_drv_info_mock: - parse_drv_info_mock.return_value = info - with task_manager.acquire(self.context, info['uuid'], - shared=False) as task: - task.driver.power.set_power_state(task, states.POWER_ON) - - parse_drv_info_mock.assert_called_once_with(task.node) - get_mac_addr_mock.assert_called_once_with(mock.ANY) - get_conn_mock.assert_called_once_with(task.node) - power_on_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_power_on', autospec=True) - def test_set_power_state_on_fail(self, power_on_mock, get_conn_mock, - get_mac_addr_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_mac_addr_mock.return_value = info['macs'] - get_conn_mock.return_value = self.sshclient - power_on_mock.return_value = states.POWER_OFF - with mock.patch.object(ssh, '_parse_driver_info', - autospec=True) as parse_drv_info_mock: - parse_drv_info_mock.return_value = info - with task_manager.acquire(self.context, info['uuid'], - shared=False) as task: - self.assertRaises( - exception.PowerStateFailure, - task.driver.power.set_power_state, - task, - states.POWER_ON) - - parse_drv_info_mock.assert_called_once_with(task.node) - get_mac_addr_mock.assert_called_once_with(mock.ANY) - get_conn_mock.assert_called_once_with(task.node) - power_on_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_power_off', autospec=True) - def test_set_power_state_off_good(self, power_off_mock, get_conn_mock, - get_mac_addr_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_mac_addr_mock.return_value = info['macs'] - get_conn_mock.return_value = self.sshclient - power_off_mock.return_value = states.POWER_OFF - with mock.patch.object(ssh, '_parse_driver_info', - autospec=True) as parse_drv_info_mock: - parse_drv_info_mock.return_value = info - with task_manager.acquire(self.context, info['uuid'], - shared=False) as task: - task.driver.power.set_power_state(task, states.POWER_OFF) - - parse_drv_info_mock.assert_called_once_with(task.node) - get_mac_addr_mock.assert_called_once_with(mock.ANY) - get_conn_mock.assert_called_once_with(task.node) - power_off_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_power_off', autospec=True) - def test_set_power_state_off_fail(self, power_off_mock, get_conn_mock, - get_mac_addr_mock): - info = ssh._parse_driver_info(self.node) - info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"] - get_mac_addr_mock.return_value = info['macs'] - get_conn_mock.return_value = self.sshclient - power_off_mock.return_value = states.POWER_ON - with mock.patch.object(ssh, '_parse_driver_info', - autospec=True) as parse_drv_info_mock: - parse_drv_info_mock.return_value = info - with task_manager.acquire(self.context, info['uuid'], - shared=False) as task: - self.assertRaises( - exception.PowerStateFailure, - task.driver.power.set_power_state, - task, - states.POWER_OFF) - - parse_drv_info_mock.assert_called_once_with(task.node) - get_mac_addr_mock.assert_called_once_with(mock.ANY) - get_conn_mock.assert_called_once_with(task.node) - power_off_mock.assert_called_once_with(self.sshclient, info) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_set_boot_device_vbox_ok(self, mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'vbox' - self.driver.management.set_boot_device(task, boot_devices.PXE) - expected_cmd = ('LC_ALL=C /usr/bin/VBoxManage modifyvm %s ' - '--boot1 net') % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_power_status', autospec=True) - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_set_boot_device_vbox_with_power_on( - self, mock_exc, mock_h, mock_get_conn, mock_get_power): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_get_conn.return_value = self.sshclient - # NOTE(jroll) _power_off calls _get_power_state twice - mock_get_power.side_effect = [ - states.POWER_ON, states.POWER_ON, states.POWER_OFF - ] - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'vbox' - task.node['driver_info']['vbox_use_headless'] = True - self.driver.management.set_boot_device(task, boot_devices.PXE) - - expected_cmds = [ - mock.call(mock.ANY, - 'LC_ALL=C /usr/bin/VBoxManage ' - 'controlvm %s poweroff' % fake_name), - mock.call(mock.ANY, - 'LC_ALL=C /usr/bin/VBoxManage ' - 'modifyvm %s --boot1 net' % fake_name) - ] - self.assertEqual(expected_cmds, mock_exc.call_args_list) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_set_boot_device_parallels_ok(self, mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'parallels' - self.driver.management.set_boot_device(task, boot_devices.PXE) - expected_cmd = ('LC_ALL=C /usr/bin/prlctl set %s ' - '--device-bootorder "net0"') % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_set_boot_device_virsh_ok(self, mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'virsh' - self.driver.management.set_boot_device(task, boot_devices.PXE) - expected_cmd = ('EDITOR="sed -i \'/' - '/d;/<\\/os>/i\\\'" ' - 'LC_ALL=C /usr/bin/virsh --connect qemu:///system ' - 'edit %s') % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_set_boot_device_xenserver_ok(self, - mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'xenserver' - self.driver.management.set_boot_device(task, boot_devices.PXE) - expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe vm-param-set uuid=%s " - "HVM-boot-params:order='n'") % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - def test_set_boot_device_bad_device(self): - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.InvalidParameterValue, - self.driver.management.set_boot_device, - task, 'invalid-device') - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test_set_boot_device_not_supported(self, mock_h, mock_get_conn): - mock_h.return_value = 'NodeName' - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - # vmware does not support set_boot_device() - task.node['driver_info']['ssh_virt_type'] = 'vmware' - self.assertRaises(NotImplementedError, - self.driver.management.set_boot_device, - task, boot_devices.PXE) - - def test_management_interface_get_supported_boot_devices(self): - with task_manager.acquire(self.context, self.node.uuid) as task: - expected = [boot_devices.PXE, boot_devices.DISK, - boot_devices.CDROM] - self.assertEqual(sorted(expected), sorted(task.driver.management. - get_supported_boot_devices(task))) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_get_boot_device_vbox(self, mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_exc.return_value = ('net', '') - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'vbox' - result = self.driver.management.get_boot_device(task) - self.assertEqual(boot_devices.PXE, result['boot_device']) - expected_cmd = ('LC_ALL=C /usr/bin/VBoxManage showvminfo ' - '--machinereadable %s ' - '| awk -F \'"\' \'/boot1/{print $2}\'') % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_get_boot_device_parallels(self, mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_exc.return_value = ('net0', '') - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'parallels' - result = self.driver.management.get_boot_device(task) - self.assertEqual(boot_devices.PXE, result['boot_device']) - expected_cmd = ('LC_ALL=C /usr/bin/prlctl list -i %s ' - '| awk \'/^Boot order:/ {print $3}\'') % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_get_boot_device_virsh(self, mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_exc.return_value = ('network', '') - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'virsh' - result = self.driver.management.get_boot_device(task) - self.assertEqual(boot_devices.PXE, result['boot_device']) - expected_cmd = ('LC_ALL=C /usr/bin/virsh --connect ' - 'qemu:///system dumpxml %s | awk \'/boot dev=/ ' - '{ gsub( ".*dev=" Q, "" ); gsub( Q ".*", "" ); ' - 'print; }\' Q="\'" RS="[<>]" | head -1') % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_management_interface_get_boot_device_xenserver(self, mock_exc, - mock_h, - mock_get_conn): - fake_name = 'fake-name' - mock_h.return_value = fake_name - mock_exc.return_value = ('n', '') - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'xenserver' - result = self.driver.management.get_boot_device(task) - self.assertEqual(boot_devices.PXE, result['boot_device']) - expected_cmd = ('LC_ALL=C /opt/xensource/bin/xe vm-param-get ' - 'uuid=%s --param-name=HVM-boot-params ' - 'param-key=order | cut -b 1') % fake_name - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - def test_get_boot_device_not_supported(self, mock_h, mock_get_conn): - mock_h.return_value = 'NodeName' - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - # vmware does not support get_boot_device() - task.node['driver_info']['ssh_virt_type'] = 'vmware' - expected = {'boot_device': None, 'persistent': None} - self.assertEqual(expected, - self.driver.management.get_boot_device(task)) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_get_power_state_vmware(self, mock_exc, mock_h, mock_get_conn): - # To see replacing {_NodeName_} in vmware's list_running - nodename = 'fakevm' - mock_h.return_value = nodename - mock_get_conn.return_value = self.sshclient - # list_running quotes names - mock_exc.return_value = ('"%s"' % nodename, '') - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'vmware' - power_state = self.driver.power.get_power_state(task) - self.assertEqual(states.POWER_ON, power_state) - expected_cmd = ("LC_ALL=C /bin/vim-cmd vmsvc/power.getstate " - "%(node)s | grep 'Powered on' >/dev/null && " - "echo '\"%(node)s\"' || true") % {'node': nodename} - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - def test_get_power_state_xenserver(self, mock_exc, mock_h, mock_get_conn): - # To see replacing {_NodeName_} in xenserver's list_running - nodename = 'fakevm' - mock_h.return_value = nodename - mock_get_conn.return_value = self.sshclient - mock_exc.return_value = (nodename, '') - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'xenserver' - power_state = self.driver.power.get_power_state(task) - self.assertEqual(states.POWER_ON, power_state) - expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe " - "vm-list power-state=running --minimal | tr ',' '\n'") - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - def test_start_command_xenserver(self, mock_power, mock_exc, mock_h, - mock_get_conn): - mock_power.side_effect = [states.POWER_OFF, states.POWER_ON] - nodename = 'fakevm' - mock_h.return_value = nodename - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'xenserver' - self.driver.power.set_power_state(task, states.POWER_ON) - expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe " - "vm-start uuid=fakevm && sleep 10s") - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - def test_stop_command_xenserver(self, mock_power, mock_exc, mock_h, - mock_get_conn): - mock_power.side_effect = [states.POWER_ON, states.POWER_OFF] - nodename = 'fakevm' - mock_h.return_value = nodename - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'xenserver' - self.driver.power.set_power_state(task, states.POWER_OFF) - expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe " - "vm-shutdown uuid=fakevm force=true") - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - def test_start_command_vbox(self, mock_power, mock_exc, mock_h, - mock_get_conn): - mock_power.side_effect = [states.POWER_OFF, states.POWER_ON] - nodename = 'fakevm' - mock_h.return_value = nodename - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'vbox' - self.driver.power.set_power_state(task, states.POWER_ON) - expected_cmd = 'LC_ALL=C /usr/bin/VBoxManage startvm fakevm' - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(ssh, '_ssh_execute', autospec=True) - @mock.patch.object(ssh, '_get_power_status', autospec=True) - def test_start_command_vbox_headless(self, mock_power, mock_exc, mock_h, - mock_get_conn): - mock_power.side_effect = [states.POWER_OFF, states.POWER_ON] - nodename = 'fakevm' - mock_h.return_value = nodename - mock_get_conn.return_value = self.sshclient - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node['driver_info']['ssh_virt_type'] = 'vbox' - task.node['driver_info']['vbox_use_headless'] = True - self.driver.power.set_power_state(task, states.POWER_ON) - expected_cmd = ('LC_ALL=C /usr/bin/VBoxManage ' - 'startvm fakevm --type headless') - mock_exc.assert_called_once_with(mock.ANY, expected_cmd) - - def test_management_interface_validate_good(self): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.management.validate(task) - - def test_management_interface_validate_fail(self): - # Missing SSH driver_info information - node = obj_utils.create_test_node(self.context, - uuid=uuidutils.generate_uuid(), - driver='fake_ssh') - with task_manager.acquire(self.context, node.uuid) as task: - self.assertRaises(exception.MissingParameterValue, - task.driver.management.validate, task) - - def test_console_validate(self): - with task_manager.acquire( - self.context, self.node.uuid, shared=True) as task: - task.node.driver_info['ssh_virt_type'] = 'virsh' - task.node.driver_info['ssh_terminal_port'] = 123 - task.driver.console.validate(task) - - def test_console_validate_missing_port(self): - with task_manager.acquire( - self.context, self.node.uuid, shared=True) as task: - task.node.driver_info['ssh_virt_type'] = 'virsh' - task.node.driver_info.pop('ssh_terminal_port', None) - self.assertRaises(exception.MissingParameterValue, - task.driver.console.validate, task) - - def test_console_validate_not_virsh(self): - with task_manager.acquire( - self.context, self.node.uuid, shared=True) as task: - task.node.driver_info = db_utils.get_test_ssh_info( - virt_type='vbox') - self.assertRaisesRegex(exception.InvalidParameterValue, - 'not supported for non-virsh types', - task.driver.console.validate, task) - - def test_console_validate_invalid_port(self): - with task_manager.acquire( - self.context, self.node.uuid, shared=True) as task: - task.node.driver_info['ssh_terminal_port'] = '' - self.assertRaisesRegex(exception.InvalidParameterValue, - 'is not a valid integer', - task.driver.console.validate, task) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(console_utils, 'start_shellinabox_console', - autospec=True) - def test_start_console(self, mock_exec, - get_hosts_name_mock, mock_get_conn): - info = ssh._parse_driver_info(self.node) - mock_exec.return_value = None - get_hosts_name_mock.return_value = "NodeName" - mock_get_conn.return_value = self.sshclient - - with task_manager.acquire(self.context, - self.node.uuid) as task: - self.driver.console.start_console(task) - - mock_exec.assert_called_once_with(info['uuid'], - info['terminal_port'], - mock.ANY) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(console_utils, 'start_shellinabox_console', - autospec=True) - def test_start_console_fail(self, mock_exec, - get_hosts_name_mock, mock_get_conn): - get_hosts_name_mock.return_value = "NodeName" - mock_get_conn.return_value = self.sshclient - mock_exec.side_effect = exception.ConsoleSubprocessFailed( - error='error') - - with task_manager.acquire(self.context, - self.node.uuid) as task: - self.assertRaises(exception.ConsoleSubprocessFailed, - self.driver.console.start_console, - task) - mock_exec.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY) - - @mock.patch.object(ssh, '_get_connection', autospec=True) - @mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True) - @mock.patch.object(console_utils, 'start_shellinabox_console', - autospec=True) - def test_start_console_fail_nodir(self, mock_exec, - get_hosts_name_mock, mock_get_conn): - get_hosts_name_mock.return_value = "NodeName" - mock_get_conn.return_value = self.sshclient - mock_exec.side_effect = exception.ConsoleError() - - with task_manager.acquire(self.context, - self.node.uuid) as task: - self.assertRaises(exception.ConsoleError, - self.driver.console.start_console, - task) - mock_exec.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY) - - @mock.patch.object(console_utils, 'stop_shellinabox_console', - autospec=True) - def test_stop_console(self, mock_exec): - mock_exec.return_value = None - - with task_manager.acquire(self.context, - self.node.uuid) as task: - self.driver.console.stop_console(task) - - mock_exec.assert_called_once_with(self.node.uuid) - - @mock.patch.object(console_utils, 'stop_shellinabox_console', - autospec=True) - def test_stop_console_fail(self, mock_stop): - mock_stop.side_effect = exception.ConsoleError() - - with task_manager.acquire(self.context, - self.node.uuid) as task: - self.assertRaises(exception.ConsoleError, - self.driver.console.stop_console, - task) - - mock_stop.assert_called_once_with(self.node.uuid) - - @mock.patch.object(console_utils, 'get_shellinabox_console_url', - autospec=True) - def test_get_console(self, mock_exec): - url = 'http://localhost:4201' - mock_exec.return_value = url - expected = {'type': 'shellinabox', 'url': url} - - with task_manager.acquire(self.context, - self.node.uuid) as task: - task.node.driver_info['ssh_terminal_port'] = 6900 - console_info = self.driver.console.get_console(task) - - self.assertEqual(expected, console_info) - mock_exec.assert_called_once_with(6900) diff --git a/ironic/tests/unit/drivers/test_pxe.py b/ironic/tests/unit/drivers/test_pxe.py index 4471cf0028..fb5d1a1943 100644 --- a/ironic/tests/unit/drivers/test_pxe.py +++ b/ironic/tests/unit/drivers/test_pxe.py @@ -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): diff --git a/releasenotes/notes/no-ssh-drivers-6ee5ff4c3ecdd3fb.yaml b/releasenotes/notes/no-ssh-drivers-6ee5ff4c3ecdd3fb.yaml new file mode 100644 index 0000000000..ac8cbba92f --- /dev/null +++ b/releasenotes/notes/no-ssh-drivers-6ee5ff4c3ecdd3fb.yaml @@ -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. diff --git a/requirements.txt b/requirements.txt index 41494be9ce..30d5e67b6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/setup.cfg b/setup.cfg index f04ebc0b8e..7b00393eeb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/vagrant.yaml b/vagrant.yaml index 055a4a9dc6..7feab7ad77 100644 --- a/vagrant.yaml +++ b/vagrant.yaml @@ -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 }