1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/webvirtcloud/middleware.py
catborise 802e90c79f fix: connection errors on VM actions name the host only to administrators
When a VM action failed because its host could not be reached (the
connection could not be opened, or it dropped during the call), the
error page showed libvirt's connection error to any user: it names the
host and how it is reached, e.g. "ssh: connect to host 10.x port 22".
The lists and the VM page already keep that to administrators.

- Opening a connection now fails with ConnectionFailed (a subclass of
  OperationError). A dropped connection is recognized by its error
  domain and code (RPC/remote/SSH layer with a transport code, measured:
  a keepalive timeout is VIR_FROM_RPC/VIR_ERR_INTERNAL_ERROR); an
  operation the daemon does not support (VIR_ERR_NO_SUPPORT) is not
  treated as one.
- ExceptionMiddleware (pages and API) and the views that report the
  error themselves (snapshot, revert, disk deletion, clone) show it to
  superusers and view_instances users, and to everyone else "The host
  of this virtual machine cannot be reached right now." Errors of the
  operation itself ("domain is not running") are shown as before.

Checked on the test setup: powering on a VM of an unreachable compute
shows the owner the generic text and the administrator the error. The
tests fail on the previous code.
2026-10-06 10:08:48 +03:00

67 lines
2.8 KiB
Python

from django.contrib import messages
from django.http import JsonResponse
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
import libvirt
from libvirt import libvirtError
from vrtManager.util import ConnectionFailed
API_PREFIX = "/api/"
# errors of the connection to the host, not of the operation: both the
# domain and the code must match (a daemon that lacks a procedure answers
# VIR_FROM_RPC / VIR_ERR_NO_SUPPORT, which the user should see)
TRANSPORT_ERROR_DOMAINS = {libvirt.VIR_FROM_RPC, libvirt.VIR_FROM_REMOTE, libvirt.VIR_FROM_SSH, libvirt.VIR_FROM_LIBSSH}
TRANSPORT_ERROR_CODES = {
libvirt.VIR_ERR_INTERNAL_ERROR, # "connection closed due to keepalive timeout"
libvirt.VIR_ERR_SYSTEM_ERROR, # "Cannot recv data: ..."
libvirt.VIR_ERR_RPC,
libvirt.VIR_ERR_NO_CONNECT,
libvirt.VIR_ERR_AUTH_FAILED,
libvirt.VIR_ERR_AUTH_UNAVAILABLE,
libvirt.VIR_ERR_AUTH_CANCELLED,
libvirt.VIR_ERR_SSH,
libvirt.VIR_ERR_LIBSSH,
}
UNREACHABLE = _("The host of this virtual machine cannot be reached right now.")
def error_text(request, exception):
"""
The libvirt error to show. A connection error names the host and how it
is reached, which only administrators (superusers and the global
view_instances permission, who see the hosts) get; other users learn
that the host cannot be reached.
"""
connection_error = isinstance(exception, ConnectionFailed) or (
isinstance(exception, libvirtError)
and exception.get_error_domain() in TRANSPORT_ERROR_DOMAINS
and exception.get_error_code() in TRANSPORT_ERROR_CODES
)
user = getattr(request, "user", None)
if connection_error and not (user and (user.is_superuser or user.has_perm("instances.view_instances"))):
return str(UNREACHABLE)
return str(exception)
class ExceptionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
# API clients get JSON with a failure status, not the HTML error page.
if request.path_info.startswith(API_PREFIX):
if isinstance(exception, libvirtError):
return JsonResponse({"detail": error_text(request, exception)}, status=400)
if isinstance(exception, TimeoutError):
return JsonResponse({"detail": "Operation is busy. Please retry."}, status=409)
return None
if isinstance(exception, libvirtError):
messages.error(
request,
_("libvirt Error - %(exception)s") % {"exception": error_text(request, exception)},
)
return render(request, "500.html", {"libvirt_error": True}, status=500)
# TODO: check connecting to host via VPN