diff --git a/cloudinit/util.py b/cloudinit/util.py index 10a7ca71234..2864e18b35d 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -381,6 +381,12 @@ def find_modules(root_dir) -> dict: return entries +def write_to_console(conpath, text): + with open(conpath, "w") as wfh: + wfh.write(text) + wfh.flush() + + def multi_log( text, console=True, @@ -393,14 +399,25 @@ def multi_log( sys.stderr.write(text) if console: conpath = "/dev/console" + writing_to_console_worked = False if os.path.exists(conpath): - with open(conpath, "w") as wfh: - wfh.write(text) - wfh.flush() - elif fallback_to_stdout: - # A container may lack /dev/console (arguably a container bug). If - # it does not exist, then write output to stdout. this will result - # in duplicate stderr and stdout messages if stderr was True. + try: + write_to_console(conpath, text) + writing_to_console_worked = True + except OSError: + console_error = "Failed to write to /dev/console" + sys.stdout.write(f"{console_error}\n") + if log: + log.log(logging.WARNING, console_error) + + if fallback_to_stdout and not writing_to_console_worked: + # A container may lack /dev/console (arguably a container bug). + # Additionally, /dev/console may not be writable to on a VM (again + # likely a VM bug or virtualization bug). + # + # If either of these is the case, then write output to stdout. + # This will result in duplicate stderr and stdout messages if + # stderr was True. # # even though upstart or systemd might have set up output to go to # /dev/console, the user may have configured elsewhere via @@ -2061,7 +2078,7 @@ def write_file( omode="wb", preserve_mode=False, *, - ensure_dir_exists=True + ensure_dir_exists=True, ): """ Writes a file with the given content and sets the file mode as specified. diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index 3f3079b04e7..a8731c01f58 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -1982,6 +1982,33 @@ def test_logs_dont_go_to_stdout_if_fallback_to_stdout_is_false(self): util.multi_log("something", fallback_to_stdout=False) self.assertEqual("", self.stdout.getvalue()) + @mock.patch( + "cloudinit.util.write_to_console", + mock.Mock(side_effect=OSError("Failed to write to console")), + ) + def test_logs_go_to_stdout_if_writing_to_console_fails_and_fallback_true( + self, + ): + self._createConsole(self.root) + util.multi_log("something", fallback_to_stdout=True) + self.assertEqual( + "Failed to write to /dev/console\nsomething", + self.stdout.getvalue(), + ) + + @mock.patch( + "cloudinit.util.write_to_console", + mock.Mock(side_effect=OSError("Failed to write to console")), + ) + def test_logs_go_nowhere_if_writing_to_console_fails_and_fallback_false( + self, + ): + self._createConsole(self.root) + util.multi_log("something", fallback_to_stdout=False) + self.assertEqual( + "Failed to write to /dev/console\n", self.stdout.getvalue() + ) + def test_logs_go_to_log_if_given(self): log = mock.MagicMock() logged_string = "something very important" diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers index 5f05dba907f..3410815608f 100644 --- a/tools/.github-cla-signers +++ b/tools/.github-cla-signers @@ -6,6 +6,7 @@ Aman306 andgein andrewbogott andrewlukoshko +andrew-lee-metaswitch antonyc aswinrajamannar beantaxi