Restructuring driver API and inheritance.
Based on discussions during and after the Ironic team meeting on June 03, regarding support for substantially different driver work flows, this is a re-working of the internal driver API. tl;dr: The strict separation of "control" and "deploy" driver was an artefact of the ipmi + pxe implementation used in nova-baremetal, and does not map on to all drivers. Furthermore, the prior implementation did not accurately represent the separation of "core", "standard", and "vendor-specific" driver functionality. These changes impact the v1 API structure, but since that is largely not implemented yet, this change does not attempt to affect the public API itself. Highlights: - No more deploy + control driver; nodes have one and only one driver. This drops the deploy_driver and deploy_info parameters, and renames control_driver -> driver, and control_info -> driver_info. - Interfaces for core, standard, and vendor functionality now clearly defined in the driver API. - Improve Fake driver to demonstrate use of interfaces. - Convert IPMI and SSH driver classes into interfaces, and move to drivers/modules/ directory. - Stub for the pxe interfaces. - Stub implementations of pxe+ipmi and pxe+ssh drivers. - driver_info field uses more standard names, but requires driver-specific data to be in a nested object. Examples in tests/db/utils.py as before. A separate doc change will follow this to update the API v1 spec. Also includes some cosmetic cleanup of test_ssh.py and test_ipmi.py. Change-Id: I057ede8e07b1b57010e81ef58415debe0ba8b934
This commit is contained in:
parent
3c02cb68cd
commit
5e0e2492e9
|
@ -60,7 +60,8 @@ LOG = logging.getLogger(__name__)
|
|||
VALID_BOOT_DEVICES = ['pxe', 'disk', 'safe', 'cdrom', 'bios']
|
||||
|
||||
|
||||
# TODO(deva): use a contextmanager for this, and port it to nova.
|
||||
# TODO(deva): replace this with remove_path_on_error once that is available
|
||||
# https://review.openstack.org/#/c/31030
|
||||
def _make_password_file(password):
|
||||
fd, path = tempfile.mkstemp()
|
||||
os.fchmod(fd, stat.S_IRUSR | stat.S_IWUSR)
|
||||
|
@ -69,37 +70,38 @@ def _make_password_file(password):
|
|||
return path
|
||||
|
||||
|
||||
def _parse_control_info(node):
|
||||
info = json.loads(node.get('control_info', ''))
|
||||
address = info.get('ipmi_address', None)
|
||||
user = info.get('ipmi_username', None)
|
||||
password = info.get('ipmi_password', None)
|
||||
port = info.get('ipmi_terminal_port', None)
|
||||
def _parse_driver_info(node):
|
||||
driver_info = json.loads(node.get('driver_info', ''))
|
||||
ipmi_info = driver_info.get('ipmi')
|
||||
address = ipmi_info.get('address', None)
|
||||
username = ipmi_info.get('username', None)
|
||||
password = ipmi_info.get('password', None)
|
||||
port = ipmi_info.get('terminal_port', None)
|
||||
|
||||
if not address or not user or not password:
|
||||
if not address or not username or not password:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"IPMI credentials not supplied to IPMI driver."))
|
||||
|
||||
return {
|
||||
'address': address,
|
||||
'user': user,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'port': port,
|
||||
'uuid': node.get('uuid')
|
||||
}
|
||||
|
||||
|
||||
def _exec_ipmitool(c_info, command):
|
||||
def _exec_ipmitool(driver_info, command):
|
||||
args = ['ipmitool',
|
||||
'-I',
|
||||
'lanplus',
|
||||
'-H',
|
||||
c_info['address'],
|
||||
driver_info['address'],
|
||||
'-U',
|
||||
c_info['user'],
|
||||
driver_info['username'],
|
||||
'-f']
|
||||
try:
|
||||
pwfile = _make_password_file(c_info['password'])
|
||||
pwfile = _make_password_file(driver_info['password'])
|
||||
args.append(pwfile)
|
||||
args.extend(command.split(" "))
|
||||
out, err = utils.execute(*args, attempts=3)
|
||||
|
@ -110,7 +112,7 @@ def _exec_ipmitool(c_info, command):
|
|||
utils.delete_if_exists(pwfile)
|
||||
|
||||
|
||||
def _power_on(c_info):
|
||||
def _power_on(driver_info):
|
||||
"""Turn the power to this node ON."""
|
||||
|
||||
# use mutable objects so the looped method can change them
|
||||
|
@ -120,7 +122,7 @@ def _power_on(c_info):
|
|||
def _wait_for_power_on(state, retries):
|
||||
"""Called at an interval until the node's power is on."""
|
||||
|
||||
state[0] = _power_status(c_info)
|
||||
state[0] = _power_status(driver_info)
|
||||
if state[0] == states.POWER_ON:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
|
@ -129,11 +131,11 @@ def _power_on(c_info):
|
|||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
retries[0] += 1
|
||||
_exec_ipmitool(c_info, "power on")
|
||||
_exec_ipmitool(driver_info, "power on")
|
||||
except Exception:
|
||||
# Log failures but keep trying
|
||||
LOG.warning(_("IPMI power on failed for node %s.")
|
||||
% c_info['uuid'])
|
||||
% driver_info['uuid'])
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_on,
|
||||
state, retries)
|
||||
|
@ -141,7 +143,7 @@ def _power_on(c_info):
|
|||
return state[0]
|
||||
|
||||
|
||||
def _power_off(c_info):
|
||||
def _power_off(driver_info):
|
||||
"""Turn the power to this node OFF."""
|
||||
|
||||
# use mutable objects so the looped method can change them
|
||||
|
@ -151,7 +153,7 @@ def _power_off(c_info):
|
|||
def _wait_for_power_off(state, retries):
|
||||
"""Called at an interval until the node's power is off."""
|
||||
|
||||
state[0] = _power_status(c_info)
|
||||
state[0] = _power_status(driver_info)
|
||||
if state[0] == states.POWER_OFF:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
|
@ -160,11 +162,11 @@ def _power_off(c_info):
|
|||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
retries[0] += 1
|
||||
_exec_ipmitool(c_info, "power off")
|
||||
_exec_ipmitool(driver_info, "power off")
|
||||
except Exception:
|
||||
# Log failures but keep trying
|
||||
LOG.warning(_("IPMI power off failed for node %s.")
|
||||
% c_info['uuid'])
|
||||
% driver_info['uuid'])
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_off,
|
||||
state=state, retries=retries)
|
||||
|
@ -172,8 +174,8 @@ def _power_off(c_info):
|
|||
return state[0]
|
||||
|
||||
|
||||
def _power_status(c_info):
|
||||
out_err = _exec_ipmitool(c_info, "power status")
|
||||
def _power_status(driver_info):
|
||||
out_err = _exec_ipmitool(driver_info, "power status")
|
||||
if out_err[0] == "Chassis Power is on\n":
|
||||
return states.POWER_ON
|
||||
elif out_err[0] == "Chassis Power is off\n":
|
||||
|
@ -182,41 +184,30 @@ def _power_status(c_info):
|
|||
return states.ERROR
|
||||
|
||||
|
||||
class IPMIPowerDriver(base.ControlDriver):
|
||||
"""Generic IPMI Power Driver
|
||||
class IPMIPower(base.PowerInterface):
|
||||
|
||||
This ControlDriver class provides mechanism for controlling the power state
|
||||
of physical hardware via IPMI calls. It also provides console access for
|
||||
some supported hardware.
|
||||
|
||||
NOTE: This driver does not currently support multi-node operations.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def validate_driver_info(self, node):
|
||||
"""Check that node['control_info'] contains the requisite fields."""
|
||||
def validate(self, node):
|
||||
"""Check that node['driver_info'] contains IPMI credentials."""
|
||||
try:
|
||||
_parse_control_info(node)
|
||||
_parse_driver_info(node)
|
||||
except exception.InvalidParameterValue:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_power_state(self, task, node):
|
||||
"""Get the current power state."""
|
||||
c_info = _parse_control_info(node)
|
||||
return _power_status(c_info)
|
||||
driver_info = _parse_driver_info(node)
|
||||
return _power_status(driver_info)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, node, pstate):
|
||||
"""Turn the power on or off."""
|
||||
c_info = _parse_control_info(node)
|
||||
driver_info = _parse_driver_info(node)
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(c_info)
|
||||
state = _power_on(driver_info)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(c_info)
|
||||
state = _power_off(driver_info)
|
||||
else:
|
||||
raise exception.IronicException(_(
|
||||
"set_power_state called with invalid power state."))
|
||||
|
@ -227,18 +218,18 @@ class IPMIPowerDriver(base.ControlDriver):
|
|||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task, node):
|
||||
"""Cycles the power to a node."""
|
||||
c_info = _parse_control_info(node)
|
||||
_power_off(c_info)
|
||||
state = _power_on(c_info)
|
||||
driver_info = _parse_driver_info(node)
|
||||
_power_off(driver_info)
|
||||
state = _power_on(driver_info)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_boot_device(self, task, node, device, persistent=False):
|
||||
def _set_boot_device(self, task, node, device, persistent=False):
|
||||
"""Set the boot device for a node.
|
||||
|
||||
:param task: TaskManager context.
|
||||
:param task: a TaskManager instance.
|
||||
:param node: The Node.
|
||||
:param device: Boot device. One of [pxe, disk, cdrom, safe, bios].
|
||||
:param persistent: Whether to set next-boot, or make the change
|
||||
|
@ -253,19 +244,9 @@ class IPMIPowerDriver(base.ControlDriver):
|
|||
cmd = "chassis bootdev %s" % device
|
||||
if persistent:
|
||||
cmd = cmd + " options=persistent"
|
||||
c_info = _parse_control_info(node)
|
||||
driver_info = _parse_driver_info(node)
|
||||
try:
|
||||
out, err = _exec_ipmitool(c_info, cmd)
|
||||
out, err = _exec_ipmitool(driver_info, cmd)
|
||||
# TODO(deva): validate (out, err) and add unit test for failure
|
||||
except Exception:
|
||||
raise exception.IPMIFailure(cmd=cmd)
|
||||
|
||||
# TODO(deva): port start_console
|
||||
def start_console(self, task, node):
|
||||
raise exception.IronicException(_(
|
||||
"start_console is not supported by IPMIPowerDriver."))
|
||||
|
||||
# TODO(deva): port stop_console
|
||||
def stop_console(self, task, node):
|
||||
raise exception.IronicException(_(
|
||||
"stop_console is not supported by IPMIPowerDriver."))
|
|
@ -1,382 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Ironic SSH power manager.
|
||||
|
||||
Provides basic power control of virtual machines via SSH.
|
||||
|
||||
For use in dev and test environments.
|
||||
|
||||
Currently supported environments are:
|
||||
Virtual Box (vbox)
|
||||
Virsh (virsh)
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.drivers import base
|
||||
from ironic.manager import task_manager
|
||||
from ironic.openstack.common import jsonutils as json
|
||||
from ironic.openstack.common import log as logging
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
COMMAND_SETS = {
|
||||
'vbox': {
|
||||
'base_cmd': '/usr/bin/VBoxManage',
|
||||
'start_cmd': 'startvm {_NodeName_}',
|
||||
'stop_cmd': 'controlvm {_NodeName_} poweroff',
|
||||
'reboot_cmd': 'controlvm {_NodeName_} reset',
|
||||
'list_all': "list vms|awk -F'\"' '{print $2}'",
|
||||
'list_running': 'list runningvms',
|
||||
'get_node_macs': ("showvminfo --machinereadable {_NodeName_} | "
|
||||
"grep "
|
||||
'"macaddress" | awk -F '
|
||||
"'"
|
||||
'"'
|
||||
"' '{print $2}'")
|
||||
},
|
||||
"virsh": {
|
||||
'base_cmd': '/usr/bin/virsh',
|
||||
'start_cmd': 'start {_NodeName_}',
|
||||
'stop_cmd': 'destroy {_NodeName_}',
|
||||
'reboot_cmd': 'reset {_NodeName_}',
|
||||
'list_all': "list --all | tail -n +2 | awk -F\" \" '{print $2}'",
|
||||
'list_running':
|
||||
"list --all|grep running|awk -v qc='\"' -F\" \" '{print qc$2qc}'",
|
||||
'get_node_macs': ("dumpxml {_NodeName_} | grep "
|
||||
'"mac address" | awk -F'
|
||||
'"'
|
||||
"'"
|
||||
'" '
|
||||
"'{print $2}' | tr -d ':'")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _normalize_mac(mac):
|
||||
return mac.translate(None, '-:').lower()
|
||||
|
||||
|
||||
def _exec_ssh_command(ssh_obj, command):
|
||||
"""Execute a SSH command on the host."""
|
||||
|
||||
LOG.debug(_('Running cmd (SSH): %s'), command)
|
||||
|
||||
stdin_stream, stdout_stream, stderr_stream = ssh_obj.exec_command(command)
|
||||
channel = stdout_stream.channel
|
||||
|
||||
# NOTE(justinsb): This seems suspicious...
|
||||
# ...other SSH clients have buffering issues with this approach
|
||||
stdout = stdout_stream.read()
|
||||
stderr = stderr_stream.read()
|
||||
stdin_stream.close()
|
||||
|
||||
exit_status = channel.recv_exit_status()
|
||||
|
||||
# exit_status == -1 if no exit code was returned
|
||||
if exit_status != -1:
|
||||
LOG.debug(_('Result was %s') % exit_status)
|
||||
if exit_status != 0:
|
||||
raise exception.ProcessExecutionError(exit_code=exit_status,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
cmd=command)
|
||||
|
||||
return (stdout, stderr)
|
||||
|
||||
|
||||
def _parse_control_info(node):
|
||||
info = json.loads(node.get('control_info', ''))
|
||||
host = info.get('ssh_host', None)
|
||||
username = info.get('ssh_user', None)
|
||||
password = info.get('ssh_pass', None)
|
||||
port = info.get('ssh_port', 22)
|
||||
key_filename = info.get('ssh_key', None)
|
||||
virt_type = info.get('virt_type', None)
|
||||
|
||||
res = {
|
||||
'host': host,
|
||||
'username': username,
|
||||
'port': port,
|
||||
'virt_type': virt_type,
|
||||
'uuid': node.get('uuid')
|
||||
}
|
||||
|
||||
if not virt_type:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver requires virt_type be set."))
|
||||
|
||||
cmd_set = COMMAND_SETS.get(virt_type, None)
|
||||
if not cmd_set:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver unknown virt_type (%s).") % cmd_set)
|
||||
res['cmd_set'] = cmd_set
|
||||
|
||||
if not host or not username:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver requires both ssh_host and ssh_user be set."))
|
||||
if password:
|
||||
res['password'] = password
|
||||
else:
|
||||
if not key_filename:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver requires either ssh_pass or ssh_key be set."))
|
||||
if not os.path.isfile(key_filename):
|
||||
raise exception.FileNotFound(file_path=key_filename)
|
||||
res['key_filename'] = key_filename
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _get_power_status(ssh_obj, c_info):
|
||||
"""Returns a node's current power state."""
|
||||
|
||||
power_state = None
|
||||
cmd_to_exec = c_info['cmd_set']['list_running']
|
||||
running_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
|
||||
# Command should return a list of running vms. If the current node is
|
||||
# not listed then we can assume it is not powered on.
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
|
||||
if node_name:
|
||||
for node in running_list:
|
||||
if node_name in node:
|
||||
power_state = states.POWER_ON
|
||||
break
|
||||
if not power_state:
|
||||
power_state = states.POWER_OFF
|
||||
else:
|
||||
power_state = states.ERROR
|
||||
|
||||
return power_state
|
||||
|
||||
|
||||
def _get_connection(node):
|
||||
return utils.ssh_connect(_parse_control_info(node))
|
||||
|
||||
|
||||
def _get_hosts_name_for_node(ssh_obj, c_info):
|
||||
"""Get the name the host uses to reference the node."""
|
||||
|
||||
matched_name = None
|
||||
cmd_to_exec = c_info['cmd_set']['list_all']
|
||||
full_node_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
|
||||
# for each node check Mac Addresses
|
||||
for node in full_node_list:
|
||||
cmd_to_exec = c_info['cmd_set']['get_node_macs']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', node)
|
||||
hosts_node_mac_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
|
||||
|
||||
for host_mac in hosts_node_mac_list:
|
||||
for node_mac in c_info['macs']:
|
||||
if _normalize_mac(host_mac) in _normalize_mac(node_mac):
|
||||
matched_name = node
|
||||
break
|
||||
|
||||
if matched_name:
|
||||
break
|
||||
if matched_name:
|
||||
break
|
||||
|
||||
return matched_name
|
||||
|
||||
|
||||
def _power_on(ssh_obj, c_info):
|
||||
"""Power ON this node."""
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
_power_off(ssh_obj, c_info)
|
||||
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
|
||||
cmd_to_power_on = c_info['cmd_set']['start_cmd']
|
||||
cmd_to_power_on = cmd_to_power_on.replace('{_NodeName_}', node_name)
|
||||
|
||||
_exec_ssh_command(ssh_obj, cmd_to_power_on)
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
return current_pstate
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
def _power_off(ssh_obj, c_info):
|
||||
"""Power OFF this node."""
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
|
||||
cmd_to_power_off = c_info['cmd_set']['stop_cmd']
|
||||
cmd_to_power_off = cmd_to_power_off.replace('{_NodeName_}', node_name)
|
||||
|
||||
_exec_ssh_command(ssh_obj, cmd_to_power_off)
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
def _get_nodes_mac_addresses(task, node):
|
||||
"""Get all mac addresses for a node."""
|
||||
interface_ports = task.dbapi.get_ports_by_node(node.get('id'))
|
||||
macs = [p.address for p in interface_ports]
|
||||
return macs
|
||||
|
||||
|
||||
class SSHPowerDriver(base.ControlDriver):
|
||||
"""SSH Power Driver.
|
||||
|
||||
This ControlDriver class provides a mechanism for controlling the power
|
||||
state of virtual machines via SSH.
|
||||
|
||||
NOTE: This driver supports VirtualBox and Virsh commands.
|
||||
NOTE: This driver does not currently support multi-node operations.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def validate_driver_info(self, node):
|
||||
"""Check that node['control_info'] contains the requisite fields.
|
||||
|
||||
:param node: Single node object.
|
||||
|
||||
:returns: True / False.
|
||||
|
||||
"""
|
||||
try:
|
||||
_parse_control_info(node)
|
||||
except exception.InvalidParameterValue:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_power_state(self, task, node):
|
||||
"""Get the current power state.
|
||||
|
||||
Poll the host for the current power state of the node.
|
||||
|
||||
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
|
||||
:param node: A single node.
|
||||
|
||||
:returns: power state. One of :class:`ironic.common.states`.
|
||||
|
||||
"""
|
||||
c_info = _parse_control_info(node)
|
||||
c_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
ssh_obj = _get_connection(node)
|
||||
return _get_power_status(ssh_obj, c_info)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, node, pstate):
|
||||
"""Turn the power on or off.
|
||||
|
||||
Set the power state of a node.
|
||||
|
||||
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
|
||||
:param node: A single node.
|
||||
:param pstate: Either POWER_ON or POWER_OFF from :class:
|
||||
`ironic.common.states`.
|
||||
|
||||
:returns NOTHING:
|
||||
|
||||
Can raise exception.IronicException or exception.PowerStateFailure.
|
||||
|
||||
"""
|
||||
c_info = _parse_control_info(node)
|
||||
c_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
ssh_obj = _get_connection(node)
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(ssh_obj, c_info)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(ssh_obj, c_info)
|
||||
else:
|
||||
raise exception.IronicException(_(
|
||||
"set_power_state called with invalid power state."))
|
||||
|
||||
if state != pstate:
|
||||
raise exception.PowerStateFailure(pstate=pstate)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task, node):
|
||||
"""Cycles the power to a node.
|
||||
|
||||
Power cycles a node.
|
||||
|
||||
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
|
||||
:param node: A single node.
|
||||
|
||||
:returns NOTHING:
|
||||
|
||||
Can raise exception.PowerStateFailure.
|
||||
|
||||
"""
|
||||
c_info = _parse_control_info(node)
|
||||
c_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
ssh_obj = _get_connection(node)
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
_power_off(ssh_obj, c_info)
|
||||
|
||||
state = _power_on(ssh_obj, c_info)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
def start_console(self, task, node):
|
||||
"""Starts a console connection to a node.
|
||||
|
||||
CURRENTLY NOT SUPPORTED.
|
||||
|
||||
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
|
||||
:param node: A single node.
|
||||
|
||||
Will raise raise exception.IronicException.
|
||||
|
||||
"""
|
||||
raise exception.IronicException(_(
|
||||
"start_console is not supported by SSHPowerDriver."))
|
||||
|
||||
def stop_console(self, task, node):
|
||||
"""Stops a console connection to a node.
|
||||
|
||||
CURRENTLY NOT SUPPORTED.
|
||||
|
||||
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
|
||||
:param node: A single node.
|
||||
|
||||
Will raise raise exception.IronicException.
|
||||
|
||||
"""
|
||||
|
||||
raise exception.IronicException(_(
|
||||
"stop_console is not supported by SSHPowerDriver."))
|
|
@ -75,8 +75,8 @@ class ManagerService(service.PeriodicService):
|
|||
|
||||
with task_manager.acquire([node_id], shared=True) as task:
|
||||
node = task.resources[0].node
|
||||
driver = task.resources[0].controller
|
||||
state = driver.get_power_state(task, node)
|
||||
driver = task.resources[0].driver
|
||||
state = driver.power.get_power_state(task, node)
|
||||
return state
|
||||
|
||||
# TODO(deva)
|
||||
|
|
|
@ -46,12 +46,8 @@ class NodeManager(object):
|
|||
|
||||
_nodes = {}
|
||||
|
||||
_control_factory = dispatch.NameDispatchExtensionManager(
|
||||
namespace='ironic.controllers',
|
||||
check_func=lambda x: True,
|
||||
invoke_on_load=True)
|
||||
_deploy_factory = dispatch.NameDispatchExtensionManager(
|
||||
namespace='ironic.deployers',
|
||||
_driver_factory = dispatch.NameDispatchExtensionManager(
|
||||
namespace='ironic.drivers',
|
||||
check_func=lambda x: True,
|
||||
invoke_on_load=True)
|
||||
|
||||
|
@ -69,22 +65,13 @@ class NodeManager(object):
|
|||
# NOTE(deva): Driver loading here may get refactored, depend on:
|
||||
# https://github.com/dreamhost/stevedore/issues/15
|
||||
try:
|
||||
ref = NodeManager._control_factory.map(
|
||||
[self.node.get('control_driver')], _get_instance)
|
||||
self.controller = ref[0]
|
||||
ref = NodeManager._driver_factory.map(
|
||||
[self.node.get('driver')], _get_instance)
|
||||
self.driver = ref[0]
|
||||
except KeyError:
|
||||
raise exception.IronicException(_(
|
||||
"Failed to load Control driver %s.") %
|
||||
self.node.get('control_driver'))
|
||||
|
||||
try:
|
||||
ref = NodeManager._deploy_factory.map(
|
||||
[self.node.get('deploy_driver')], _get_instance)
|
||||
self.deployer = ref[0]
|
||||
except KeyError:
|
||||
raise exception.IronicException(_(
|
||||
"Failed to load Deploy driver %s.") %
|
||||
self.node.get('deploy_driver'))
|
||||
"Failed to load driver %s.") %
|
||||
self.node.get('driver'))
|
||||
|
||||
@classmethod
|
||||
@lockutils.synchronized(RESOURCE_MANAGER_SEMAPHORE, 'ironic-')
|
||||
|
|
|
@ -23,7 +23,7 @@ A context manager to peform a series of tasks on a set of resources.
|
|||
locking and simplify operations across a set of
|
||||
:class:`ironic.manager.resource_manager.NodeManager` instances. Each
|
||||
NodeManager holds the data model for a node, as well as references to the
|
||||
controller and deployer driver singletons appropriate for that node.
|
||||
driver singleton appropriate for that node.
|
||||
|
||||
The :class:`TaskManager` will acquire either a shared or exclusive lock, as
|
||||
indicated. Multiple shared locks for the same resource may coexist with an
|
||||
|
@ -56,11 +56,10 @@ driver function, you can access the drivers directly in this way::
|
|||
|
||||
with task_manager.acquire(node_ids) as task:
|
||||
states = []
|
||||
for node, control, deploy in [r.node, r.controller, r.deployer
|
||||
for node, driver in [r.node, r.driver
|
||||
for r in task.resources]:
|
||||
# control and deploy are driver singletons,
|
||||
# loaded based on that node's configuration.
|
||||
states.append(control.get_power_state(task, node)
|
||||
# the driver is loaded based on that node's configuration.
|
||||
states.append(driver.power.get_power_state(task, node)
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
|
|
15
setup.cfg
15
setup.cfg
|
@ -33,14 +33,13 @@ console_scripts =
|
|||
ironic-manager = ironic.cmd.manager:main
|
||||
ironic-rootwrap = ironic.openstack.common.rootwrap.cmd:main
|
||||
|
||||
ironic.controllers =
|
||||
fake = ironic.drivers.fake:FakeControlDriver
|
||||
ipmi = ironic.drivers.ipmi:IPMIPowerDriver
|
||||
ssh = ironic.drivers.ssh:SSHPowerDriver
|
||||
|
||||
ironic.deployers =
|
||||
fake = ironic.drivers.fake:FakeDeployDriver
|
||||
# pxe = ironic.drivers.pxe.PXEDeployDriver
|
||||
ironic.drivers =
|
||||
fake = ironic.drivers.fake:FakeDriver
|
||||
fake_ipmi = ironic.drivers.fake:FakeIPMIDriver
|
||||
fake_ssh = ironic.drivers.fake:FakeSSHDriver
|
||||
fake_pxe = ironic.drivers.fake:FakePXEDriver
|
||||
pxe_ipmi = ironic.drivers.pxe:PXEAndIPMIDriver
|
||||
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
|
|
Loading…
Reference in New Issue