diff --git a/instances/tests/test_root_pass.py b/instances/tests/test_root_pass.py index 05b01197..0c889faf 100644 --- a/instances/tests/test_root_pass.py +++ b/instances/tests/test_root_pass.py @@ -169,6 +169,24 @@ class GuestAgentAccessTestCase(TestCase): self.assertEqual(self.post("rootpasswd", {"passwd": "S3cret-pass"}), ["guest agent command failed: user root not found"]) + def test_a_command_that_timed_out_may_still_be_made(self): + # sent to the agent: it runs when the agent answers again; a failed + # synchronization sends nothing + from libvirt import VIR_ERR_AGENT_UNRESPONSIVE + + for code, text, note in ( + (112, "guest agent command timed out", True), # VIR_ERR_AGENT_COMMAND_TIMEOUT + (VIR_ERR_AGENT_UNRESPONSIVE, "Guest agent is not responding", False), + ): + with self.subTest(code=code): + err = libvirtError(text) + err.get_error_code = lambda code=code: code + self.proxy.set_user_password.side_effect = err + self.proxy.add_authorized_key.side_effect = err + expected = text + (" The change may still be made when the agent answers again." if note else "") + self.assertEqual(self.post("rootpasswd", {"passwd": "S3cret-pass"})[-1], expected) + self.assertEqual(self.post("add_public_key", {"sshkeyid": self.key.id})[-1], expected) + def test_a_user_who_does_not_see_hosts_gets_no_connection_details(self): from accounts.models import UserInstance from django.contrib.auth import get_user_model diff --git a/instances/views.py b/instances/views.py index 89cec857..9cd5e7af 100755 --- a/instances/views.py +++ b/instances/views.py @@ -869,6 +869,20 @@ def gstfsd_request(hostname, data): AGENT_OR_SHUT_OFF = _("Shut the VM down, or start its QEMU guest agent, and try again") +# libvirt's value; bindings built against older libvirt (Ubuntu 24.04's) lack +# the name, and the code comes from the host's libvirtd +VIR_ERR_AGENT_COMMAND_TIMEOUT = 112 + + +def access_error(request, err): + """The error of a root password or SSH key change. A command that timed + out was sent to the agent, which still runs it when it answers again.""" + text = error_text(request, err) + if isinstance(err, libvirtError) and err.get_error_code() == VIR_ERR_AGENT_COMMAND_TIMEOUT: + text += " " + _("The change may still be made when the agent answers again.") + return text + + @require_POST @serialize_instance_mutation def set_root_pass(request, pk): @@ -890,7 +904,7 @@ def set_root_pass(request, pk): else: instance.proxy.set_user_password("root", passwd_hash) except (util.OperationError, libvirtError) as err: - messages.error(request, error_text(request, err)) + messages.error(request, access_error(request, err)) else: messages.success(request, _("Reset root password")) else: @@ -928,7 +942,7 @@ def add_public_key(request, pk): else: instance.proxy.add_authorized_key("root", publickey.keypublic) except (util.OperationError, libvirtError) as err: - messages.error(request, error_text(request, err)) + messages.error(request, access_error(request, err)) else: messages.success(request, _("Installed new SSH public key %(keyname)s") % { "keyname": publickey.keyname