From 04b62bf25736ce2090c654de194718b48223ab7f Mon Sep 17 00:00:00 2001 From: James Falcon Date: Wed, 21 Feb 2024 20:56:03 -0600 Subject: [PATCH] comments --- cloudinit/cmd/status.py | 75 +++++++++++++++------------- doc/rtd/howto/status.rst | 55 ++++++++++++++++---- tests/unittests/cmd/test_cloud_id.py | 4 +- tests/unittests/cmd/test_status.py | 16 +++--- 4 files changed, 96 insertions(+), 54 deletions(-) diff --git a/cloudinit/cmd/status.py b/cloudinit/cmd/status.py index 8da9b0dedc4..33f697566c7 100644 --- a/cloudinit/cmd/status.py +++ b/cloudinit/cmd/status.py @@ -96,19 +96,10 @@ def query_systemctl( while True: try: return subp.subp(["systemctl", *systemctl_args]).stdout.strip() - except subp.ProcessExecutionError as e: - last_exception = e - if wait: - sleep(0.25) - else: - break - print( - "Failed to get status from systemd. " - "Cloud-init status may be inaccurate. ", - f"Error from systemctl: {last_exception.stderr}", - file=sys.stderr, - ) - return "" + except subp.ProcessExecutionError: + if not wait: + raise + sleep(0.25) def get_parser(parser=None): @@ -161,13 +152,13 @@ def translate_status( """ # If we're done and have errors, we're in an error state if condition == ConditionStatus.ERROR: - return ("error", f"{condition.value} - {running.value}") + return "error", f"{condition.value} - {running.value}" # Handle the "degraded done" and "degraded running" states elif condition == ConditionStatus.DEGRADED and running in [ RunningStatus.DONE, RunningStatus.RUNNING, ]: - return (running.value, f"{condition.value} {running.value}") + return running.value, f"{condition.value} {running.value}" return running.value, running.value @@ -264,6 +255,15 @@ def handle_status_args(name, args) -> int: return 0 +def _disabled_via_environment(wait) -> bool: + """Return whether cloud-init is disabled via environment variable.""" + try: + env = query_systemctl(["show-environment"], wait=wait) + except subp.ProcessExecutionError: + env = "" + return "cloud-init=disabled" in env + + def get_bootstatus(disable_file, paths, wait) -> Tuple[EnabledStatus, str]: """Report whether cloud-init current boot status @@ -287,9 +287,7 @@ def get_bootstatus(disable_file, paths, wait) -> Tuple[EnabledStatus, str]: bootstatus_code = EnabledStatus.DISABLED_BY_KERNEL_CMDLINE reason = "Cloud-init disabled by kernel parameter cloud-init=disabled" elif "cloud-init=disabled" in os.environ.get("KERNEL_CMDLINE", "") or ( - uses_systemd() - and "cloud-init=disabled" - in query_systemctl(["show-environment"], wait=wait) + uses_systemd() and _disabled_via_environment(wait=wait) ): bootstatus_code = EnabledStatus.DISABLED_BY_ENV_VARIABLE reason = ( @@ -323,16 +321,23 @@ def systemd_failed(wait: bool) -> bool: "cloud-init.service", "cloud-init-local.service", ]: - stdout = query_systemctl( - [ - "show", - "--property=ActiveState,UnitFileState,SubState,MainPID", - service, - ], - wait=wait, - ) - if not stdout: + try: + stdout = query_systemctl( + [ + "show", + "--property=ActiveState,UnitFileState,SubState,MainPID", + service, + ], + wait=wait, + ) + except subp.ProcessExecutionError as e: # Systemd isn't ready, assume the same state + print( + "Failed to get status from systemd. " + "Cloud-init status may be inaccurate. " + f"Error from systemctl: {e.stderr}", + file=sys.stderr, + ) return False states = dict( [[x.strip() for x in r.split("=")] for r in stdout.splitlines()] @@ -375,16 +380,14 @@ def get_running_status( status_file, result_file, boot_status_code, latest_event ) -> RunningStatus: """Return the running status of cloud-init.""" - if is_running(status_file, result_file): - return RunningStatus.RUNNING - elif boot_status_code in DISABLED_BOOT_CODES: + if boot_status_code in DISABLED_BOOT_CODES: return RunningStatus.DISABLED + elif is_running(status_file, result_file): + return RunningStatus.RUNNING + elif latest_event > 0: + return RunningStatus.DONE else: - return ( - RunningStatus.DONE - if latest_event > 0 - else RunningStatus.NOT_STARTED - ) + return RunningStatus.NOT_STARTED def get_datasource(status_v1) -> str: @@ -501,7 +504,7 @@ def get_status_details( condition_status = ConditionStatus.ERROR description = "Failed due to systemd unit failure" errors.append( - "Failed due to sysetmd unit failure. Ensure all cloud-init " + "Failed due to systemd unit failure. Ensure all cloud-init " "services are enabled, and check 'systemctl' or 'journalctl' " "for more information." ) diff --git a/doc/rtd/howto/status.rst b/doc/rtd/howto/status.rst index 51ca02c3e12..d6cdbe09212 100644 --- a/doc/rtd/howto/status.rst +++ b/doc/rtd/howto/status.rst @@ -36,24 +36,59 @@ subcommand under the ``extended_status`` key. $ cloud-init status --format json { - "boot_status_code": "enabled-by-generator", - "datasource": "", - "detail": "Cloud-init enabled by systemd cloud-init-generator", - "errors": [], - "extended_status": "degraded done", - "last_update": "", - "recoverable_errors": {}, - "status": "done" + "boot_status_code": "enabled-by-generator", + "datasource": "lxd", + "detail": "DataSourceLXD", + "errors": [], + "extended_status": "degraded done", + "init": { + "errors": [], + "finished": 1708550839.1837437, + "recoverable_errors": {}, + "start": 1708550838.6881146 + }, + "init-local": { + "errors": [], + "finished": 1708550838.0196638, + "recoverable_errors": {}, + "start": 1708550837.7719762 + }, + "last_update": "Wed, 21 Feb 2024 21:27:24 +0000", + "modules-config": { + "errors": [], + "finished": 1708550843.8297973, + "recoverable_errors": { + "WARNING": [ + "Removing /etc/apt/sources.list to favor deb822 source format" + ] + }, + "start": 1708550843.7163966 + }, + "modules-final": { + "errors": [], + "finished": 1708550844.0884337, + "recoverable_errors": {}, + "start": 1708550844.029698 + }, + "recoverable_errors": { + "WARNING": [ + "Removing /etc/apt/sources.list to favor deb822 source format" + ] + }, + "stage": null, + "status": "done" } + See the list of all possible reported statuses: .. code-block:: shell-session - "not running" + "not started" "running" "done" - "error" + "error - done" + "error - running" "degraded done" "degraded running" "disabled" diff --git a/tests/unittests/cmd/test_cloud_id.py b/tests/unittests/cmd/test_cloud_id.py index e01e21cf4fc..d01c6efa283 100644 --- a/tests/unittests/cmd/test_cloud_id.py +++ b/tests/unittests/cmd/test_cloud_id.py @@ -33,7 +33,7 @@ "", {}, ) -STATUS_DETAILS_NOT_RUN = status.StatusDetails( +STATUS_DETAILS_NOT_STARTED = status.StatusDetails( status.RunningStatus.NOT_STARTED, status.ConditionStatus.PEACHY, status.EnabledStatus.UNKNOWN, @@ -226,7 +226,7 @@ def test_cloud_id_lookup_json_instance_data_adds_cloud_id_to_json( "details, exit_code", ( (STATUS_DETAILS_DISABLED, 2), - (STATUS_DETAILS_NOT_RUN, 3), + (STATUS_DETAILS_NOT_STARTED, 3), (STATUS_DETAILS_RUNNING, 0), (STATUS_DETAILS_RUNNING_DS_NONE, 0), ), diff --git a/tests/unittests/cmd/test_status.py b/tests/unittests/cmd/test_status.py index 09cc27870d2..e8afd43d866 100644 --- a/tests/unittests/cmd/test_status.py +++ b/tests/unittests/cmd/test_status.py @@ -145,7 +145,7 @@ def test_get_status_systemd_failure( assert details.condition_status == status.ConditionStatus.ERROR assert details.description == "Failed due to systemd unit failure" assert details.errors == [ - "Failed due to sysetmd unit failure. Ensure all cloud-init " + "Failed due to systemd unit failure. Ensure all cloud-init " "services are enabled, and check 'systemctl' or 'journalctl' " "for more information." ] @@ -1003,8 +1003,10 @@ def test_retry_no_wait(self, mocker, capsys): m_subp = mocker.patch( f"{M_PATH}subp.subp", side_effect=subp.ProcessExecutionError( - "Message recipient disconnected from message bus without" - " replying" + stderr=( + "Message recipient disconnected from message bus without " + "replying" + ), ), ) mocker.patch("time.time", side_effect=[1, 2, 50]) @@ -1012,7 +1014,9 @@ def test_retry_no_wait(self, mocker, capsys): assert 1 == m_subp.call_count assert ( "Failed to get status from systemd. " - "Cloud-init status may be inaccurate." + "Cloud-init status may be inaccurate. " + "Error from systemctl: Message recipient disconnected from " + "message bus without replying" ) in capsys.readouterr().err @@ -1032,9 +1036,9 @@ def test_query_systemctl_with_exception(self, mocker, capsys): "Message recipient disconnected", stderr="oh noes!" ), ) - assert status.query_systemctl(["some", "args"], wait=False) == "" + with pytest.raises(subp.ProcessExecutionError): + status.query_systemctl(["some", "args"], wait=False) m_subp.assert_called_once_with(["systemctl", "some", "args"]) - assert "Error from systemctl: oh noes!" in capsys.readouterr().err def test_query_systemctl_wait_with_exception(self, mocker): m_sleep = mocker.patch(f"{M_PATH}sleep")