Add Redfish as OOB driver

This patch implements Refish as new OOB driver for Drydock.
All the existing Drydock Orchestrator actions are implemented.

Change-Id: I31d653fb41189a18c34cfafb0f490ca4f4d661b5
This commit is contained in:
Hemanth Nakkina 2018-07-19 10:48:46 +05:30
parent 485f919822
commit da0c7e831e
10 changed files with 814 additions and 1 deletions

View File

@ -325,6 +325,7 @@ conf:
ingester:
- 'drydock_provisioner.ingester.plugins.yaml.YamlIngester'
oob_driver:
- 'drydock_provisioner.drivers.oob.redfish_driver.driver.RedfishDriver'
- 'drydock_provisioner.drivers.oob.pyghmi_driver.driver.PyghmiDriver'
- 'drydock_provisioner.drivers.oob.manual_driver.driver.ManualDriver'
- 'drydock_provisioner.drivers.oob.libvirt_driver.driver.LibvirtDriver'

View File

@ -370,6 +370,25 @@
#poll_interval = 10
[redfish_driver]
#
# From drydock_provisioner
#
# Maximum number of connection retries to Redfish server
#max_retries = 5
# Maximum reties to wait for power state change
#power_state_change_max_retries = 18
# Polling interval in seconds between retries for power state change
#power_state_change_retry_interval = 10
# Use SSL to communicate with Redfish API server (boolean value)
#use_ssl = true
[timeouts]
#

View File

@ -569,7 +569,7 @@ class Machines(model_base.ResourceCollectionBase):
"""
maas_node = None
if node_model.oob_type == 'ipmi':
if node_model.oob_type == 'ipmi' or node_model.oob_type == 'redfish':
node_oob_network = node_model.oob_parameters['network']
node_oob_ip = node_model.get_network_address(node_oob_network)

View File

@ -0,0 +1,443 @@
# Copyright 2018 AT&T Intellectual Property. All other 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.
"""Driver for controlling OOB interface via Redfish.
Based on Redfish Rest API specification.
"""
import time
from oslo_config import cfg
from drydock_provisioner.orchestrator.actions.orchestrator import BaseAction
from drydock_provisioner.drivers.oob.redfish_driver.client import RedfishException
from drydock_provisioner.drivers.oob.redfish_driver.client import RedfishSession
import drydock_provisioner.error as errors
import drydock_provisioner.objects.fields as hd_fields
class RedfishBaseAction(BaseAction):
"""Base action for Redfish executed actions."""
def get_redfish_session(self, node):
"""Initialize a Redfish session to the node.
:param node: instance of objects.BaremetalNode
:return: An instance of client.RedfishSession initialized to node's Redfish interface
"""
if node.oob_type != 'redfish':
raise errors.DriverError("Node OOB type is not Redfish")
oob_network = node.oob_parameters['network']
oob_address = node.get_network_address(oob_network)
if oob_address is None:
raise errors.DriverError(
"Node %s has no OOB Redfish address" % (node.name))
oob_account = node.oob_parameters['account']
oob_credential = node.oob_parameters['credential']
self.logger.debug("Starting Redfish session to %s with %s" %
(oob_address, oob_account))
try:
redfish_obj = RedfishSession(host=oob_address,
account=oob_account,
password=oob_credential,
use_ssl=cfg.CONF.redfish_driver.use_ssl,
connection_retries=cfg.CONF.redfish_driver.max_retries)
except (RedfishException, errors.DriverError) as iex:
self.logger.error(
"Error initializing Redfish session for node %s" % node.name)
self.logger.error("Redfish Exception: %s" % str(iex))
redfish_obj = None
return redfish_obj
def exec_redfish_command(self, node, session, func, *args):
"""Call a Redfish command after establishing a session.
:param node: Instance of objects.BaremetalNode to execute against
:param session: Redfish session
:param func: The redfish Command method to call
:param args: The args to pass the func
"""
try:
self.logger.debug("Calling Redfish command %s on %s" %
(func.__name__, node.name))
response = func(session, *args)
return response
except RedfishException as iex:
self.logger.error(
"Error executing Redfish command %s for node %s" % (func.__name__, node.name))
self.logger.error("Redfish Exception: %s" % str(iex))
raise errors.DriverError("Redfish command failed.")
class ValidateOobServices(RedfishBaseAction):
"""Action to validate OOB services are available."""
def start(self):
self.task.add_status_msg(
msg="OOB does not require services.",
error=False,
ctx='NA',
ctx_type='NA')
self.task.set_status(hd_fields.TaskStatus.Complete)
self.task.success()
self.task.save()
return
class ConfigNodePxe(RedfishBaseAction):
"""Action to configure PXE booting via OOB."""
def start(self):
self.task.set_status(hd_fields.TaskStatus.Running)
self.task.save()
node_list = self.orchestrator.get_target_nodes(self.task)
for n in node_list:
self.task.add_status_msg(
msg="Redfish doesn't configure PXE options.",
error=True,
ctx=n.name,
ctx_type='node')
self.task.set_status(hd_fields.TaskStatus.Complete)
self.task.failure()
self.task.save()
return
class SetNodeBoot(RedfishBaseAction):
"""Action to configure a node to PXE boot."""
def start(self):
self.task.set_status(hd_fields.TaskStatus.Running)
self.task.save()
node_list = self.orchestrator.get_target_nodes(self.task)
for n in node_list:
self.logger.debug("Setting bootdev to PXE for %s" % n.name)
self.task.add_status_msg(
msg="Setting node to PXE boot.",
error=False,
ctx=n.name,
ctx_type='node')
bootdev = None
try:
session = self.get_redfish_session(n)
bootdev = self.exec_redfish_command(n, session, RedfishSession.get_bootdev)
if bootdev.get('bootdev', '') != 'Pxe':
self.exec_redfish_command(n, session, RedfishSession.set_bootdev, 'Pxe')
bootdev = self.exec_redfish_command(n, session, RedfishSession.get_bootdev)
session.close_session()
except errors.DriverError:
pass
if bootdev is not None and (bootdev.get('bootdev',
'') == 'Pxe'):
self.task.add_status_msg(
msg="Set bootdev to PXE.",
error=False,
ctx=n.name,
ctx_type='node')
self.logger.debug("%s reports bootdev of network" % n.name)
self.task.success(focus=n.name)
else:
self.task.add_status_msg(
msg="Unable to set bootdev to PXE.",
error=True,
ctx=n.name,
ctx_type='node')
self.task.failure(focus=n.name)
self.logger.warning(
"Unable to set node %s to PXE boot." % (n.name))
self.task.set_status(hd_fields.TaskStatus.Complete)
self.task.save()
return
class PowerOffNode(RedfishBaseAction):
"""Action to power off a node via Redfish."""
def start(self):
self.task.set_status(hd_fields.TaskStatus.Running)
self.task.save()
node_list = self.orchestrator.get_target_nodes(self.task)
for n in node_list:
self.logger.debug("Sending set_power = off command to %s" % n.name)
self.task.add_status_msg(
msg="Sending set_power = off command.",
error=False,
ctx=n.name,
ctx_type='node')
session = self.get_redfish_session(n)
# If power is already off, continue with the next node
power_state = self.exec_redfish_command(n, RedfishSession.get_power)
if power_state is not None and (power_state.get(
'powerstate', '') == 'Off'):
self.task.add_status_msg(
msg="Node reports power off.",
error=False,
ctx=n.name,
ctx_type='node')
self.logger.debug(
"Node %s reports powerstate already off. No action required" % n.name)
self.task.success(focus=n.name)
continue
self.exec_redfish_command(n, session, RedfishSession.set_power, 'ForceOff')
attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
while attempts > 0:
self.logger.debug("Polling powerstate waiting for success.")
power_state = self.exec_redfish_command(n, RedfishSession.get_power)
if power_state is not None and (power_state.get(
'powerstate', '') == 'Off'):
self.task.add_status_msg(
msg="Node reports power off.",
error=False,
ctx=n.name,
ctx_type='node')
self.logger.debug(
"Node %s reports powerstate of off" % n.name)
self.task.success(focus=n.name)
break
time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
attempts = attempts - 1
if power_state is not None and (power_state.get('powerstate', '')
!= 'Off'):
self.task.add_status_msg(
msg="Node failed to power off.",
error=True,
ctx=n.name,
ctx_type='node')
self.logger.error("Giving up on Redfish command to %s" % n.name)
self.task.failure(focus=n.name)
session.close_session()
self.task.set_status(hd_fields.TaskStatus.Complete)
self.task.save()
return
class PowerOnNode(RedfishBaseAction):
"""Action to power on a node via Redfish."""
def start(self):
self.task.set_status(hd_fields.TaskStatus.Running)
self.task.save()
node_list = self.orchestrator.get_target_nodes(self.task)
for n in node_list:
self.logger.debug("Sending set_power = on command to %s" % n.name)
self.task.add_status_msg(
msg="Sending set_power = on command.",
error=False,
ctx=n.name,
ctx_type='node')
session = self.get_redfish_session(n)
# If power is already on, continue with the next node
power_state = self.exec_redfish_command(n, RedfishSession.get_power)
if power_state is not None and (power_state.get(
'powerstate', '') == 'On'):
self.task.add_status_msg(
msg="Node reports power on.",
error=False,
ctx=n.name,
ctx_type='node')
self.logger.debug(
"Node %s reports powerstate already on. No action required" % n.name)
self.task.success(focus=n.name)
continue
self.exec_redfish_command(n, session, RedfishSession.set_power, 'On')
attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
while attempts > 0:
self.logger.debug("Polling powerstate waiting for success.")
power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
if power_state is not None and (power_state.get(
'powerstate', '') == 'On'):
self.logger.debug(
"Node %s reports powerstate of on" % n.name)
self.task.add_status_msg(
msg="Node reports power on.",
error=False,
ctx=n.name,
ctx_type='node')
self.task.success(focus=n.name)
break
time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
attempts = attempts - 1
if power_state is not None and (power_state.get('powerstate', '')
!= 'On'):
self.task.add_status_msg(
msg="Node failed to power on.",
error=True,
ctx=n.name,
ctx_type='node')
self.logger.error("Giving up on Redfish command to %s" % n.name)
self.task.failure(focus=n.name)
session.close_session()
self.task.set_status(hd_fields.TaskStatus.Complete)
self.task.save()
return
class PowerCycleNode(RedfishBaseAction):
"""Action to hard powercycle a node via Redfish."""
def start(self):
self.task.set_status(hd_fields.TaskStatus.Running)
self.task.save()
node_list = self.orchestrator.get_target_nodes(self.task)
for n in node_list:
self.logger.debug("Sending set_power = off command to %s" % n.name)
self.task.add_status_msg(
msg="Power cycling node via Redfish.",
error=False,
ctx=n.name,
ctx_type='node')
session = self.get_redfish_session(n)
self.exec_redfish_command(n, session, RedfishSession.set_power, 'ForceOff')
# Wait for power state of off before booting back up
attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
while attempts > 0:
power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
if power_state is not None and power_state.get(
'powerstate', '') == 'Off':
self.logger.debug("%s reports powerstate of off" % n.name)
break
elif power_state is None:
self.logger.debug(
"No response on Redfish power query to %s" % n.name)
time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
attempts = attempts - 1
if power_state.get('powerstate', '') != 'Off':
self.task.add_status_msg(
msg="Failed to power down during power cycle.",
error=True,
ctx=n.name,
ctx_type='node')
self.logger.warning(
"Failed powering down node %s during power cycle task" %
n.name)
self.task.failure(focus=n.name)
break
self.logger.debug("Sending set_power = on command to %s" % n.name)
self.exec_redfish_command(n, session, RedfishSession.set_power, 'On')
attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
while attempts > 0:
power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
if power_state is not None and power_state.get(
'powerstate', '') == 'On':
self.logger.debug("%s reports powerstate of on" % n.name)
break
elif power_state is None:
self.logger.debug(
"No response on Redfish power query to %s" % n.name)
time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
attempts = attempts - 1
if power_state is not None and (power_state.get('powerstate',
'') == 'On'):
self.task.add_status_msg(
msg="Node power cycle complete.",
error=False,
ctx=n.name,
ctx_type='node')
self.task.success(focus=n.name)
else:
self.task.add_status_msg(
msg="Failed to power up during power cycle.",
error=True,
ctx=n.name,
ctx_type='node')
self.logger.warning(
"Failed powering up node %s during power cycle task" %
n.name)
self.task.failure(focus=n.name)
session.close_session()
self.task.set_status(hd_fields.TaskStatus.Complete)
self.task.save()
return
class InterrogateOob(RedfishBaseAction):
"""Action to complete a basic interrogation of the node Redfish interface."""
def start(self):
self.task.set_status(hd_fields.TaskStatus.Running)
self.task.save()
node_list = self.orchestrator.get_target_nodes(self.task)
for n in node_list:
try:
self.logger.debug(
"Interrogating node %s Redfish interface." % n.name)
session = self.get_redfish_session(n)
powerstate = self.exec_redfish_command(n, session, RedfishSession.get_power)
session.close_session()
if powerstate is None:
raise errors.DriverError()
self.task.add_status_msg(
msg="Redfish interface interrogation yielded powerstate %s" %
powerstate.get('powerstate'),
error=False,
ctx=n.name,
ctx_type='node')
self.task.success(focus=n.name)
except errors.DriverError:
self.logger.debug(
"Interrogating node %s Redfish interface failed." % n.name)
self.task.add_status_msg(
msg="Redfish interface interrogation failed.",
error=True,
ctx=n.name,
ctx_type='node')
self.task.failure(focus=n.name)
self.task.set_status(hd_fields.TaskStatus.Complete)
self.task.save()
return

View File

@ -0,0 +1,177 @@
# Copyright 2018 AT&T Intellectual Property. All other 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.
"""Redfish object to provide commands.
Uses Redfish client to communicate to node.
"""
from redfish import AuthMethod, redfish_client
from redfish.rest.v1 import ServerDownOrUnreachableError
from redfish.rest.v1 import InvalidCredentialsError
from redfish.rest.v1 import RetriesExhaustedError
class RedfishSession(object):
"""Redfish Client to provide OOB commands"""
def __init__(self, host, account, password, use_ssl=True, connection_retries=10):
try:
if use_ssl:
redfish_url = 'https://' + host
else:
redfish_url = 'http://' + host
self.redfish_client = redfish_client(base_url=redfish_url,
username=account,
password=password)
self.redfish_client.MAX_RETRY = connection_retries
self.redfish_client.login(auth=AuthMethod.SESSION)
except RetriesExhaustedError:
raise RedfishException("Login failed: Retries exhausted")
except InvalidCredentialsError:
raise RedfishException("Login failed: Invalid credentials")
except ServerDownOrUnreachableError:
raise RedfishException("Login failed: Server unreachable")
def __del__(self):
self.redfish_client.logout()
def close_session(self):
self.redfish_client.logout()
def get_system_instance(self):
response = self.redfish_client.get("/redfish/v1/Systems")
if response.status != 200:
raise RedfishException(response._read)
# Assumption that only one system is available on Node
if response.dict["Members@odata.count"] != 1:
raise RedfishException("Number of systems are more than one in the node")
instance = response.dict["Members"][0]["@odata.id"]
return instance
def get_bootdev(self):
"""Get current boot type information from Node.
:raises: RedfishException on an error
:return: dict -- response will return as dict in format of
{'bootdev': bootdev}
"""
instance = self.get_system_instance()
response = self.redfish_client.get(path=instance)
if response.status != 200:
raise RedfishException(response._read)
bootdev = response.dict["Boot"]["BootSourceOverrideTarget"]
return {'bootdev': bootdev}
def set_bootdev(self, bootdev, **kwargs):
"""Set boot type on the Node for next boot.
:param bootdev: Boot source for the next boot
* None - Boot from the normal boot device.
* Pxe - Boot from network
* Cd - Boot from CD/DVD disc
* Usb - Boot from USB device specified by system BIOS
* Hdd - Boot from Hard drive
* BiosSetup - Boot to bios setup utility
* Utilities - Boot manufacurer utlities program
* UefiTarget - Boot to the UEFI Device specified in the
UefiTargetBootSourceOverride property
* UefiShell - Boot to the UEFI Shell
* UefiHttp - Boot from UEFI HTTP network location
:param **kwargs: To specify extra arguments for a given bootdev
Example to specify UefiTargetBootSourceOverride value
for bootdev UefiTarget
:raises: RedfishException on an error
:return: dict -- response will return as dict in format of
{'bootdev': bootdev}
"""
instance = self.get_system_instance()
payload = {
"Boot": {
"BootSourceOverrideEnabled": "Once",
"BootSourceOverrideTarget": bootdev,
}
}
if bootdev == 'UefiTarget':
payload['Boot']['UefiTargetBootSourceOverride'] = kwargs.get(
'UefiTargetBootSourceOverride', '')
response = self.redfish_client.patch(path=instance, body=payload)
if response.status != 200:
raise RedfishException(response._read)
return {'bootdev': bootdev}
def get_power(self):
"""Get current power state information from Node.
:raises: RedfishException on an error
:return: dict -- response will return as dict in format of
{'powerstate': powerstate}
"""
instance = self.get_system_instance()
response = self.redfish_client.get(path=instance)
if response.status != 200:
raise RedfishException(response._read)
powerstate = response.dict["PowerState"]
return {'powerstate': powerstate}
def set_power(self, powerstate):
"""Request power change on the node.
:param powerstate: set power change
* On - Power On the unit
* ForceOff - Turn off immediately (non graceful)
* PushPowerButton - Simulate pressing physical
power button
* GracefulRestart - Perform a graceful shutdown
and then start
:raises: RedfishException on an error
:return: dict -- response will return as dict in format of
{'powerstate': powerstate}
"""
instance = self.get_system_instance()
if powerstate not in ["On", "ForceOff", "PushPowerButton", "GracefulRestart"]:
raise RedfishException("Unsupported powerstate")
current_state = self.get_power()
if (powerstate == "On" and current_state["powerstate"] == "On") or \
(powerstate == "ForceOff" and current_state["powerstate"] == "Off"):
return {'powerstate': powerstate}
payload = {
"ResetType": powerstate
}
url = instance + "/Actions/ComputerSystem.Reset"
response = self.redfish_client.post(path=url, body=payload)
if response.status in [200, 201, 204]:
return {'powerstate': powerstate}
else:
raise RedfishException(response._read)
class RedfishException(Exception):
"""Redfish Exception with error in message"""
pass

View File

@ -0,0 +1,171 @@
# Copyright 2018 AT&T Intellectual Property. All other 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.
"""Driver for controlling OOB interface via Redfish.
Based on Redfish Rest API specification.
"""
import uuid
import logging
import concurrent.futures
from oslo_config import cfg
import drydock_provisioner.error as errors
import drydock_provisioner.config as config
import drydock_provisioner.objects.fields as hd_fields
import drydock_provisioner.drivers.oob.driver as oob_driver
import drydock_provisioner.drivers.driver as generic_driver
from .actions.oob import ValidateOobServices
from .actions.oob import ConfigNodePxe
from .actions.oob import SetNodeBoot
from .actions.oob import PowerOffNode
from .actions.oob import PowerOnNode
from .actions.oob import PowerCycleNode
from .actions.oob import InterrogateOob
class RedfishDriver(oob_driver.OobDriver):
"""Driver for executing OOB actions via Redfish library."""
redfish_driver_options = [
cfg.IntOpt(
'max_retries',
default=10,
min=1,
help='Maximum number of connection retries to Redfish server'),
cfg.IntOpt(
'power_state_change_max_retries',
default=18,
min=1,
help='Maximum reties to wait for power state change'),
cfg.IntOpt(
'power_state_change_retry_interval',
default=10,
help='Polling interval in seconds between retries for power state change'),
cfg.BoolOpt(
'use_ssl',
default=True,
help='Use SSL to communicate with Redfish API server'),
]
oob_types_supported = ['redfish']
driver_name = "redfish_driver"
driver_key = "redfish_driver"
driver_desc = "Redfish OOB Driver"
action_class_map = {
hd_fields.OrchestratorAction.ValidateOobServices: ValidateOobServices,
hd_fields.OrchestratorAction.ConfigNodePxe: ConfigNodePxe,
hd_fields.OrchestratorAction.SetNodeBoot: SetNodeBoot,
hd_fields.OrchestratorAction.PowerOffNode: PowerOffNode,
hd_fields.OrchestratorAction.PowerOnNode: PowerOnNode,
hd_fields.OrchestratorAction.PowerCycleNode: PowerCycleNode,
hd_fields.OrchestratorAction.InterrogateOob: InterrogateOob,
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
cfg.CONF.register_opts(
RedfishDriver.redfish_driver_options, group=RedfishDriver.driver_key)
self.logger = logging.getLogger(
config.config_mgr.conf.logging.oobdriver_logger_name)
def execute_task(self, task_id):
task = self.state_manager.get_task(task_id)
if task is None:
self.logger.error("Invalid task %s" % (task_id))
raise errors.DriverError("Invalid task %s" % (task_id))
if task.action not in self.supported_actions:
self.logger.error("Driver %s doesn't support task action %s" %
(self.driver_desc, task.action))
raise errors.DriverError("Driver %s doesn't support task action %s"
% (self.driver_desc, task.action))
task.set_status(hd_fields.TaskStatus.Running)
task.save()
target_nodes = self.orchestrator.get_target_nodes(task)
with concurrent.futures.ThreadPoolExecutor(max_workers=16) as e:
subtask_futures = dict()
for n in target_nodes:
sub_nf = self.orchestrator.create_nodefilter_from_nodelist([n])
subtask = self.orchestrator.create_task(
action=task.action,
design_ref=task.design_ref,
node_filter=sub_nf)
task.register_subtask(subtask)
self.logger.debug(
"Starting Redfish subtask %s for action %s on node %s" %
(str(subtask.get_id()), task.action, n.name))
action_class = self.action_class_map.get(task.action, None)
if action_class is None:
self.logger.error(
"Could not find action resource for action %s" %
task.action)
self.task.failure()
break
action = action_class(subtask, self.orchestrator,
self.state_manager)
subtask_futures[subtask.get_id().bytes] = e.submit(
action.start)
timeout = config.config_mgr.conf.timeouts.drydock_timeout
finished, running = concurrent.futures.wait(
subtask_futures.values(), timeout=(timeout * 60))
for t, f in subtask_futures.items():
if not f.done():
task.add_status_msg(
msg="Subtask %s timed out before completing.",
error=True,
ctx=str(uuid.UUID(bytes=t)),
ctx_type='task')
task.failure()
else:
if f.exception():
self.logger.error(
"Uncaught exception in subtask %s" % str(
uuid.UUID(bytes=t)),
exc_info=f.exception())
task.align_result()
task.bubble_results()
task.set_status(hd_fields.TaskStatus.Complete)
task.save()
return
class RedfishActionRunner(generic_driver.DriverActionRunner):
"""Threaded runner for a Redfish Action."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.logger = logging.getLogger(
config.config_mgr.conf.logging.oobdriver_logger_name)
def list_opts():
return {RedfishDriver.driver_key: RedfishDriver.redfish_driver_options}

View File

@ -24,3 +24,4 @@ ulid2==0.1.1
defusedxml===0.5.0
libvirt-python==3.10.0
beaker==1.9.1
redfish==2.0.1

View File

@ -61,6 +61,7 @@ python-keystoneclient==3.17.0
python-mimeparse==1.6.0
pytz==2018.5
PyYAML==3.12
redfish==2.0.1
repoze.lru==0.7
requests==2.19.1
rfc3986==1.1.0