summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-12-21 14:36:22 +0000
committerGerrit Code Review <review@openstack.org>2018-12-21 14:36:22 +0000
commitc39a4ede1a28beaed18cfe09a9bcf741055eac20 (patch)
tree08bf52aa897bb12d8b8a216bef37afb7f4557574
parent54ea0e1374ddd18a5195edc418b5c0b042666f45 (diff)
parentda0c7e831e1983620dcc8e3e95fb02f68d582c3c (diff)
Merge "Add Redfish as OOB driver"
-rw-r--r--charts/drydock/values.yaml1
-rw-r--r--etc/drydock/drydock.conf.sample19
-rw-r--r--python/drydock_provisioner/drivers/node/maasdriver/models/machine.py2
-rw-r--r--python/drydock_provisioner/drivers/oob/redfish_driver/__init__.py0
-rw-r--r--python/drydock_provisioner/drivers/oob/redfish_driver/actions/__init__.py0
-rw-r--r--python/drydock_provisioner/drivers/oob/redfish_driver/actions/oob.py443
-rw-r--r--python/drydock_provisioner/drivers/oob/redfish_driver/client.py177
-rw-r--r--python/drydock_provisioner/drivers/oob/redfish_driver/driver.py171
-rw-r--r--python/requirements-direct.txt1
-rw-r--r--python/requirements-lock.txt1
10 files changed, 814 insertions, 1 deletions
diff --git a/charts/drydock/values.yaml b/charts/drydock/values.yaml
index fc0fa9a..3e7462c 100644
--- a/charts/drydock/values.yaml
+++ b/charts/drydock/values.yaml
@@ -325,6 +325,7 @@ conf:
325 ingester: 325 ingester:
326 - 'drydock_provisioner.ingester.plugins.yaml.YamlIngester' 326 - 'drydock_provisioner.ingester.plugins.yaml.YamlIngester'
327 oob_driver: 327 oob_driver:
328 - 'drydock_provisioner.drivers.oob.redfish_driver.driver.RedfishDriver'
328 - 'drydock_provisioner.drivers.oob.pyghmi_driver.driver.PyghmiDriver' 329 - 'drydock_provisioner.drivers.oob.pyghmi_driver.driver.PyghmiDriver'
329 - 'drydock_provisioner.drivers.oob.manual_driver.driver.ManualDriver' 330 - 'drydock_provisioner.drivers.oob.manual_driver.driver.ManualDriver'
330 - 'drydock_provisioner.drivers.oob.libvirt_driver.driver.LibvirtDriver' 331 - 'drydock_provisioner.drivers.oob.libvirt_driver.driver.LibvirtDriver'
diff --git a/etc/drydock/drydock.conf.sample b/etc/drydock/drydock.conf.sample
index b0c17d5..e271d5f 100644
--- a/etc/drydock/drydock.conf.sample
+++ b/etc/drydock/drydock.conf.sample
@@ -370,6 +370,25 @@
370#poll_interval = 10 370#poll_interval = 10
371 371
372 372
373[redfish_driver]
374
375#
376# From drydock_provisioner
377#
378
379# Maximum number of connection retries to Redfish server
380#max_retries = 5
381
382# Maximum reties to wait for power state change
383#power_state_change_max_retries = 18
384
385# Polling interval in seconds between retries for power state change
386#power_state_change_retry_interval = 10
387
388# Use SSL to communicate with Redfish API server (boolean value)
389#use_ssl = true
390
391
373[timeouts] 392[timeouts]
374 393
375# 394#
diff --git a/python/drydock_provisioner/drivers/node/maasdriver/models/machine.py b/python/drydock_provisioner/drivers/node/maasdriver/models/machine.py
index 5186bd1..4a92666 100644
--- a/python/drydock_provisioner/drivers/node/maasdriver/models/machine.py
+++ b/python/drydock_provisioner/drivers/node/maasdriver/models/machine.py
@@ -574,7 +574,7 @@ class Machines(model_base.ResourceCollectionBase):
574 """ 574 """
575 maas_node = None 575 maas_node = None
576 576
577 if node_model.oob_type == 'ipmi': 577 if node_model.oob_type == 'ipmi' or node_model.oob_type == 'redfish':
578 node_oob_network = node_model.oob_parameters['network'] 578 node_oob_network = node_model.oob_parameters['network']
579 node_oob_ip = node_model.get_network_address(node_oob_network) 579 node_oob_ip = node_model.get_network_address(node_oob_network)
580 580
diff --git a/python/drydock_provisioner/drivers/oob/redfish_driver/__init__.py b/python/drydock_provisioner/drivers/oob/redfish_driver/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/drydock_provisioner/drivers/oob/redfish_driver/__init__.py
diff --git a/python/drydock_provisioner/drivers/oob/redfish_driver/actions/__init__.py b/python/drydock_provisioner/drivers/oob/redfish_driver/actions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/drydock_provisioner/drivers/oob/redfish_driver/actions/__init__.py
diff --git a/python/drydock_provisioner/drivers/oob/redfish_driver/actions/oob.py b/python/drydock_provisioner/drivers/oob/redfish_driver/actions/oob.py
new file mode 100644
index 0000000..acec822
--- /dev/null
+++ b/python/drydock_provisioner/drivers/oob/redfish_driver/actions/oob.py
@@ -0,0 +1,443 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Driver for controlling OOB interface via Redfish.
15
16Based on Redfish Rest API specification.
17"""
18
19import time
20
21from oslo_config import cfg
22
23from drydock_provisioner.orchestrator.actions.orchestrator import BaseAction
24from drydock_provisioner.drivers.oob.redfish_driver.client import RedfishException
25from drydock_provisioner.drivers.oob.redfish_driver.client import RedfishSession
26
27import drydock_provisioner.error as errors
28import drydock_provisioner.objects.fields as hd_fields
29
30class RedfishBaseAction(BaseAction):
31 """Base action for Redfish executed actions."""
32
33 def get_redfish_session(self, node):
34 """Initialize a Redfish session to the node.
35
36 :param node: instance of objects.BaremetalNode
37 :return: An instance of client.RedfishSession initialized to node's Redfish interface
38 """
39 if node.oob_type != 'redfish':
40 raise errors.DriverError("Node OOB type is not Redfish")
41
42 oob_network = node.oob_parameters['network']
43 oob_address = node.get_network_address(oob_network)
44 if oob_address is None:
45 raise errors.DriverError(
46 "Node %s has no OOB Redfish address" % (node.name))
47
48 oob_account = node.oob_parameters['account']
49 oob_credential = node.oob_parameters['credential']
50
51 self.logger.debug("Starting Redfish session to %s with %s" %
52 (oob_address, oob_account))
53 try:
54 redfish_obj = RedfishSession(host=oob_address,
55 account=oob_account,
56 password=oob_credential,
57 use_ssl=cfg.CONF.redfish_driver.use_ssl,
58 connection_retries=cfg.CONF.redfish_driver.max_retries)
59 except (RedfishException, errors.DriverError) as iex:
60 self.logger.error(
61 "Error initializing Redfish session for node %s" % node.name)
62 self.logger.error("Redfish Exception: %s" % str(iex))
63 redfish_obj = None
64
65 return redfish_obj
66
67 def exec_redfish_command(self, node, session, func, *args):
68 """Call a Redfish command after establishing a session.
69
70 :param node: Instance of objects.BaremetalNode to execute against
71 :param session: Redfish session
72 :param func: The redfish Command method to call
73 :param args: The args to pass the func
74 """
75 try:
76 self.logger.debug("Calling Redfish command %s on %s" %
77 (func.__name__, node.name))
78 response = func(session, *args)
79 return response
80 except RedfishException as iex:
81 self.logger.error(
82 "Error executing Redfish command %s for node %s" % (func.__name__, node.name))
83 self.logger.error("Redfish Exception: %s" % str(iex))
84
85 raise errors.DriverError("Redfish command failed.")
86
87
88class ValidateOobServices(RedfishBaseAction):
89 """Action to validate OOB services are available."""
90
91 def start(self):
92 self.task.add_status_msg(
93 msg="OOB does not require services.",
94 error=False,
95 ctx='NA',
96 ctx_type='NA')
97 self.task.set_status(hd_fields.TaskStatus.Complete)
98 self.task.success()
99 self.task.save()
100
101 return
102
103
104class ConfigNodePxe(RedfishBaseAction):
105 """Action to configure PXE booting via OOB."""
106
107 def start(self):
108 self.task.set_status(hd_fields.TaskStatus.Running)
109 self.task.save()
110
111 node_list = self.orchestrator.get_target_nodes(self.task)
112
113 for n in node_list:
114 self.task.add_status_msg(
115 msg="Redfish doesn't configure PXE options.",
116 error=True,
117 ctx=n.name,
118 ctx_type='node')
119 self.task.set_status(hd_fields.TaskStatus.Complete)
120 self.task.failure()
121 self.task.save()
122 return
123
124
125class SetNodeBoot(RedfishBaseAction):
126 """Action to configure a node to PXE boot."""
127
128 def start(self):
129 self.task.set_status(hd_fields.TaskStatus.Running)
130 self.task.save()
131
132 node_list = self.orchestrator.get_target_nodes(self.task)
133
134 for n in node_list:
135 self.logger.debug("Setting bootdev to PXE for %s" % n.name)
136 self.task.add_status_msg(
137 msg="Setting node to PXE boot.",
138 error=False,
139 ctx=n.name,
140 ctx_type='node')
141
142 bootdev = None
143 try:
144 session = self.get_redfish_session(n)
145 bootdev = self.exec_redfish_command(n, session, RedfishSession.get_bootdev)
146 if bootdev.get('bootdev', '') != 'Pxe':
147 self.exec_redfish_command(n, session, RedfishSession.set_bootdev, 'Pxe')
148 bootdev = self.exec_redfish_command(n, session, RedfishSession.get_bootdev)
149 session.close_session()
150 except errors.DriverError:
151 pass
152
153 if bootdev is not None and (bootdev.get('bootdev',
154 '') == 'Pxe'):
155 self.task.add_status_msg(
156 msg="Set bootdev to PXE.",
157 error=False,
158 ctx=n.name,
159 ctx_type='node')
160 self.logger.debug("%s reports bootdev of network" % n.name)
161 self.task.success(focus=n.name)
162 else:
163 self.task.add_status_msg(
164 msg="Unable to set bootdev to PXE.",
165 error=True,
166 ctx=n.name,
167 ctx_type='node')
168 self.task.failure(focus=n.name)
169 self.logger.warning(
170 "Unable to set node %s to PXE boot." % (n.name))
171
172 self.task.set_status(hd_fields.TaskStatus.Complete)
173 self.task.save()
174 return
175
176
177class PowerOffNode(RedfishBaseAction):
178 """Action to power off a node via Redfish."""
179
180 def start(self):
181 self.task.set_status(hd_fields.TaskStatus.Running)
182 self.task.save()
183
184 node_list = self.orchestrator.get_target_nodes(self.task)
185
186 for n in node_list:
187 self.logger.debug("Sending set_power = off command to %s" % n.name)
188 self.task.add_status_msg(
189 msg="Sending set_power = off command.",
190 error=False,
191 ctx=n.name,
192 ctx_type='node')
193 session = self.get_redfish_session(n)
194
195 # If power is already off, continue with the next node
196 power_state = self.exec_redfish_command(n, RedfishSession.get_power)
197 if power_state is not None and (power_state.get(
198 'powerstate', '') == 'Off'):
199 self.task.add_status_msg(
200 msg="Node reports power off.",
201 error=False,
202 ctx=n.name,
203 ctx_type='node')
204 self.logger.debug(
205 "Node %s reports powerstate already off. No action required" % n.name)
206 self.task.success(focus=n.name)
207 continue
208
209 self.exec_redfish_command(n, session, RedfishSession.set_power, 'ForceOff')
210
211 attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
212
213 while attempts > 0:
214 self.logger.debug("Polling powerstate waiting for success.")
215 power_state = self.exec_redfish_command(n, RedfishSession.get_power)
216 if power_state is not None and (power_state.get(
217 'powerstate', '') == 'Off'):
218 self.task.add_status_msg(
219 msg="Node reports power off.",
220 error=False,
221 ctx=n.name,
222 ctx_type='node')
223 self.logger.debug(
224 "Node %s reports powerstate of off" % n.name)
225 self.task.success(focus=n.name)
226 break
227 time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
228 attempts = attempts - 1
229
230 if power_state is not None and (power_state.get('powerstate', '')
231 != 'Off'):
232 self.task.add_status_msg(
233 msg="Node failed to power off.",
234 error=True,
235 ctx=n.name,
236 ctx_type='node')
237 self.logger.error("Giving up on Redfish command to %s" % n.name)
238 self.task.failure(focus=n.name)
239
240 session.close_session()
241
242 self.task.set_status(hd_fields.TaskStatus.Complete)
243 self.task.save()
244 return
245
246
247class PowerOnNode(RedfishBaseAction):
248 """Action to power on a node via Redfish."""
249
250 def start(self):
251 self.task.set_status(hd_fields.TaskStatus.Running)
252 self.task.save()
253
254 node_list = self.orchestrator.get_target_nodes(self.task)
255
256 for n in node_list:
257 self.logger.debug("Sending set_power = on command to %s" % n.name)
258 self.task.add_status_msg(
259 msg="Sending set_power = on command.",
260 error=False,
261 ctx=n.name,
262 ctx_type='node')
263 session = self.get_redfish_session(n)
264
265 # If power is already on, continue with the next node
266 power_state = self.exec_redfish_command(n, RedfishSession.get_power)
267 if power_state is not None and (power_state.get(
268 'powerstate', '') == 'On'):
269 self.task.add_status_msg(
270 msg="Node reports power on.",
271 error=False,
272 ctx=n.name,
273 ctx_type='node')
274 self.logger.debug(
275 "Node %s reports powerstate already on. No action required" % n.name)
276 self.task.success(focus=n.name)
277 continue
278
279 self.exec_redfish_command(n, session, RedfishSession.set_power, 'On')
280
281 attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
282
283 while attempts > 0:
284 self.logger.debug("Polling powerstate waiting for success.")
285 power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
286 if power_state is not None and (power_state.get(
287 'powerstate', '') == 'On'):
288 self.logger.debug(
289 "Node %s reports powerstate of on" % n.name)
290 self.task.add_status_msg(
291 msg="Node reports power on.",
292 error=False,
293 ctx=n.name,
294 ctx_type='node')
295 self.task.success(focus=n.name)
296 break
297 time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
298 attempts = attempts - 1
299
300 if power_state is not None and (power_state.get('powerstate', '')
301 != 'On'):
302 self.task.add_status_msg(
303 msg="Node failed to power on.",
304 error=True,
305 ctx=n.name,
306 ctx_type='node')
307 self.logger.error("Giving up on Redfish command to %s" % n.name)
308 self.task.failure(focus=n.name)
309
310 session.close_session()
311
312 self.task.set_status(hd_fields.TaskStatus.Complete)
313 self.task.save()
314 return
315
316
317class PowerCycleNode(RedfishBaseAction):
318 """Action to hard powercycle a node via Redfish."""
319
320 def start(self):
321 self.task.set_status(hd_fields.TaskStatus.Running)
322 self.task.save()
323
324 node_list = self.orchestrator.get_target_nodes(self.task)
325
326 for n in node_list:
327 self.logger.debug("Sending set_power = off command to %s" % n.name)
328 self.task.add_status_msg(
329 msg="Power cycling node via Redfish.",
330 error=False,
331 ctx=n.name,
332 ctx_type='node')
333 session = self.get_redfish_session(n)
334 self.exec_redfish_command(n, session, RedfishSession.set_power, 'ForceOff')
335
336 # Wait for power state of off before booting back up
337 attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
338
339 while attempts > 0:
340 power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
341 if power_state is not None and power_state.get(
342 'powerstate', '') == 'Off':
343 self.logger.debug("%s reports powerstate of off" % n.name)
344 break
345 elif power_state is None:
346 self.logger.debug(
347 "No response on Redfish power query to %s" % n.name)
348 time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
349 attempts = attempts - 1
350
351 if power_state.get('powerstate', '') != 'Off':
352 self.task.add_status_msg(
353 msg="Failed to power down during power cycle.",
354 error=True,
355 ctx=n.name,
356 ctx_type='node')
357 self.logger.warning(
358 "Failed powering down node %s during power cycle task" %
359 n.name)
360 self.task.failure(focus=n.name)
361 break
362
363 self.logger.debug("Sending set_power = on command to %s" % n.name)
364 self.exec_redfish_command(n, session, RedfishSession.set_power, 'On')
365
366 attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
367
368 while attempts > 0:
369 power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
370 if power_state is not None and power_state.get(
371 'powerstate', '') == 'On':
372 self.logger.debug("%s reports powerstate of on" % n.name)
373 break
374 elif power_state is None:
375 self.logger.debug(
376 "No response on Redfish power query to %s" % n.name)
377 time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
378 attempts = attempts - 1
379
380 if power_state is not None and (power_state.get('powerstate',
381 '') == 'On'):
382 self.task.add_status_msg(
383 msg="Node power cycle complete.",
384 error=False,
385 ctx=n.name,
386 ctx_type='node')
387 self.task.success(focus=n.name)
388 else:
389 self.task.add_status_msg(
390 msg="Failed to power up during power cycle.",
391 error=True,
392 ctx=n.name,
393 ctx_type='node')
394 self.logger.warning(
395 "Failed powering up node %s during power cycle task" %
396 n.name)
397 self.task.failure(focus=n.name)
398
399 session.close_session()
400
401 self.task.set_status(hd_fields.TaskStatus.Complete)
402 self.task.save()
403 return
404
405
406class InterrogateOob(RedfishBaseAction):
407 """Action to complete a basic interrogation of the node Redfish interface."""
408
409 def start(self):
410 self.task.set_status(hd_fields.TaskStatus.Running)
411 self.task.save()
412
413 node_list = self.orchestrator.get_target_nodes(self.task)
414
415 for n in node_list:
416 try:
417 self.logger.debug(
418 "Interrogating node %s Redfish interface." % n.name)
419 session = self.get_redfish_session(n)
420 powerstate = self.exec_redfish_command(n, session, RedfishSession.get_power)
421 session.close_session()
422 if powerstate is None:
423 raise errors.DriverError()
424 self.task.add_status_msg(
425 msg="Redfish interface interrogation yielded powerstate %s" %
426 powerstate.get('powerstate'),
427 error=False,
428 ctx=n.name,
429 ctx_type='node')
430 self.task.success(focus=n.name)
431 except errors.DriverError:
432 self.logger.debug(
433 "Interrogating node %s Redfish interface failed." % n.name)
434 self.task.add_status_msg(
435 msg="Redfish interface interrogation failed.",
436 error=True,
437 ctx=n.name,
438 ctx_type='node')
439 self.task.failure(focus=n.name)
440
441 self.task.set_status(hd_fields.TaskStatus.Complete)
442 self.task.save()
443 return
diff --git a/python/drydock_provisioner/drivers/oob/redfish_driver/client.py b/python/drydock_provisioner/drivers/oob/redfish_driver/client.py
new file mode 100644
index 0000000..48225de
--- /dev/null
+++ b/python/drydock_provisioner/drivers/oob/redfish_driver/client.py
@@ -0,0 +1,177 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Redfish object to provide commands.
15
16Uses Redfish client to communicate to node.
17"""
18
19from redfish import AuthMethod, redfish_client
20from redfish.rest.v1 import ServerDownOrUnreachableError
21from redfish.rest.v1 import InvalidCredentialsError
22from redfish.rest.v1 import RetriesExhaustedError
23
24class RedfishSession(object):
25 """Redfish Client to provide OOB commands"""
26
27 def __init__(self, host, account, password, use_ssl=True, connection_retries=10):
28 try:
29 if use_ssl:
30 redfish_url = 'https://' + host
31 else:
32 redfish_url = 'http://' + host
33 self.redfish_client = redfish_client(base_url=redfish_url,
34 username=account,
35 password=password)
36
37 self.redfish_client.MAX_RETRY = connection_retries
38 self.redfish_client.login(auth=AuthMethod.SESSION)
39 except RetriesExhaustedError:
40 raise RedfishException("Login failed: Retries exhausted")
41 except InvalidCredentialsError:
42 raise RedfishException("Login failed: Invalid credentials")
43 except ServerDownOrUnreachableError:
44 raise RedfishException("Login failed: Server unreachable")
45
46 def __del__(self):
47 self.redfish_client.logout()
48
49 def close_session(self):
50 self.redfish_client.logout()
51
52 def get_system_instance(self):
53 response = self.redfish_client.get("/redfish/v1/Systems")
54
55 if response.status != 200:
56 raise RedfishException(response._read)
57
58 # Assumption that only one system is available on Node
59 if response.dict["Members@odata.count"] != 1:
60 raise RedfishException("Number of systems are more than one in the node")
61 instance = response.dict["Members"][0]["@odata.id"]
62
63 return instance
64
65 def get_bootdev(self):
66 """Get current boot type information from Node.
67
68 :raises: RedfishException on an error
69 :return: dict -- response will return as dict in format of
70 {'bootdev': bootdev}
71 """
72 instance = self.get_system_instance()
73 response = self.redfish_client.get(path=instance)
74
75 if response.status != 200:
76 raise RedfishException(response._read)
77
78 bootdev = response.dict["Boot"]["BootSourceOverrideTarget"]
79 return {'bootdev': bootdev}
80
81 def set_bootdev(self, bootdev, **kwargs):
82 """Set boot type on the Node for next boot.
83
84 :param bootdev: Boot source for the next boot
85 * None - Boot from the normal boot device.
86 * Pxe - Boot from network
87 * Cd - Boot from CD/DVD disc
88 * Usb - Boot from USB device specified by system BIOS
89 * Hdd - Boot from Hard drive
90 * BiosSetup - Boot to bios setup utility
91 * Utilities - Boot manufacurer utlities program
92 * UefiTarget - Boot to the UEFI Device specified in the
93 UefiTargetBootSourceOverride property
94 * UefiShell - Boot to the UEFI Shell
95 * UefiHttp - Boot from UEFI HTTP network location
96 :param **kwargs: To specify extra arguments for a given bootdev
97 Example to specify UefiTargetBootSourceOverride value
98 for bootdev UefiTarget
99 :raises: RedfishException on an error
100 :return: dict -- response will return as dict in format of
101 {'bootdev': bootdev}
102 """
103 instance = self.get_system_instance()
104
105 payload = {
106 "Boot": {
107 "BootSourceOverrideEnabled": "Once",
108 "BootSourceOverrideTarget": bootdev,
109 }
110 }
111
112 if bootdev == 'UefiTarget':
113 payload['Boot']['UefiTargetBootSourceOverride'] = kwargs.get(
114 'UefiTargetBootSourceOverride', '')
115
116 response = self.redfish_client.patch(path=instance, body=payload)
117 if response.status != 200:
118 raise RedfishException(response._read)
119
120 return {'bootdev': bootdev}
121
122 def get_power(self):
123 """Get current power state information from Node.
124
125 :raises: RedfishException on an error
126 :return: dict -- response will return as dict in format of
127 {'powerstate': powerstate}
128 """
129 instance = self.get_system_instance()
130
131 response = self.redfish_client.get(path=instance)
132 if response.status != 200:
133 raise RedfishException(response._read)
134
135 powerstate = response.dict["PowerState"]
136 return {'powerstate': powerstate}
137
138 def set_power(self, powerstate):
139 """Request power change on the node.
140
141 :param powerstate: set power change
142 * On - Power On the unit
143 * ForceOff - Turn off immediately (non graceful)
144 * PushPowerButton - Simulate pressing physical
145 power button
146 * GracefulRestart - Perform a graceful shutdown
147 and then start
148
149 :raises: RedfishException on an error
150 :return: dict -- response will return as dict in format of
151 {'powerstate': powerstate}
152 """
153 instance = self.get_system_instance()
154
155 if powerstate not in ["On", "ForceOff", "PushPowerButton", "GracefulRestart"]:
156 raise RedfishException("Unsupported powerstate")
157
158 current_state = self.get_power()
159 if (powerstate == "On" and current_state["powerstate"] == "On") or \
160 (powerstate == "ForceOff" and current_state["powerstate"] == "Off"):
161 return {'powerstate': powerstate}
162
163 payload = {
164 "ResetType": powerstate
165 }
166
167 url = instance + "/Actions/ComputerSystem.Reset"
168 response = self.redfish_client.post(path=url, body=payload)
169 if response.status in [200, 201, 204]:
170 return {'powerstate': powerstate}
171 else:
172 raise RedfishException(response._read)
173
174
175class RedfishException(Exception):
176 """Redfish Exception with error in message"""
177 pass
diff --git a/python/drydock_provisioner/drivers/oob/redfish_driver/driver.py b/python/drydock_provisioner/drivers/oob/redfish_driver/driver.py
new file mode 100644
index 0000000..fab298d
--- /dev/null
+++ b/python/drydock_provisioner/drivers/oob/redfish_driver/driver.py
@@ -0,0 +1,171 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Driver for controlling OOB interface via Redfish.
15
16Based on Redfish Rest API specification.
17"""
18
19import uuid
20import logging
21import concurrent.futures
22
23from oslo_config import cfg
24
25import drydock_provisioner.error as errors
26import drydock_provisioner.config as config
27
28import drydock_provisioner.objects.fields as hd_fields
29
30import drydock_provisioner.drivers.oob.driver as oob_driver
31import drydock_provisioner.drivers.driver as generic_driver
32
33from .actions.oob import ValidateOobServices
34from .actions.oob import ConfigNodePxe
35from .actions.oob import SetNodeBoot
36from .actions.oob import PowerOffNode
37from .actions.oob import PowerOnNode
38from .actions.oob import PowerCycleNode
39from .actions.oob import InterrogateOob
40
41
42class RedfishDriver(oob_driver.OobDriver):
43 """Driver for executing OOB actions via Redfish library."""
44
45 redfish_driver_options = [
46 cfg.IntOpt(
47 'max_retries',
48 default=10,
49 min=1,
50 help='Maximum number of connection retries to Redfish server'),
51 cfg.IntOpt(
52 'power_state_change_max_retries',
53 default=18,
54 min=1,
55 help='Maximum reties to wait for power state change'),
56 cfg.IntOpt(
57 'power_state_change_retry_interval',
58 default=10,
59 help='Polling interval in seconds between retries for power state change'),
60 cfg.BoolOpt(
61 'use_ssl',
62 default=True,
63 help='Use SSL to communicate with Redfish API server'),
64 ]
65
66 oob_types_supported = ['redfish']
67
68 driver_name = "redfish_driver"
69 driver_key = "redfish_driver"
70 driver_desc = "Redfish OOB Driver"
71
72 action_class_map = {
73 hd_fields.OrchestratorAction.ValidateOobServices: ValidateOobServices,
74 hd_fields.OrchestratorAction.ConfigNodePxe: ConfigNodePxe,
75 hd_fields.OrchestratorAction.SetNodeBoot: SetNodeBoot,
76 hd_fields.OrchestratorAction.PowerOffNode: PowerOffNode,
77 hd_fields.OrchestratorAction.PowerOnNode: PowerOnNode,
78 hd_fields.OrchestratorAction.PowerCycleNode: PowerCycleNode,
79 hd_fields.OrchestratorAction.InterrogateOob: InterrogateOob,
80 }
81
82 def __init__(self, **kwargs):
83 super().__init__(**kwargs)
84
85 cfg.CONF.register_opts(
86 RedfishDriver.redfish_driver_options, group=RedfishDriver.driver_key)
87
88 self.logger = logging.getLogger(
89 config.config_mgr.conf.logging.oobdriver_logger_name)
90
91 def execute_task(self, task_id):
92 task = self.state_manager.get_task(task_id)
93
94 if task is None:
95 self.logger.error("Invalid task %s" % (task_id))
96 raise errors.DriverError("Invalid task %s" % (task_id))
97
98 if task.action not in self.supported_actions:
99 self.logger.error("Driver %s doesn't support task action %s" %
100 (self.driver_desc, task.action))
101 raise errors.DriverError("Driver %s doesn't support task action %s"
102 % (self.driver_desc, task.action))
103
104 task.set_status(hd_fields.TaskStatus.Running)
105 task.save()
106
107 target_nodes = self.orchestrator.get_target_nodes(task)
108
109 with concurrent.futures.ThreadPoolExecutor(max_workers=16) as e:
110 subtask_futures = dict()
111 for n in target_nodes:
112 sub_nf = self.orchestrator.create_nodefilter_from_nodelist([n])
113 subtask = self.orchestrator.create_task(
114 action=task.action,
115 design_ref=task.design_ref,
116 node_filter=sub_nf)
117 task.register_subtask(subtask)
118 self.logger.debug(
119 "Starting Redfish subtask %s for action %s on node %s" %
120 (str(subtask.get_id()), task.action, n.name))
121
122 action_class = self.action_class_map.get(task.action, None)
123 if action_class is None:
124 self.logger.error(
125 "Could not find action resource for action %s" %
126 task.action)
127 self.task.failure()
128 break
129 action = action_class(subtask, self.orchestrator,
130 self.state_manager)
131 subtask_futures[subtask.get_id().bytes] = e.submit(
132 action.start)
133
134 timeout = config.config_mgr.conf.timeouts.drydock_timeout
135 finished, running = concurrent.futures.wait(
136 subtask_futures.values(), timeout=(timeout * 60))
137
138 for t, f in subtask_futures.items():
139 if not f.done():
140 task.add_status_msg(
141 msg="Subtask %s timed out before completing.",
142 error=True,
143 ctx=str(uuid.UUID(bytes=t)),
144 ctx_type='task')
145 task.failure()
146 else:
147 if f.exception():
148 self.logger.error(
149 "Uncaught exception in subtask %s" % str(
150 uuid.UUID(bytes=t)),
151 exc_info=f.exception())
152 task.align_result()
153 task.bubble_results()
154 task.set_status(hd_fields.TaskStatus.Complete)
155 task.save()
156
157 return
158
159
160class RedfishActionRunner(generic_driver.DriverActionRunner):
161 """Threaded runner for a Redfish Action."""
162
163 def __init__(self, **kwargs):
164 super().__init__(**kwargs)
165
166 self.logger = logging.getLogger(
167 config.config_mgr.conf.logging.oobdriver_logger_name)
168
169
170def list_opts():
171 return {RedfishDriver.driver_key: RedfishDriver.redfish_driver_options}
diff --git a/python/requirements-direct.txt b/python/requirements-direct.txt
index 974097c..b237029 100644
--- a/python/requirements-direct.txt
+++ b/python/requirements-direct.txt
@@ -24,3 +24,4 @@ ulid2==0.1.1
24defusedxml===0.5.0 24defusedxml===0.5.0
25libvirt-python==3.10.0 25libvirt-python==3.10.0
26beaker==1.9.1 26beaker==1.9.1
27redfish==2.0.1
diff --git a/python/requirements-lock.txt b/python/requirements-lock.txt
index 94491ae..969ef0b 100644
--- a/python/requirements-lock.txt
+++ b/python/requirements-lock.txt
@@ -61,6 +61,7 @@ python-keystoneclient==3.17.0
61python-mimeparse==1.6.0 61python-mimeparse==1.6.0
62pytz==2018.5 62pytz==2018.5
63PyYAML==3.12 63PyYAML==3.12
64redfish==2.0.1
64repoze.lru==0.7 65repoze.lru==0.7
65requests==2.19.1 66requests==2.19.1
66rfc3986==1.1.0 67rfc3986==1.1.0