diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 2cbd079..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 %} @@ -876,7 +892,9 @@ {% trans 'Name' %} {% trans 'MAC' %} - {% trans 'NIC' %} + {% trans 'IP Address' %} + {% trans 'Source' %} + {% trans 'LinkState' %} {% trans 'Filter' %} {% trans 'Qos' %} {% trans 'Actions' %} @@ -885,10 +903,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 %} @@ -1399,11 +1427,11 @@
-
+

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

{% csrf_token %}
- +
+ + + + + + +
+
+
+
+
+
{% endif %}
diff --git a/instances/views.py b/instances/views.py index 6daa554..78982d4 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() @@ -302,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) @@ -342,10 +345,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: @@ -616,7 +624,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) @@ -804,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) @@ -867,6 +886,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/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..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: @@ -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..27354db 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,72 @@ 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.is_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 = [] @@ -295,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] @@ -310,14 +368,15 @@ 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, + 'state': link_state, + 'ipv4': ipv4, + 'ipv6': ipv6, 'filterref': filterref_inst, 'inbound': inbound, 'outbound': outbound, @@ -709,7 +768,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 @@ -1217,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) @@ -1304,11 +1381,64 @@ 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 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. + """ + for channel in doc.xpath('/domain/devices/channel'): + ch_type = channel.get("type") + target = channel.find("target") + target_name = target.get("name") + if ch_type == "unix" and target_name == "org.qemu.guest_agent.0": + return channel + return None + + 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": + 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