Merge ~newell-jensen/maas:2.5-lp1814623 into maas:2.5

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: ce19827232ef9f86df01ec94cf0e9741b82c4302
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~newell-jensen/maas:2.5-lp1814623
Merge into: maas:2.5
Diff against target: 242 lines (+67/-34)
2 files modified
src/provisioningserver/drivers/power/redfish.py (+45/-21)
src/provisioningserver/drivers/power/tests/test_redfish.py (+22/-13)
Reviewer Review Type Date Requested Status
Newell Jensen (community) Approve
Review via email: mp+362933@code.launchpad.net

Commit message

backport 225eed04a3b40bd2b2d2b777bf70030452ecf394 - LP: #1814623 -- Don't require node_id power parameter for Redfish. If not supplied by the user, search for the first available system's id.

To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Self approved backport.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/provisioningserver/drivers/power/redfish.py b/src/provisioningserver/drivers/power/redfish.py
2index 5b47e21..bca30b5 100644
3--- a/src/provisioningserver/drivers/power/redfish.py
4+++ b/src/provisioningserver/drivers/power/redfish.py
5@@ -1,4 +1,4 @@
6-# Copyright 2018 Canonical Ltd. This software is licensed under the
7+# Copyright 2018-2019 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 """Redfish Power Driver."""
11@@ -11,7 +11,10 @@ from base64 import b64encode
12 from http import HTTPStatus
13 from io import BytesIO
14 import json
15-from os.path import join
16+from os.path import (
17+ basename,
18+ join,
19+)
20
21 from provisioningserver.drivers import (
22 make_ip_extractor,
23@@ -42,7 +45,7 @@ from twisted.web.http_headers import Headers
24 REDFISH_POWER_CONTROL_ENDPOINT = (
25 b"redfish/v1/Systems/%s/Actions/ComputerSystem.Reset/")
26
27-REDFISH_SYSTEMS_ENDPOINT = b"redfish/v1/Systems/%s/"
28+REDFISH_SYSTEMS_ENDPOINT = b"redfish/v1/Systems/"
29
30
31 class WebClientContextFactory(BrowserLikePolicyForHTTPS):
32@@ -78,13 +81,6 @@ class RedfishPowerDriverBase(PowerDriver):
33 }
34 )
35
36- def process_redfish_context(self, context):
37- """Process Redfish power driver context."""
38- url = self.get_url(context)
39- node_id = context.get('node_id').encode('utf-8')
40- headers = self.make_auth_headers(**context)
41- return url, node_id, headers
42-
43 @asynchronous
44 def redfish_request(self, method, uri, headers=None, bodyProducer=None):
45 """Send the redfish request and return the response."""
46@@ -144,8 +140,7 @@ class RedfishPowerDriver(RedfishPowerDriverBase):
47 'power_pass', "Redfish password",
48 field_type='password', required=True),
49 make_setting_field(
50- 'node_id', "Node ID",
51- scope=SETTING_SCOPE.NODE, required=True),
52+ 'node_id', "Node ID", scope=SETTING_SCOPE.NODE),
53 ]
54 ip_extractor = make_ip_extractor('power_address')
55
56@@ -154,9 +149,38 @@ class RedfishPowerDriver(RedfishPowerDriverBase):
57 return []
58
59 @inlineCallbacks
60+ def process_redfish_context(self, context):
61+ """Process Redfish power driver context.
62+
63+ Returns the basename of the first member found
64+ in the Redfish Systems:
65+
66+ "Members": [
67+ {
68+ "@odata.id": "/redfish/v1/Systems/1"
69+ }
70+ """
71+ url = self.get_url(context)
72+ headers = self.make_auth_headers(**context)
73+ node_id = context.get('node_id')
74+ if node_id:
75+ node_id = node_id.encode('utf-8')
76+ else:
77+ node_id = yield self.get_node_id(url, headers)
78+ return url, node_id, headers
79+
80+ @inlineCallbacks
81+ def get_node_id(self, url, headers):
82+ uri = join(url, REDFISH_SYSTEMS_ENDPOINT)
83+ systems, _ = yield self.redfish_request(b"GET", uri, headers)
84+ members = systems.get('Members')
85+ member = members[0].get('@odata.id')
86+ return basename(member).encode('utf-8')
87+
88+ @inlineCallbacks
89 def set_pxe_boot(self, url, node_id, headers):
90 """Set the machine with node_id to PXE boot."""
91- endpoint = REDFISH_SYSTEMS_ENDPOINT % node_id
92+ endpoint = REDFISH_SYSTEMS_ENDPOINT + b'%s/' % node_id
93 payload = FileBodyProducer(
94 BytesIO(
95 json.dumps(
96@@ -185,10 +209,10 @@ class RedfishPowerDriver(RedfishPowerDriverBase):
97
98 @asynchronous
99 @inlineCallbacks
100- def power_on(self, system_id, context):
101+ def power_on(self, node_id, context):
102 """Power on machine."""
103- url, node_id, headers = self.process_redfish_context(context)
104- power_state = yield self.power_query(system_id, context)
105+ url, node_id, headers = yield self.process_redfish_context(context)
106+ power_state = yield self.power_query(node_id, context)
107 # Power off the machine if currently on.
108 if power_state == 'on':
109 yield self.power("ForceOff", url, node_id, headers)
110@@ -199,9 +223,9 @@ class RedfishPowerDriver(RedfishPowerDriverBase):
111
112 @asynchronous
113 @inlineCallbacks
114- def power_off(self, system_id, context):
115+ def power_off(self, node_id, context):
116 """Power off machine."""
117- url, node_id, headers = self.process_redfish_context(context)
118+ url, node_id, headers = yield self.process_redfish_context(context)
119 # Set to PXE boot.
120 yield self.set_pxe_boot(url, node_id, headers)
121 # Power off the machine.
122@@ -209,9 +233,9 @@ class RedfishPowerDriver(RedfishPowerDriverBase):
123
124 @asynchronous
125 @inlineCallbacks
126- def power_query(self, system_id, context):
127+ def power_query(self, node_id, context):
128 """Power query machine."""
129- url, node_id, headers = self.process_redfish_context(context)
130- uri = join(url, REDFISH_SYSTEMS_ENDPOINT % node_id)
131+ url, node_id, headers = yield self.process_redfish_context(context)
132+ uri = join(url, REDFISH_SYSTEMS_ENDPOINT + b'%s/' % node_id)
133 node_data, _ = yield self.redfish_request(b"GET", uri, headers)
134 return node_data.get('PowerState').lower()
135diff --git a/src/provisioningserver/drivers/power/tests/test_redfish.py b/src/provisioningserver/drivers/power/tests/test_redfish.py
136index f6db6e3..ca2a771 100644
137--- a/src/provisioningserver/drivers/power/tests/test_redfish.py
138+++ b/src/provisioningserver/drivers/power/tests/test_redfish.py
139@@ -1,4 +1,4 @@
140-# Copyright 2018 Canonical Ltd. This software is licensed under the
141+# Copyright 2018-2019 Canonical Ltd. This software is licensed under the
142 # GNU Affero General Public License version 3 (see the file LICENSE).
143
144 """Tests for `provisioningserver.drivers.power.redfish`."""
145@@ -168,7 +168,6 @@ def make_context():
146 'power_address': factory.make_ipv4_address(),
147 'power_user': factory.make_name('power_user'),
148 'power_pass': factory.make_name('power_pass'),
149- 'node_id': factory.make_name('node_id'),
150 }
151
152
153@@ -332,7 +331,7 @@ class TestRedfishPowerDriver(MAASTestCase):
154 power_change = factory.make_name('power_change')
155 url = driver.get_url(context)
156 headers = driver.make_auth_headers(**context)
157- node_id = context.get('node_id').encode('utf-8')
158+ node_id = b'1'
159 mock_file_body_producer = self.patch(
160 redfish_module, 'FileBodyProducer')
161 payload = FileBodyProducer(
162@@ -354,7 +353,7 @@ class TestRedfishPowerDriver(MAASTestCase):
163 driver = RedfishPowerDriver()
164 context = make_context()
165 url = driver.get_url(context)
166- node_id = context.get('node_id').encode('utf-8')
167+ node_id = b'1'
168 headers = driver.make_auth_headers(**context)
169 mock_file_body_producer = self.patch(
170 redfish_module, 'FileBodyProducer')
171@@ -378,21 +377,23 @@ class TestRedfishPowerDriver(MAASTestCase):
172 @inlineCallbacks
173 def test__power_on(self):
174 driver = RedfishPowerDriver()
175- system_id = factory.make_name('system_id')
176 context = make_context()
177 url = driver.get_url(context)
178 headers = driver.make_auth_headers(**context)
179- node_id = context.get('node_id').encode('utf-8')
180+ node_id = b'1'
181+ mock_redfish_request = self.patch(driver, 'redfish_request')
182+ mock_redfish_request.return_value = (
183+ SAMPLE_JSON_SYSTEMS, None)
184 mock_set_pxe_boot = self.patch(driver, 'set_pxe_boot')
185 mock_power_query = self.patch(driver, 'power_query')
186 mock_power_query.return_value = "on"
187 mock_power = self.patch(driver, 'power')
188
189- yield driver.power_on(system_id, context)
190+ yield driver.power_on(node_id, context)
191 self.assertThat(mock_set_pxe_boot, MockCalledOnceWith(
192 url, node_id, headers))
193 self.assertThat(mock_power_query, MockCalledOnceWith(
194- system_id, context))
195+ node_id, context))
196 self.assertThat(mock_power, MockCallsMatch(
197 call("ForceOff", url, node_id, headers),
198 call("On", url, node_id, headers)))
199@@ -400,15 +401,17 @@ class TestRedfishPowerDriver(MAASTestCase):
200 @inlineCallbacks
201 def test__power_off(self):
202 driver = RedfishPowerDriver()
203- system_id = factory.make_name('system_id')
204 context = make_context()
205 url = driver.get_url(context)
206 headers = driver.make_auth_headers(**context)
207- node_id = context.get('node_id').encode('utf-8')
208+ node_id = b'1'
209+ mock_redfish_request = self.patch(driver, 'redfish_request')
210+ mock_redfish_request.return_value = (
211+ SAMPLE_JSON_SYSTEMS, None)
212 mock_set_pxe_boot = self.patch(driver, 'set_pxe_boot')
213 mock_power = self.patch(driver, 'power')
214
215- yield driver.power_off(system_id, context)
216+ yield driver.power_off(node_id, context)
217 self.assertThat(mock_set_pxe_boot, MockCalledOnceWith(
218 url, node_id, headers))
219 self.assertThat(mock_power, MockCalledOnceWith(
220@@ -423,7 +426,10 @@ class TestRedfishPowerDriver(MAASTestCase):
221 mock_redfish_request = self.patch(driver, 'redfish_request')
222 NODE_POWERED_ON = deepcopy(SAMPLE_JSON_SYSTEM)
223 NODE_POWERED_ON['PowerState'] = "On"
224- mock_redfish_request.return_value = (NODE_POWERED_ON, None)
225+ mock_redfish_request.side_effect = [
226+ (SAMPLE_JSON_SYSTEMS, None),
227+ (NODE_POWERED_ON, None),
228+ ]
229 power_state = yield driver.power_query(system_id, context)
230 self.assertEquals(power_state, power_change.lower())
231
232@@ -434,6 +440,9 @@ class TestRedfishPowerDriver(MAASTestCase):
233 system_id = factory.make_name('system_id')
234 context = make_context()
235 mock_redfish_request = self.patch(driver, 'redfish_request')
236- mock_redfish_request.return_value = (SAMPLE_JSON_SYSTEM, None)
237+ mock_redfish_request.side_effect = [
238+ (SAMPLE_JSON_SYSTEMS, None),
239+ (SAMPLE_JSON_SYSTEM, None),
240+ ]
241 power_state = yield driver.power_query(system_id, context)
242 self.assertEquals(power_state, power_change.lower())

Subscribers

People subscribed via source and target branches