From adfdad013ca88d91952bd6d4056348580913a53f Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 20 Dec 2019 16:43:14 +0300 Subject: [PATCH 1/7] change cd-rom bus for q35 type machines; it is sata not ide --- instances/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instances/views.py b/instances/views.py index 6daa554..39f88fa 100644 --- a/instances/views.py +++ b/instances/views.py @@ -616,7 +616,7 @@ def instance(request, compute_id, vname): return HttpResponseRedirect(request.get_full_path() + '#disks') if 'add_cdrom' in request.POST and allow_admin_or_not_template: - bus = request.POST.get('bus', 'ide') + bus = request.POST.get('bus', 'ide' if machine == 'pc' else 'sata') target = get_new_disk_dev(media, disks, bus) conn.attach_disk("", target, device='cdrom', cache='none', targetbus=bus) msg = _('Add CD-ROM: ' + target) From 592112590c1ab3fa6dc3102b4e81246a4524efb8 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 23 Dec 2019 15:48:11 +0300 Subject: [PATCH 2/7] add ability to show interface's ip address with qemu agent. Add checking agent if installed or not --- instances/templates/instance.html | 10 +-- instances/views.py | 3 +- interfaces/views.py | 2 +- vrtManager/connection.py | 2 +- vrtManager/instance.py | 115 +++++++++++++++++++++++++----- vrtManager/network.py | 13 ++++ 6 files changed, 122 insertions(+), 23 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 2cbd079..b83809b 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -876,7 +876,8 @@ {% trans 'Name' %} {% trans 'MAC' %} - {% trans 'NIC' %} + {% trans 'IP Address' %} + {% trans 'Source' %} {% trans 'Filter' %} {% trans 'Qos' %} {% trans 'Actions' %} @@ -886,9 +887,10 @@ {% for network in networks %} - - - + + + +
{% csrf_token %} diff --git a/instances/views.py b/instances/views.py index 39f88fa..ac24121 100644 --- a/instances/views.py +++ b/instances/views.py @@ -266,6 +266,7 @@ def instance(request, compute_id, vname): compute.password, compute.type, vname) + status = conn.get_status() autostart = conn.get_autostart() bootmenu = conn.get_bootmenu() @@ -282,7 +283,7 @@ def instance(request, compute_id, vname): cur_memory = conn.get_cur_memory() title = conn.get_title() description = conn.get_description() - networks = conn.get_net_device() + networks = conn.get_net_devices() qos = conn.get_all_qos() disks = conn.get_disk_devices() media = conn.get_media_devices() diff --git a/interfaces/views.py b/interfaces/views.py index a747120..6ac9dd6 100644 --- a/interfaces/views.py +++ b/interfaces/views.py @@ -30,7 +30,7 @@ def interfaces(request, compute_id): compute.type) ifaces = conn.get_ifaces() try: - netdevs = conn.get_net_device() + netdevs = conn.get_net_devices() except: netdevs = ['eth0', 'eth1'] diff --git a/vrtManager/connection.py b/vrtManager/connection.py index 8e826f0..19b9684 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -686,7 +686,7 @@ class wvmConnect(object): instance.append(dom.name()) return instance - def get_net_device(self): + def get_net_devices(self): netdevice = [] def get_info(doc): diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 8a18559..3609bb2 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -11,6 +11,7 @@ try: VIR_MIGRATE_COMPRESSED, \ VIR_MIGRATE_AUTO_CONVERGE, \ VIR_MIGRATE_POSTCOPY + from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT except: from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE @@ -148,6 +149,7 @@ class wvmInstances(wvmConnect): class wvmInstance(wvmConnect): def __init__(self, host, login, passwd, conn, vname): wvmConnect.__init__(self, host, login, passwd, conn) + self._ip_cache = None self.instance = self.get_instance(vname) def start(self): @@ -276,17 +278,73 @@ class wvmInstance(wvmConnect): range_pcpus = xrange(1, int(pcpus + 1)) return range_pcpus - def get_net_device(self): - def get_mac_ipaddr(net, mac_host): - def fixed(doc): - for net in doc.xpath('/network/ip/dhcp/host'): - mac = net.xpath('@mac')[0] - host = net.xpath('@ip')[0] - if mac == mac_host: - return host - return None + def get_interface_addresses(self, iface_mac): + if self._ip_cache is None: + self.refresh_interface_addresses() - return util.get_xml_path(net.XMLDesc(0), func=fixed) + qemuga = self._ip_cache["qemuga"] + arp = self._ip_cache["arp"] + leases = [] + + def extract_dom(info): + ipv4 = None + ipv6 = None + for addrs in info.values(): + if addrs["hwaddr"] != iface_mac: + continue + if not addrs["addrs"]: + continue + for addr in addrs["addrs"]: + if addr["type"] == 0: + ipv4 = addr["addr"] + elif (addr["type"] == 1 and + not str(addr["addr"]).startswith("fe80")): + ipv6 = addr["addr"] + "/" + str(addr["prefix"]) + return ipv4, ipv6 + + def extract_lease(info): + ipv4 = None + ipv6 = None + if info["mac"] == iface_mac: + if info["type"] == 0: + ipv4 = info["ipaddr"] + elif info["type"] == 1: + ipv6 = info["ipaddr"] + return ipv4, ipv6 + + for ips in ([qemuga] + leases + [arp]): + if "expirytime" in ips: + ipv4, ipv6 = extract_lease(ips) + else: + ipv4, ipv6 = extract_dom(ips) + if ipv4 or ipv6: + return ipv4, ipv6 + return None, None + + def _get_interface_addresses(self, source): + #("Calling interfaceAddresses source=%s", source) + try: + return self.instance.interfaceAddresses(source) + except Exception as e: + #log.debug("interfaceAddresses failed: %s", str(e)) + pass + return {} + + def refresh_interface_addresses(self): + + self._ip_cache = {"qemuga": {}, "arp": {}} + + if not self.get_status() == 1: + return + + if self.agent_ready(): + self._ip_cache["qemuga"] = self._get_interface_addresses( + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT) + + arp_flag = 3 # libvirt."VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP" + self._ip_cache["arp"] = self._get_interface_addresses(arp_flag) + + def get_net_devices(self): def networks(ctx): result = [] @@ -310,14 +368,14 @@ class wvmInstance(wvmConnect): outbound = {'average': out_av, 'peak': out_peak, 'burst': out_burst} try: - net = self.get_network(nic_inst) - ip = get_mac_ipaddr(net, mac_inst) - except libvirtError: - ip = None + ipv4, ipv6 = self.get_interface_addresses(mac_inst) + except: + ipv4, ipv6 = None, None result.append({'mac': mac_inst, 'nic': nic_inst, 'target': target_inst, - 'ip': ip, + 'ipv4': ipv4, + 'ipv6': ipv6, 'filterref': filterref_inst, 'inbound': inbound, 'outbound': outbound, @@ -709,7 +767,7 @@ class wvmInstance(wvmConnect): tx_diff_usage = (tx_use_now - tx_use_ago) * 8 dev_usage.append({'dev': i, 'rx': rx_diff_usage, 'tx': tx_diff_usage}) else: - for i, dev in enumerate(self.get_net_device()): + for i, dev in enumerate(self.get_net_devices()): dev_usage.append({'dev': i, 'rx': 0, 'tx': 0}) return dev_usage @@ -1311,4 +1369,29 @@ class wvmInstance(wvmConnect): self.wvm.defineXML(etree.tostring(tree)) + def agent_ready(self): + """ + Return connected state of an agent. + """ + # we need to get a fresh agent channel object on each call so it + # reflects the current state + def _get_agent(doc): + """ + Return agent channel object if it is defined. + """ + for channel in doc.xpath('/domain/devices/channel'): + type = channel.get("type") + target = channel.find("target") + target_name = target.get("name") + if type == "unix" and target_name == "org.qemu.guest_agent.0": + return channel + return None + + dev = util.get_xml_path(self._XMLDesc(0), func=_get_agent) + state = dev.xpath("target/@state")[0] + if dev and state == "connected": + return True + return False + + diff --git a/vrtManager/network.py b/vrtManager/network.py index 87333da..560bd42 100644 --- a/vrtManager/network.py +++ b/vrtManager/network.py @@ -89,6 +89,7 @@ class wvmNetworks(wvmConnect): class wvmNetwork(wvmConnect): def __init__(self, host, login, passwd, conn, net): wvmConnect.__init__(self, host, login, passwd, conn) + self.leases = None self.net = self.get_network(net) self.parent_count = len(self.get_ip_networks()) @@ -347,3 +348,15 @@ class wvmNetwork(wvmConnect): def edit_network(self, new_xml): self.wvm.networkDefineXML(new_xml) + + def refresh_dhcp_leases(self): + try: + self.leases = self.net.DHCPLeases() + except Exception as e: + self.leases = [] + raise "Error getting %s DHCP leases: %s" % self, str(e) + + def get_dhcp_leases(self): + if self.leases is None: + self.refresh_dhcp_leases() + return self.leases From 01f2290dd9b3070855d5b94dbc2d81f8b61bdb39 Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 24 Dec 2019 09:23:49 +0300 Subject: [PATCH 3/7] host ifaces configuration error prevents management of instance. It is isolated. --- instances/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/instances/views.py b/instances/views.py index ac24121..0de54b0 100644 --- a/instances/views.py +++ b/instances/views.py @@ -343,10 +343,15 @@ def instance(request, compute_id, vname): bus_host = conn.get_disk_bus_types(arch, machine) videos_host = conn.get_video_models(arch, machine) networks_host = sorted(conn.get_networks()) - interfaces_host = sorted(conn.get_ifaces()) nwfilters_host = conn.get_nwfilters() storages_host = sorted(conn.get_storages(True)) + try: + interfaces_host = sorted(conn.get_ifaces()) + except Exception as e: + addlogmsg(request.user.username, instance.name, e) + error_messages.append(e) + if request.method == 'POST': if 'poweron' in request.POST: if instance.is_template: From ecf31b0b5b9a7d88f31d2b85739d52017d9c3ebb Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 24 Dec 2019 14:54:04 +0300 Subject: [PATCH 4/7] adds link state option for instance networks. Enable/disable link while instance running --- instances/templates/instance.html | 20 +++++++++++++++----- instances/views.py | 9 +++++++++ vrtManager/instance.py | 31 ++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index b83809b..7b45eef 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -878,6 +878,7 @@ {% trans 'MAC' %} {% trans 'IP Address' %} {% trans 'Source' %} + {% trans 'LinkState' %} {% trans 'Filter' %} {% trans 'Qos' %} {% trans 'Actions' %} @@ -886,11 +887,20 @@ {% for network in networks %} - - - - - + eth{{ forloop.counter0 }}({{ network.target|default:"no target" }}) + {{ network.mac }} + {{ network.ipv4|default:"unknown" }} + {{ network.nic }} + + {% csrf_token %} + + + + {% trans 'active' %} + + + {{ network.filterref|default:"None" }}
{% csrf_token %} diff --git a/instances/views.py b/instances/views.py index 0de54b0..495c790 100644 --- a/instances/views.py +++ b/instances/views.py @@ -873,6 +873,15 @@ def instance(request, compute_id, vname): addlogmsg(request.user.username, instance.name, msg) return HttpResponseRedirect(request.get_full_path() + '#network') + if 'set_link_state' in request.POST: + mac_address = request.POST.get('mac', '') + state = request.POST.get('set_link_state') + state = 'down' if state == 'up' else 'up' + conn.set_link_state(mac_address, state) + msg = _("Set Link State: {}".format(state)) + addlogmsg(request.user.username, instance.name, msg) + return HttpResponseRedirect(request.get_full_path() + '#network') + if 'set_qos' in request.POST: qos_dir = request.POST.get('qos_direction', '') average = request.POST.get('qos_average') or 0 diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 3609bb2..2605b93 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -331,7 +331,6 @@ class wvmInstance(wvmConnect): return {} def refresh_interface_addresses(self): - self._ip_cache = {"qemuga": {}, "arp": {}} if not self.get_status() == 1: @@ -353,6 +352,7 @@ class wvmInstance(wvmConnect): mac_inst = net.xpath('mac/@address')[0] nic_inst = net.xpath('source/@network|source/@bridge|source/@dev')[0] target_inst = '' if not net.xpath('target/@dev') else net.xpath('target/@dev')[0] + link_state = 'up' if not net.xpath('link') else net.xpath('link/@state')[0] filterref_inst = '' if not net.xpath('filterref/@filter') else net.xpath('filterref/@filter')[0] if net.xpath('bandwidth/inbound'): in_attr = net.xpath('bandwidth/inbound')[0] @@ -374,6 +374,7 @@ class wvmInstance(wvmConnect): result.append({'mac': mac_inst, 'nic': nic_inst, 'target': target_inst, + 'state': link_state, 'ipv4': ipv4, 'ipv6': ipv6, 'filterref': filterref_inst, @@ -1275,6 +1276,24 @@ class wvmInstance(wvmConnect): new_xml = ElementTree.tostring(tree) self._defineXML(new_xml) + def set_link_state(self, mac_address, state): + tree = etree.fromstring(self._XMLDesc(0)) + for interface in tree.findall('devices/interface'): + source = interface.find('mac') + if source.get('address') == mac_address: + link = interface.find('link') + if link is not None: + interface.remove(link) + link_el = etree.Element("link") + link_el.attrib["state"] = state + interface.append(link_el) + new_xml = etree.tostring(interface) + if self.get_status() == 1: + self.instance.updateDeviceFlags(new_xml, VIR_DOMAIN_AFFECT_LIVE) + self.instance.updateDeviceFlags(new_xml, VIR_DOMAIN_AFFECT_CONFIG) + if self.get_status() == 5: + self.instance.updateDeviceFlags(new_xml, VIR_DOMAIN_AFFECT_CONFIG) + def _set_options(self, tree, options): for o in ['title', 'description']: option = tree.find(o) @@ -1388,10 +1407,12 @@ class wvmInstance(wvmConnect): return None dev = util.get_xml_path(self._XMLDesc(0), func=_get_agent) - state = dev.xpath("target/@state")[0] - if dev and state == "connected": - return True - return False + if len(dev) > 0: + states = dev.xpath("target/@state") + state = states[0] if len(states) > 0 else '' + if state == "connected": + return True + return False From 9d58e56d165194deb9d005b0bc4b9df303fc2322 Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 24 Dec 2019 17:19:11 +0300 Subject: [PATCH 5/7] add/remove guest agent channel function added --- instances/templates/instance.html | 29 +++++++++++++++++-- instances/views.py | 13 +++++++++ vrtManager/instance.py | 46 ++++++++++++++++++++++++------- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 7b45eef..2ca73a5 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -1411,11 +1411,11 @@
-
+

{% trans "To set instance vCPUs hotpluggable" %}

{% csrf_token %}
- +
+ + + + + + +
+
+
+
+
+
{% endif %} diff --git a/instances/views.py b/instances/views.py index 495c790..78982d4 100644 --- a/instances/views.py +++ b/instances/views.py @@ -303,6 +303,8 @@ def instance(request, compute_id, vname): console_port = conn.get_console_port() console_keymap = conn.get_console_keymap() console_listen_address = conn.get_console_listen_addr() + guest_agent = False if conn.get_guest_agent() is None else True + guest_agent_ready = conn.is_agent_ready() video_model = conn.get_video_model() snapshots = sorted(conn.get_snapshot(), reverse=True, key=lambda k: k['date']) inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE) @@ -810,6 +812,17 @@ def instance(request, compute_id, vname): return HttpResponseRedirect(request.get_full_path() + '#vncsettings') if request.user.is_superuser: + if 'set_guest_agent' in request.POST: + status = request.POST.get('guest_agent') + if status == 'True': + conn.add_guest_agent() + if status == 'False': + conn.remove_guest_agent() + + msg = _("Set Quest Agent {}".format(status)) + addlogmsg(request.user.username, instance.name, msg) + return HttpResponseRedirect(request.get_full_path() + '#options') + if 'set_video_model' in request.POST: video_model = request.POST.get('video_model', 'vga') conn.set_video_model(video_model) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 2605b93..71d44a2 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -336,7 +336,7 @@ class wvmInstance(wvmConnect): if not self.get_status() == 1: return - if self.agent_ready(): + if self.is_agent_ready(): self._ip_cache["qemuga"] = self._get_interface_addresses( VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT) @@ -1381,19 +1381,37 @@ class wvmInstance(wvmConnect): tree = etree.fromstring(self._XMLDesc(0)) for direct in tree.xpath("/domain/devices/interface/bandwidth/{}".format(direction)): band_el = direct.getparent() - interface_el = band_el.getparent() # parent bandwidth,it parent is interface + interface_el = band_el.getparent() # parent bandwidth,its parent is interface parent_mac = interface_el.xpath('mac/@address') if parent_mac[0] == mac: band_el.remove(direct) self.wvm.defineXML(etree.tostring(tree)) - def agent_ready(self): - """ - Return connected state of an agent. - """ - # we need to get a fresh agent channel object on each call so it - # reflects the current state + def add_guest_agent(self): + channel_xml = """ + + + + """ + if self.get_status() == 1: + self.instance.attachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_LIVE) + self.instance.attachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG) + if self.get_status() == 5: + self.instance.attachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG) + + def remove_guest_agent(self): + tree = etree.fromstring(self._XMLDesc(0)) + for target in tree.xpath("/domain/devices/channel[@type='unix']/target[@name='org.qemu.guest_agent.0']"): + parent = target.getparent() + channel_xml = etree.tostring(parent) + if self.get_status() == 1: + self.instance.detachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_LIVE) + self.instance.detachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG) + if self.get_status() == 5: + self.instance.detachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG) + + def get_guest_agent(self): def _get_agent(doc): """ Return agent channel object if it is defined. @@ -1406,8 +1424,16 @@ class wvmInstance(wvmConnect): return channel return None - dev = util.get_xml_path(self._XMLDesc(0), func=_get_agent) - if len(dev) > 0: + return util.get_xml_path(self._XMLDesc(0), func=_get_agent) + + def is_agent_ready(self): + """ + Return connected state of an agent. + """ + # we need to get a fresh agent channel object on each call so it + # reflects the current state + dev = self.get_guest_agent() + if dev is not None: states = dev.xpath("target/@state") state = states[0] if len(states) > 0 else '' if state == "connected": From e87d7463fedaabdfc2a35aea960b722b3df5d7f4 Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 25 Dec 2019 14:34:55 +0300 Subject: [PATCH 6/7] check if host supports kvm, if it is, use kvm else qemu --- vrtManager/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrtManager/connection.py b/vrtManager/connection.py index 19b9684..a1105a8 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -351,7 +351,7 @@ class wvmConnect(object): def get_dom_cap_xml(self, arch, machine): """ Return domain capabilities xml""" emulatorbin = self.get_emulator(arch) - virttype = self.get_hypervisors_domain_types()[arch][0] + virttype = 'kvm' if 'kvm' in self.get_hypervisors_domain_types()[arch] else 'qemu' machine_types = self.get_machine_types(arch) if not machine or machine not in machine_types: From 38054d9882ef40c7e112118b7f27b8a3981cab67 Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 25 Dec 2019 14:36:43 +0300 Subject: [PATCH 7/7] for instances add guest agent indicator: installed, connected. --- instances/templates/instance.html | 32 +++++++++++++++++++++++++------ vrtManager/instance.py | 4 ++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 2ca73a5..261ab76 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -13,15 +13,15 @@
- {% ifequal status 5 %} + {% if status == 5 %} {% trans "Off" %} - {% endifequal %} - {% ifequal status 1 %} + {% endif %} + {% if status == 1 %} {% trans "Active" %} - {% endifequal %} - {% ifequal status 3 %} + {% endif %} + {% if status == 3 %} {% trans "Suspend" %} - {% endifequal %} + {% endif %} | {% if cur_vcpu %} {{ cur_vcpu }} {% trans "Vcpu" %} @@ -34,6 +34,22 @@ {% for disk in disks %} {{ disk.size|filesizeformat }} {% trans "Disk" %} | {% endfor %} + + | on {{ compute.name }}{% if compute.name != compute.hostname %} - {{ compute.hostname }}{% endif %} @@ -1437,11 +1453,15 @@

{% trans "To Enable/Disable Qemu Guest Agent. Status:" %} + {% if status == 1 %} {% if guest_agent_ready %} {% else %} {% endif %}

+ {% else %} + + {% endif %}
{% csrf_token %}
diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 71d44a2..27354db 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -1417,10 +1417,10 @@ class wvmInstance(wvmConnect): Return agent channel object if it is defined. """ for channel in doc.xpath('/domain/devices/channel'): - type = channel.get("type") + ch_type = channel.get("type") target = channel.find("target") target_name = target.get("name") - if type == "unix" and target_name == "org.qemu.guest_agent.0": + if ch_type == "unix" and target_name == "org.qemu.guest_agent.0": return channel return None