mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
The console page of a VM whose host does not answer said "Fail to get console. Please check the console configuration of your VM." and logged nothing. It now shows the connection error (to users who do not see the hosts, that the host cannot be reached) and logs it with the VM and host. A host's Storage, Networks, Interfaces, Filters and Secrets pages showed the connection error and also "This host has no ...". The list is None when it was not read, and the page shows only the error.
305 lines
14 KiB
Python
305 lines
14 KiB
Python
import ipaddress
|
|
|
|
from admin.decorators import superuser_only
|
|
from computes.models import Compute
|
|
from instances.models import Instance
|
|
from django.contrib import messages
|
|
from django.http import HttpResponseRedirect
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.urls import reverse
|
|
from django.utils.translation import gettext_lazy as _
|
|
from libvirt import libvirtError
|
|
from logs.audit import audit, failed
|
|
from vrtManager.network import network_size, wvmNetwork, wvmNetworks
|
|
from vrtManager.util import OperationError
|
|
|
|
from networks.forms import AddNetPool
|
|
|
|
|
|
# a network's mode (vrtManager.network.network_mode): its name, and what it does
|
|
MODES = {
|
|
"isolated": (_("Isolated"), _("VMs reach each other and the host only.")),
|
|
"nat": (_("NAT"), _("VMs reach out through the host's address; IPv6, when set, is routed.")),
|
|
"route": (_("Routed"), _("The host routes the subnet; the upstream router needs a route back to it.")),
|
|
"open": (_("Open"), _("The host routes the subnet and adds no firewall rules for it.")),
|
|
"bridge": (_("Bridge"), _("VMs join a bridge of the host and take their addresses from its network.")),
|
|
"macvtap": (_("Macvtap"), _("VMs share an interface of the host; the host cannot reach them through it.")),
|
|
"hostdev": (_("SR-IOV"), _("VMs get a virtual function of the host's network card.")),
|
|
}
|
|
# the modes libvirt gives no addresses of its own: the outside network does
|
|
EXTERNAL = ("bridge", "macvtap", "hostdev")
|
|
|
|
|
|
@superuser_only
|
|
def networks(request, compute_id):
|
|
"""
|
|
:param request:
|
|
:param compute_id:
|
|
:return:
|
|
"""
|
|
|
|
compute = get_object_or_404(Compute, pk=compute_id)
|
|
form = AddNetPool()
|
|
# the new network dialog's mode: the one sent back after a refusal
|
|
net_mode = request.POST.get("forward", "nat")
|
|
networks = None # not read: the page shows no list
|
|
|
|
try:
|
|
conn = wvmNetworks(
|
|
compute.hostname,
|
|
compute.login,
|
|
compute.password,
|
|
compute.type,
|
|
)
|
|
networks = conn.get_networks_info()
|
|
for net in networks:
|
|
net["mode_label"] = MODES.get(net["mode"], (net["mode"], ""))[0]
|
|
net["external"] = net["mode"] in EXTERNAL
|
|
# suggestions for a bridge or macvtap network: not the bridges libvirt made for its own networks
|
|
taken = {net["device"] for net in networks if not net["external"]}
|
|
host_ifaces = [iface for iface in conn.get_host_interfaces() if iface["name"] not in taken]
|
|
|
|
if request.method == "POST" and "create" in request.POST:
|
|
form = AddNetPool(request.POST, networks=networks)
|
|
if form.is_valid():
|
|
data = form.cleaned_data
|
|
gateway4 = netmask4 = dhcp4 = gateway6 = prefix6 = dhcp6 = ""
|
|
if data["subnet"]:
|
|
gateway4, netmask4, dhcp4 = network_size(data["subnet"], data["dhcp4"])
|
|
if data["subnet6"]:
|
|
gateway6, prefix6, dhcp6 = network_size(data["subnet6"], data["dhcp6"])
|
|
try:
|
|
with audit(request, compute, f"Network {data['name']}", "add"):
|
|
conn.create_network(
|
|
data["name"], data["forward"],
|
|
bool(data["subnet"]), gateway4, netmask4, dhcp4,
|
|
bool(data["subnet6"]), gateway6, prefix6, dhcp6,
|
|
data["bridge_name"], data["openvswitch"],
|
|
)
|
|
except libvirtError as err: # shown in the dialog, which opens again
|
|
form.add_error(None, str(err))
|
|
else:
|
|
return HttpResponseRedirect(reverse("network", args=[compute_id, data["name"]]))
|
|
elif form.has_error("name", "taken"):
|
|
failed(request, compute, f"Network {form.data['name'].strip()}", "add", "name already in use")
|
|
conn.close()
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
|
|
return render(request, "networks.html", locals())
|
|
|
|
|
|
@superuser_only
|
|
def network(request, compute_id, pool):
|
|
"""
|
|
:param request:
|
|
:param compute_id:
|
|
:param pool:
|
|
:return:
|
|
"""
|
|
|
|
compute = get_object_or_404(Compute, pk=compute_id)
|
|
|
|
try:
|
|
conn = wvmNetwork(
|
|
compute.hostname,
|
|
compute.login,
|
|
compute.password,
|
|
compute.type,
|
|
pool,
|
|
)
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
return HttpResponseRedirect(reverse("networks", args=[compute_id]))
|
|
|
|
if request.method == "POST":
|
|
subject = f"Network {pool}"
|
|
here = reverse("network", args=[compute_id, pool])
|
|
if "start" in request.POST:
|
|
try:
|
|
with audit(request, compute, subject, "start"):
|
|
conn.start()
|
|
return HttpResponseRedirect(here)
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
if "stop" in request.POST:
|
|
try:
|
|
with audit(request, compute, subject, "stop"):
|
|
conn.stop()
|
|
return HttpResponseRedirect(here)
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
if "delete" in request.POST:
|
|
try:
|
|
with audit(request, compute, subject, "delete"):
|
|
conn.delete()
|
|
return HttpResponseRedirect(reverse("networks", args=[compute_id]))
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
if "set_autostart" in request.POST:
|
|
try:
|
|
with audit(request, compute, subject, "autostart on"):
|
|
conn.set_autostart(1)
|
|
return HttpResponseRedirect(here)
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
if "unset_autostart" in request.POST:
|
|
try:
|
|
with audit(request, compute, subject, "autostart off"):
|
|
conn.set_autostart(0)
|
|
return HttpResponseRedirect(here)
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
# an address of the network by its index (ip_networks), an entry by the address it had
|
|
try:
|
|
ip_index = int(request.POST.get("ip_index", ""))
|
|
except ValueError:
|
|
ip_index = -1
|
|
if "modify_fixed_address" in request.POST:
|
|
address = request.POST.get("address", "")
|
|
mac_duid = request.POST.get("mac") or request.POST.get("id", "")
|
|
try:
|
|
with audit(request, compute, subject, f"set fixed address {address}") as entry:
|
|
changed = conn.modify_fixed_address(ip_index, request.POST.get("original", ""),
|
|
request.POST.get("name", ""), address, mac_duid)
|
|
entry.skip = changed is False # nothing was done
|
|
if changed is False:
|
|
messages.info(request, _("The fixed address is unchanged"))
|
|
else:
|
|
messages.success(request, _("The fixed address %(address)s is set") % {"address": address})
|
|
except (libvirtError, OperationError) as err:
|
|
messages.error(request, err)
|
|
return HttpResponseRedirect(here)
|
|
if "delete_fixed_address" in request.POST:
|
|
address = request.POST.get("original", "")
|
|
try:
|
|
with audit(request, compute, subject, f"delete fixed address {address}"):
|
|
conn.delete_fixed_address(ip_index, address)
|
|
messages.success(request, _("The fixed address %(address)s is deleted") % {"address": address})
|
|
except (libvirtError, OperationError) as err:
|
|
messages.error(request, err)
|
|
return HttpResponseRedirect(here)
|
|
if "modify_dhcp_range" in request.POST:
|
|
try:
|
|
with audit(request, compute, subject, "set DHCP range"):
|
|
conn.modify_dhcp_range(ip_index, request.POST.get("range_start", ""), request.POST.get("range_end", ""))
|
|
messages.success(request, _("The DHCP range is changed"))
|
|
except (libvirtError, OperationError) as err:
|
|
messages.error(request, err)
|
|
return HttpResponseRedirect(here)
|
|
if "edit_network" in request.POST:
|
|
edit_xml = request.POST.get("edit_xml", "")
|
|
if edit_xml:
|
|
try:
|
|
with audit(request, compute, subject, "change XML"):
|
|
conn.edit_network(edit_xml)
|
|
except (libvirtError, OperationError) as err:
|
|
messages.error(request, err)
|
|
return HttpResponseRedirect(here)
|
|
if conn.is_active():
|
|
messages.success(
|
|
request,
|
|
_(
|
|
"Network XML is changed. "
|
|
"Stop and start network to activate new config."
|
|
),
|
|
)
|
|
else:
|
|
messages.success(request, _("Network XML is changed."))
|
|
return HttpResponseRedirect(here)
|
|
if "set_qos" in request.POST:
|
|
qos_dir = request.POST.get("qos_direction", "")
|
|
average = request.POST.get("qos_average") or 0
|
|
peak = request.POST.get("qos_peak") or 0
|
|
burst = request.POST.get("qos_burst") or 0
|
|
|
|
try:
|
|
with audit(request, compute, subject, f"set QoS {qos_dir}"):
|
|
conn.set_qos(qos_dir, average, peak, burst)
|
|
if conn.is_active():
|
|
messages.success(
|
|
request,
|
|
_(
|
|
"%(qos_dir)s QoS is updated. Network XML is changed. Stop and start network to activate new config"
|
|
)
|
|
% {"qos_dir": _(qos_dir.capitalize())},
|
|
)
|
|
else:
|
|
messages.success(
|
|
request,
|
|
_("%(qos_dir)s QoS is set") % {"qos_dir": _(qos_dir.capitalize())},
|
|
)
|
|
except (libvirtError, ValueError) as err:
|
|
messages.error(request, err)
|
|
return HttpResponseRedirect(here)
|
|
if "unset_qos" in request.POST:
|
|
qos_dir = request.POST.get("qos_direction", "")
|
|
try:
|
|
with audit(request, compute, subject, f"unset QoS {qos_dir}"):
|
|
conn.unset_qos(qos_dir)
|
|
except (libvirtError, ValueError) as err:
|
|
messages.error(request, err)
|
|
return HttpResponseRedirect(here)
|
|
|
|
if conn.is_active():
|
|
messages.success(
|
|
request,
|
|
_(
|
|
"%(qos_dir)s QoS is deleted. Network XML is changed. "
|
|
"Stop and start network to activate new config"
|
|
)
|
|
% {"qos_dir": _(qos_dir.capitalize())},
|
|
)
|
|
else:
|
|
messages.success(
|
|
request,
|
|
_("%(qos_dir)s QoS is deleted") % {"qos_dir": _(qos_dir.capitalize())},
|
|
)
|
|
return HttpResponseRedirect(here)
|
|
# read only for the page: every action above redirects
|
|
try:
|
|
state = conn.is_active()
|
|
device = conn.get_bridge_device()
|
|
autostart = conn.get_autostart()
|
|
net_mac = conn.get_network_mac()
|
|
qos = conn.get_qos()
|
|
ip_networks = conn.get_ip_networks()
|
|
mode = conn.get_mode()
|
|
mode_label, mode_help = MODES.get(mode, (mode, ""))
|
|
external = mode in EXTERNAL
|
|
# libvirt limits the bandwidth of routed, isolated and host bridge networks, not of macvtap or SR-IOV ones
|
|
qos_supported = mode in ("isolated", "nat", "route", "open", "bridge")
|
|
families = {ip["family"] for ip in ip_networks}
|
|
# every fixed address, with the address of the network it belongs to
|
|
fixed_addresses = [{**host, "ip_index": ip["index"], "subnet": ip["subnet"]}
|
|
for ip in ip_networks for host in ip["hosts"]]
|
|
dhcp_networks = [ip for ip in ip_networks if ip["dhcp"]]
|
|
vms = conn.get_vms()
|
|
vm_ids = dict(Instance.objects.filter(compute=compute, name__in=[vm["name"] for vm in vms]).values_list("name", "id"))
|
|
for vm in vms:
|
|
vm["id"] = vm_ids.get(vm["name"])
|
|
xml = conn.saved_xml
|
|
except libvirtError as lib_err:
|
|
messages.error(request, lib_err)
|
|
return HttpResponseRedirect(reverse("networks", args=[compute_id]))
|
|
|
|
# leases that cannot be read leave the rest of the page
|
|
leases, leases_error = [], None
|
|
if dhcp_networks:
|
|
try:
|
|
leases = conn.get_leases()
|
|
except libvirtError as lib_err:
|
|
leases_error = lib_err
|
|
vm_by_mac = {mac: vm for vm in vms for mac in vm["macs"]}
|
|
fixed_ips = {ipaddress.ip_address(host["ip"]) for host in fixed_addresses if host["ip"]}
|
|
for lease in leases:
|
|
lease["vm"] = vm_by_mac.get(lease["mac"])
|
|
lease["name"] = lease["hostname"] or (lease["vm"]["name"] if lease["vm"] else "") # for a fixed address
|
|
lease["fixed"] = lease["ip"] in fixed_ips
|
|
# what a fixed address for it is known by: its MAC, or an IPv6 client's DUID
|
|
lease["key"] = lease["mac"] if lease["ip"].version == 4 else lease["client_id"]
|
|
lease["ip_index"] = next((ip["index"] for ip in dhcp_networks
|
|
if ip["subnet"] and lease["ip"] in ip["subnet"]), None)
|
|
conn.close()
|
|
|
|
return render(request, "network.html", locals())
|