99
1010import pytest
1111
12+ from cloudinit import subp
1213from cloudinit .atomic_helper import write_json
1314from cloudinit .cmd import status
14- from cloudinit .cmd .status import UXAppStatus , _get_systemd_status
15+ from cloudinit .cmd .status import (
16+ UXAppStatus ,
17+ _get_error_or_running_from_systemd ,
18+ _get_error_or_running_from_systemd_with_retry ,
19+ )
1520from cloudinit .subp import SubpResult
1621from cloudinit .util import ensure_file
1722from tests .unittests .helpers import wrap_and_call
@@ -59,7 +64,10 @@ class TestStatus:
5964 "Cloud-init enabled by systemd cloud-init-generator" ,
6065 ),
6166 )
62- @mock .patch (f"{ M_PATH } _get_systemd_status" , return_value = None )
67+ @mock .patch (
68+ f"{ M_PATH } _get_error_or_running_from_systemd_with_retry" ,
69+ return_value = None ,
70+ )
6371 def test_get_status_details_ds_none (
6472 self ,
6573 m_get_systemd_status ,
@@ -704,7 +712,10 @@ def fakeexists(filepath):
704712 ],
705713 )
706714 @mock .patch (M_PATH + "read_cfg_paths" )
707- @mock .patch (f"{ M_PATH } _get_systemd_status" , return_value = None )
715+ @mock .patch (
716+ f"{ M_PATH } _get_error_or_running_from_systemd_with_retry" ,
717+ return_value = None ,
718+ )
708719 def test_status_output (
709720 self ,
710721 m_get_systemd_status ,
@@ -745,7 +756,10 @@ def test_status_output(
745756 assert out == expected_status
746757
747758 @mock .patch (M_PATH + "read_cfg_paths" )
748- @mock .patch (f"{ M_PATH } _get_systemd_status" , return_value = None )
759+ @mock .patch (
760+ f"{ M_PATH } _get_error_or_running_from_systemd_with_retry" ,
761+ return_value = None ,
762+ )
749763 def test_status_wait_blocks_until_done (
750764 self , m_get_systemd_status , m_read_cfg_paths , config : Config , capsys
751765 ):
@@ -796,7 +810,10 @@ def fake_sleep(interval):
796810 assert out == "....\n status: done\n "
797811
798812 @mock .patch (M_PATH + "read_cfg_paths" )
799- @mock .patch (f"{ M_PATH } _get_systemd_status" , return_value = None )
813+ @mock .patch (
814+ f"{ M_PATH } _get_error_or_running_from_systemd_with_retry" ,
815+ return_value = None ,
816+ )
800817 def test_status_wait_blocks_until_error (
801818 self , m_get_systemd_status , m_read_cfg_paths , config : Config , capsys
802819 ):
@@ -849,7 +866,10 @@ def fake_sleep(interval):
849866 assert out == "....\n status: error\n "
850867
851868 @mock .patch (M_PATH + "read_cfg_paths" )
852- @mock .patch (f"{ M_PATH } _get_systemd_status" , return_value = None )
869+ @mock .patch (
870+ f"{ M_PATH } _get_error_or_running_from_systemd_with_retry" ,
871+ return_value = None ,
872+ )
853873 def test_status_main (
854874 self , m_get_systemd_status , m_read_cfg_paths , config : Config , capsys
855875 ):
@@ -873,15 +893,20 @@ def test_status_main(
873893 assert out == "status: running\n "
874894
875895
876- class TestSystemdStatusDetails :
896+ class TestGetErrorOrRunningFromSystemd :
897+ @pytest .fixture (autouse = True )
898+ def common_mocks (self , mocker ):
899+ mocker .patch ("cloudinit.cmd.status.sleep" )
900+ yield
901+
877902 @pytest .mark .parametrize (
878903 ["active_state" , "unit_file_state" , "sub_state" , "main_pid" , "status" ],
879904 [
880905 # To cut down on the combination of states, I'm grouping
881906 # enabled, enabled-runtime, and static into an "enabled" state
882907 # and everything else functionally disabled.
883908 # Additionally, SubStates are undocumented and may mean something
884- # different depending on the ActiveState they are mapped too .
909+ # different depending on the ActiveState they are mapped to .
885910 # Because of this I'm only testing SubState combinations seen
886911 # in real-world testing (or using "any" string if we dont care).
887912 ("activating" , "enabled" , "start" , "123" , UXAppStatus .RUNNING ),
@@ -910,7 +935,7 @@ class TestSystemdStatusDetails:
910935 ("failed" , "invalid" , "failed" , "0" , UXAppStatus .ERROR ),
911936 ],
912937 )
913- def test_get_systemd_status (
938+ def test_get_error_or_running_from_systemd (
914939 self , active_state , unit_file_state , sub_state , main_pid , status
915940 ):
916941 with mock .patch (
@@ -923,4 +948,70 @@ def test_get_systemd_status(
923948 stderr = None ,
924949 ),
925950 ):
926- assert _get_systemd_status () == status
951+ assert _get_error_or_running_from_systemd () == status
952+
953+ def test_exception_while_running (self , mocker , capsys ):
954+ m_subp = mocker .patch (
955+ f"{ M_PATH } subp.subp" ,
956+ side_effect = subp .ProcessExecutionError (
957+ "Message recipient disconnected from message bus without"
958+ " replying"
959+ ),
960+ )
961+ assert (
962+ _get_error_or_running_from_systemd_with_retry (
963+ UXAppStatus .RUNNING , wait = True
964+ )
965+ is None
966+ )
967+ assert 1 == m_subp .call_count
968+ assert "Failed to get status" not in capsys .readouterr ().err
969+
970+ def test_retry (self , mocker , capsys ):
971+ m_subp = mocker .patch (
972+ f"{ M_PATH } subp.subp" ,
973+ side_effect = [
974+ subp .ProcessExecutionError (
975+ "Message recipient disconnected from message bus without"
976+ " replying"
977+ ),
978+ subp .ProcessExecutionError (
979+ "Message recipient disconnected from message bus without"
980+ " replying"
981+ ),
982+ SubpResult (
983+ "ActiveState=activating\n UnitFileState=enabled\n "
984+ "SubState=start\n MainPID=123\n " ,
985+ stderr = None ,
986+ ),
987+ ],
988+ )
989+ assert (
990+ _get_error_or_running_from_systemd_with_retry (
991+ UXAppStatus .ERROR , wait = True
992+ )
993+ is UXAppStatus .RUNNING
994+ )
995+ assert 3 == m_subp .call_count
996+ assert "Failed to get status" not in capsys .readouterr ().err
997+
998+ def test_retry_no_wait (self , mocker , capsys ):
999+ m_subp = mocker .patch (
1000+ f"{ M_PATH } subp.subp" ,
1001+ side_effect = subp .ProcessExecutionError (
1002+ "Message recipient disconnected from message bus without"
1003+ " replying"
1004+ ),
1005+ )
1006+ mocker .patch ("time.time" , side_effect = [1 , 2 , 50 ])
1007+ assert (
1008+ _get_error_or_running_from_systemd_with_retry (
1009+ UXAppStatus .ERROR , wait = False
1010+ )
1011+ is None
1012+ )
1013+ assert 1 == m_subp .call_count
1014+ assert (
1015+ "Failed to get status from systemd. "
1016+ "Cloud-init status may be inaccurate."
1017+ ) in capsys .readouterr ().err
0 commit comments