@@ -201,6 +201,91 @@ def test_redfish_reset_400_bad_request_not_off(self, mock_get, mock_post):
201201 mock_post .assert_called_once ()
202202 mock_get .assert_called_once ()
203203
204+ @patch ('instanceha.requests.post' )
205+ @patch ('instanceha.requests.get' )
206+ @patch ('instanceha.time.sleep' )
207+ def test_redfish_reset_400_on_action_retries_when_off (self , mock_sleep , mock_get , mock_post ):
208+ """Test Redfish reset retries On action with 400 when server is OFF.
209+
210+ Some hardware (e.g. PRIMEQUEST 4400E) returns 400 Bad Request when
211+ an On operation is sent too soon after ForceOff, because the system
212+ is not yet ready to accept the power-on command even though
213+ PowerState is already Off. The retry logic should handle this.
214+ """
215+ mock_config_manager = Mock ()
216+ mock_config_manager .get_requests_ssl_config .return_value = False
217+
218+ # First two POST attempts return 400, third succeeds with 200
219+ mock_post .side_effect = [
220+ Mock (status_code = 400 ),
221+ Mock (status_code = 400 ),
222+ Mock (status_code = 200 ),
223+ ]
224+
225+ # GET returns OFF for both 400 responses (server is off but not ready)
226+ mock_get_response = Mock ()
227+ mock_get_response .status_code = 200
228+ mock_get_response .json .return_value = {'PowerState' : 'Off' }
229+ mock_get .return_value = mock_get_response
230+
231+ result = instanceha ._redfish_reset (
232+ 'http://test-server/redfish/v1/Systems/1' , 'user' , 'pass' , 30 ,
233+ 'On' , mock_config_manager )
234+
235+ self .assertTrue (result )
236+ self .assertEqual (mock_post .call_count , 3 )
237+ self .assertEqual (mock_get .call_count , 2 ) # Called on each 400
238+ self .assertEqual (mock_sleep .call_count , 2 ) # Sleep between retries
239+
240+ @patch ('instanceha.requests.post' )
241+ @patch ('instanceha.requests.get' )
242+ @patch ('instanceha.time.sleep' )
243+ def test_redfish_reset_400_on_action_retry_exhausted (self , mock_sleep , mock_get , mock_post ):
244+ """Test Redfish reset fails after max retries on 400 for On action."""
245+ mock_config_manager = Mock ()
246+ mock_config_manager .get_requests_ssl_config .return_value = False
247+
248+ # All POST attempts return 400
249+ mock_post .return_value = Mock (status_code = 400 )
250+
251+ # GET always returns OFF (server off but never ready)
252+ mock_get_response = Mock ()
253+ mock_get_response .status_code = 200
254+ mock_get_response .json .return_value = {'PowerState' : 'Off' }
255+ mock_get .return_value = mock_get_response
256+
257+ result = instanceha ._redfish_reset (
258+ 'http://test-server/redfish/v1/Systems/1' , 'user' , 'pass' , 30 ,
259+ 'On' , mock_config_manager )
260+
261+ self .assertFalse (result )
262+ self .assertEqual (mock_post .call_count , 3 ) # MAX_FENCING_RETRIES
263+ self .assertEqual (mock_get .call_count , 3 )
264+ self .assertEqual (mock_sleep .call_count , 2 ) # No sleep after last attempt
265+
266+ @patch ('instanceha.requests.post' )
267+ @patch ('instanceha.requests.get' )
268+ def test_redfish_reset_400_forceoff_already_off (self , mock_get , mock_post ):
269+ """Test Redfish reset with 400 for ForceOff when already off succeeds immediately."""
270+ mock_config_manager = Mock ()
271+ mock_config_manager .get_requests_ssl_config .return_value = False
272+
273+ mock_post .return_value = Mock (status_code = 400 )
274+
275+ mock_get_response = Mock ()
276+ mock_get_response .status_code = 200
277+ mock_get_response .json .return_value = {'PowerState' : 'Off' }
278+ mock_get .return_value = mock_get_response
279+
280+ result = instanceha ._redfish_reset (
281+ 'http://test-server/redfish/v1/Systems/1' , 'user' , 'pass' , 30 ,
282+ 'ForceOff' , mock_config_manager )
283+
284+ # ForceOff with 400 and power state OFF should succeed immediately (no retry)
285+ self .assertTrue (result )
286+ mock_post .assert_called_once ()
287+ mock_get .assert_called_once ()
288+
204289 @patch ('instanceha.requests.post' )
205290 def test_redfish_reset_success (self , mock_post ):
206291 """Test successful Redfish reset."""
0 commit comments