Merge ~ltrager/maas:hmcz_transitional_states into maas:master

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: a3f418656a3810cd78ba3ad2a2f8f5ff484d97ee
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:hmcz_transitional_states
Merge into: maas:master
Diff against target: 206 lines (+125/-3)
2 files modified
src/provisioningserver/drivers/power/hmcz.py (+44/-2)
src/provisioningserver/drivers/power/tests/test_hmcz.py (+81/-1)
Reviewer Review Type Date Requested Status
Adam Collard (community) Approve
MAAS Lander Approve
Review via email: mp+400156@code.launchpad.net

Commit message

HMCZ - Wait for transitional states

IBM Z has transitional states starting and stopping. When these operations
are occurring the HMC will reject any call to change states or properties.
If detected wait for the transition to complete before making a call to
the HMC.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b hmcz_transitional_states lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 1fe22b9f21ecd0fa38ec5832a4bf0f31aab30f87

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b hmcz_transitional_states lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 0fb0f0eaefb085fc8e19037daf24c390c66282e9

review: Approve
Revision history for this message
Adam Collard (adam-collard) wrote :

basically looks good - some nit picks

review: Approve
c27b242... by Lee Trager

Merge branch 'master' into hmcz_transitional_states

a3f4186... by Lee Trager

adam-collard fixes

Revision history for this message
Lee Trager (ltrager) wrote :

Thanks for the review. Updated as suggested!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/provisioningserver/drivers/power/hmcz.py b/src/provisioningserver/drivers/power/hmcz.py
index 5ccef9a..a4d7a56 100644
--- a/src/provisioningserver/drivers/power/hmcz.py
+++ b/src/provisioningserver/drivers/power/hmcz.py
@@ -18,14 +18,18 @@ from provisioningserver.drivers import (
18 make_setting_field,18 make_setting_field,
19 SETTING_SCOPE,19 SETTING_SCOPE,
20)20)
21from provisioningserver.drivers.power import PowerActionError, PowerDriver21from provisioningserver.drivers.power import (
22 PowerActionError,
23 PowerDriver,
24 PowerError,
25)
22from provisioningserver.logger import get_maas_logger26from provisioningserver.logger import get_maas_logger
23from provisioningserver.rpc.utils import commission_node, create_node27from provisioningserver.rpc.utils import commission_node, create_node
24from provisioningserver.utils import typed28from provisioningserver.utils import typed
25from provisioningserver.utils.twisted import asynchronous, threadDeferred29from provisioningserver.utils.twisted import asynchronous, threadDeferred
2630
27try:31try:
28 from zhmcclient import Client, NotFound, Session32 from zhmcclient import Client, NotFound, Session, StatusTimeout
29except ImportError:33except ImportError:
30 no_zhmcclient = True34 no_zhmcclient = True
31else:35else:
@@ -101,6 +105,21 @@ class HMCZPowerDriver(PowerDriver):
101 # IBM is aware this isn't optimal for us so they are looking into105 # IBM is aware this isn't optimal for us so they are looking into
102 # modifying IBM Z to go into a stopped state.106 # modifying IBM Z to go into a stopped state.
103 partition.stop(wait_for_completion=True)107 partition.stop(wait_for_completion=True)
108 elif status == "stopping":
109 # The HMC does not allow a machine to be powered on if its
110 # currently stopping. Wait 120s for it which should be more
111 # than enough time.
112 try:
113 partition.wait_for_status("stopped", 120)
114 except StatusTimeout:
115 # If 120s isn't enough time raise a PowerError() which will
116 # trigger the builtin retry code in the base PowerDriver()
117 # class.
118 raise PowerError(
119 "Partition is stuck in a "
120 f"{partition.get_property('status')} state!"
121 )
122
104 partition.start(wait_for_completion=False)123 partition.start(wait_for_completion=False)
105124
106 @typed125 @typed
@@ -109,6 +128,21 @@ class HMCZPowerDriver(PowerDriver):
109 def power_off(self, system_id: str, context: dict):128 def power_off(self, system_id: str, context: dict):
110 """Power off IBM Z DPM."""129 """Power off IBM Z DPM."""
111 partition = self._get_partition(context)130 partition = self._get_partition(context)
131 status = partition.get_property("status")
132 if status == "starting":
133 # The HMC does not allow a machine to be powered off if its
134 # currently starting. Wait 120s for it which should be more
135 # than enough time.
136 try:
137 partition.wait_for_status("active", 120)
138 except StatusTimeout:
139 # If 120s isn't enough time raise a PowerError() which will
140 # trigger the builtin retry code in the base PowerDriver()
141 # class.
142 raise PowerError(
143 "Partition is stuck in a "
144 f"{partition.get_property('status')} state!"
145 )
112 partition.stop(wait_for_completion=False)146 partition.stop(wait_for_completion=False)
113147
114 @typed148 @typed
@@ -147,6 +181,14 @@ class HMCZPowerDriver(PowerDriver):
147 :param order: An ordered list of network or storage devices.181 :param order: An ordered list of network or storage devices.
148 """182 """
149 partition = self._get_partition(context)183 partition = self._get_partition(context)
184 status = partition.get_property("status")
185
186 if status in {"starting", "stopping"}:
187 # The HMC does not allow a machine's boot order to be reconfigured
188 # while in a transitional state. Wait for it to complete. If this
189 # times out allow it to be raised so the region can log it.
190 partition.wait_for_status(["stopped", "active"], 120)
191
150 # You can only specify one boot device on IBM Z192 # You can only specify one boot device on IBM Z
151 boot_device = order[0]193 boot_device = order[0]
152 if boot_device.get("mac_address"):194 if boot_device.get("mac_address"):
diff --git a/src/provisioningserver/drivers/power/tests/test_hmcz.py b/src/provisioningserver/drivers/power/tests/test_hmcz.py
index 20d4f76..d76b347 100644
--- a/src/provisioningserver/drivers/power/tests/test_hmcz.py
+++ b/src/provisioningserver/drivers/power/tests/test_hmcz.py
@@ -9,7 +9,9 @@ import random
9import typing9import typing
10from unittest.mock import Mock10from unittest.mock import Mock
1111
12import pytest
12from twisted.internet.defer import inlineCallbacks, returnValue, succeed13from twisted.internet.defer import inlineCallbacks, returnValue, succeed
14from zhmcclient import StatusTimeout
13from zhmcclient_mock import FakedSession15from zhmcclient_mock import FakedSession
1416
15from maastesting.factory import factory17from maastesting.factory import factory
@@ -20,7 +22,7 @@ from maastesting.matchers import (
20)22)
21from maastesting.testcase import MAASTestCase, MAASTwistedRunTest23from maastesting.testcase import MAASTestCase, MAASTwistedRunTest
22from provisioningserver.drivers.power import hmcz as hmcz_module24from provisioningserver.drivers.power import hmcz as hmcz_module
23from provisioningserver.drivers.power import PowerActionError25from provisioningserver.drivers.power import PowerActionError, PowerError
24from provisioningserver.rpc import clusterservice, region26from provisioningserver.rpc import clusterservice, region
25from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture27from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture
2628
@@ -160,6 +162,28 @@ class TestHMCZPowerDriver(MAASTestCase):
160 )162 )
161163
162 @inlineCallbacks164 @inlineCallbacks
165 def test_power_on_waits_for_stopping_state(self):
166 mock_get_partition = self.patch(self.hmcz, "_get_partition")
167 mock_get_partition.return_value.get_property.return_value = "stopping"
168 yield self.hmcz.power_on(None, self.make_context())
169 mock_get_partition.return_value.wait_for_status.assert_called_once_with(
170 "stopped", 120
171 )
172 mock_get_partition.return_value.start.assert_called_once_with(
173 wait_for_completion=False
174 )
175
176 @inlineCallbacks
177 def test_power_on_times_out_waiting_for_stopping_state(self):
178 mock_get_partition = self.patch(self.hmcz, "_get_partition")
179 mock_get_partition.return_value.get_property.return_value = "stopping"
180 mock_get_partition.return_value.wait_for_status.side_effect = (
181 StatusTimeout(None, None, None, None)
182 )
183 with pytest.raises(PowerError):
184 yield self.hmcz.power_on(None, self.make_context())
185
186 @inlineCallbacks
163 def test_power_off(self):187 def test_power_off(self):
164 mock_get_partition = self.patch(self.hmcz, "_get_partition")188 mock_get_partition = self.patch(self.hmcz, "_get_partition")
165 yield self.hmcz.power_off(None, self.make_context())189 yield self.hmcz.power_off(None, self.make_context())
@@ -169,6 +193,28 @@ class TestHMCZPowerDriver(MAASTestCase):
169 )193 )
170194
171 @inlineCallbacks195 @inlineCallbacks
196 def test_power_off_waits_for_starting_state(self):
197 mock_get_partition = self.patch(self.hmcz, "_get_partition")
198 mock_get_partition.return_value.get_property.return_value = "starting"
199 yield self.hmcz.power_off(None, self.make_context())
200 mock_get_partition.return_value.wait_for_status.assert_called_once_with(
201 "active", 120
202 )
203 mock_get_partition.return_value.stop.assert_called_once_with(
204 wait_for_completion=False
205 )
206
207 @inlineCallbacks
208 def test_power_off_timesout_waiting_for_stopping_state(self):
209 mock_get_partition = self.patch(self.hmcz, "_get_partition")
210 mock_get_partition.return_value.get_property.return_value = "starting"
211 mock_get_partition.return_value.wait_for_status.side_effect = (
212 StatusTimeout(None, None, None, None)
213 )
214 with pytest.raises(PowerError):
215 yield self.hmcz.power_off(None, self.make_context())
216
217 @inlineCallbacks
172 def test_power_query_starting(self):218 def test_power_query_starting(self):
173 power_partition_name = factory.make_name("power_partition_name")219 power_partition_name = factory.make_name("power_partition_name")
174 cpc = self.fake_session.hmc.cpcs.add(220 cpc = self.fake_session.hmc.cpcs.add(
@@ -430,6 +476,40 @@ class TestHMCZPowerDriver(MAASTestCase):
430 }476 }
431 )477 )
432478
479 @inlineCallbacks
480 def test_set_boot_order_waits_for_starting_state(self):
481 # Mock must be used as partition.wait_for_status() hangs when not
482 # connected to a real HMC
483 mock_get_partition = self.patch(self.hmcz, "_get_partition")
484 mock_get_partition.return_value.get_property.return_value = "starting"
485
486 yield self.hmcz.set_boot_order(
487 None,
488 self.make_context(),
489 [{"mac_address": factory.make_mac_address()}],
490 )
491
492 mock_get_partition.return_value.wait_for_status.assert_called_once_with(
493 ["stopped", "active"], 120
494 )
495
496 @inlineCallbacks
497 def test_set_boot_order_waits_for_stopping_state(self):
498 # Mock must be used as partition.wait_for_status() hangs when not
499 # connected to a real HMC
500 mock_get_partition = self.patch(self.hmcz, "_get_partition")
501 mock_get_partition.return_value.get_property.return_value = "stopping"
502
503 yield self.hmcz.set_boot_order(
504 None,
505 self.make_context(),
506 [{"mac_address": factory.make_mac_address()}],
507 )
508
509 mock_get_partition.return_value.wait_for_status.assert_called_once_with(
510 ["stopped", "active"], 120
511 )
512
433513
434@dataclass514@dataclass
435class FakeNode:515class FakeNode:

Subscribers

People subscribed via source and target branches