mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-02-25 18:55:27 -06:00
658 lines
22 KiB
Python
658 lines
22 KiB
Python
#
|
|
# Copyright (C) 2006 Red Hat, Inc.
|
|
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
#
|
|
|
|
import gobject
|
|
import libvirt
|
|
import logging
|
|
import os, sys
|
|
import traceback
|
|
from time import time
|
|
import logging
|
|
from socket import gethostbyaddr, gethostname
|
|
import dbus
|
|
|
|
from virtManager.domain import vmmDomain
|
|
from virtManager.network import vmmNetwork
|
|
from virtManager.netdev import vmmNetDevice
|
|
|
|
class vmmConnection(gobject.GObject):
|
|
__gsignals__ = {
|
|
"vm-added": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str, str]),
|
|
"vm-started": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str, str]),
|
|
"vm-removed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str, str]),
|
|
"net-added": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str, str]),
|
|
"net-removed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str, str]),
|
|
"net-started": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str, str]),
|
|
"net-stopped": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str, str]),
|
|
"netdev-added": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str]),
|
|
"netdev-removed": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[str]),
|
|
"resources-sampled": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
[]),
|
|
"disconnected": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, [str])
|
|
}
|
|
|
|
def __init__(self, config, uri, readOnly, active=True):
|
|
self.__gobject_init__()
|
|
self.config = config
|
|
self.uri = uri
|
|
self.readOnly = readOnly
|
|
self._active = active
|
|
|
|
openURI = uri
|
|
if openURI == "Xen":
|
|
openURI = None
|
|
|
|
if readOnly is None:
|
|
try:
|
|
self.vmm = libvirt.open(openURI)
|
|
self.readOnly = False
|
|
except:
|
|
self.vmm = libvirt.openReadOnly(openURI)
|
|
self.readOnly = True
|
|
else:
|
|
if readOnly:
|
|
self.vmm = libvirt.openReadOnly(openURI)
|
|
else:
|
|
self.vmm = libvirt.open(openURI)
|
|
|
|
self.netdevs = {}
|
|
self.nets = {}
|
|
self.vms = {}
|
|
self.activeUUIDs = []
|
|
self.record = []
|
|
|
|
self.detect_network_devices()
|
|
|
|
def detect_network_devices(self):
|
|
try:
|
|
# Get a connection to the SYSTEM bus
|
|
self.bus = dbus.SystemBus()
|
|
# Get a handle to the HAL service
|
|
hal_object = self.bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
|
|
self.hal_iface = dbus.Interface(hal_object, 'org.freedesktop.Hal.Manager')
|
|
|
|
# Track device add/removes so we can detect newly inserted CD media
|
|
self.hal_iface.connect_to_signal("DeviceAdded", self._device_added)
|
|
self.hal_iface.connect_to_signal("DeviceRemoved", self._device_removed)
|
|
|
|
# Find info about all current present media
|
|
for path in self.hal_iface.FindDeviceByCapability("net"):
|
|
self._device_added(path)
|
|
except:
|
|
(type, value, stacktrace) = sys.exc_info ()
|
|
logging.error("Unable to connect to HAL to list network devices: '%s'" + \
|
|
str(type) + " " + str(value) + "\n" + \
|
|
traceback.format_exc (stacktrace))
|
|
self.bus = None
|
|
self.hal_iface = None
|
|
|
|
def _device_added(self, path):
|
|
obj = self.bus.get_object("org.freedesktop.Hal", path)
|
|
if obj.QueryCapability("net"):
|
|
name = obj.GetPropertyString("net.interface")
|
|
mac = obj.GetPropertyString("net.address")
|
|
|
|
# Now magic to determine if the device is part of a bridge
|
|
shared = False
|
|
bridge = None
|
|
try:
|
|
# XXX Linux specific - needs porting for other OS - patches
|
|
# welcomed...
|
|
sysfspath = obj.GetPropertyString("linux.sysfs_path")
|
|
|
|
# Sick, disgusting hack for Xen netloop crack which renames
|
|
# ethN -> pethN, but which HAL never sees
|
|
psysfspath = sysfspath[0:len(sysfspath)-len(name)] + "p" + name
|
|
if os.path.exists(psysfspath):
|
|
sysfspath = psysfspath
|
|
|
|
brportpath = os.path.join(sysfspath, "brport")
|
|
|
|
if os.path.exists(brportpath):
|
|
shared = True
|
|
brlinkpath = os.path.join(brportpath, "bridge")
|
|
dest = os.readlink(brlinkpath)
|
|
(head,tail) = os.path.split(dest)
|
|
bridge = tail
|
|
except:
|
|
(type, value, stacktrace) = sys.exc_info ()
|
|
logging.error("Unable to determine if device is shared:" +
|
|
str(type) + " " + str(value) + "\n" + \
|
|
traceback.format_exc (stacktrace))
|
|
|
|
if self.netdevs.has_key(path):
|
|
currDev = self.netdevs[path]
|
|
if currDev.get_info() == (name, mac, shared, bridge):
|
|
return
|
|
del self.netdevs[path]
|
|
dev = vmmNetDevice(self.config, self, name, mac, shared, bridge)
|
|
self.netdevs[path] = dev
|
|
self.emit("netdev-added", dev.get_name())
|
|
|
|
def _device_removed(self, path):
|
|
if self.netdevs.has_key(path):
|
|
dev = self.netdevs[path]
|
|
self.emit("netdev-removed", dev.get_name())
|
|
del self.netdevs[path]
|
|
|
|
def is_read_only(self):
|
|
return self.readOnly
|
|
|
|
def get_type(self):
|
|
return self.vmm.getType()
|
|
|
|
def get_local_hostname(self):
|
|
try:
|
|
(host, aliases, ipaddrs) = gethostbyaddr(gethostname())
|
|
return host
|
|
except:
|
|
logging.warning("Unable to resolve local hostname for machine")
|
|
return "localhost"
|
|
|
|
def get_hostname(self):
|
|
try:
|
|
(scheme, netloc, path, query, fragment) = self.uri_split()
|
|
|
|
if netloc != "":
|
|
return netloc
|
|
except Exception, e:
|
|
logging.warning("Cannot parse URI %s: %s" % (self.uri, str(e)))
|
|
|
|
return self.get_local_hostname()
|
|
|
|
def get_name(self):
|
|
try:
|
|
(scheme, netloc, path, query, fragment) = self.uri_split()
|
|
|
|
i = scheme.find("+")
|
|
if i > 0:
|
|
scheme = scheme[0:i]
|
|
|
|
if netloc == "":
|
|
netloc = self.get_local_hostname()
|
|
|
|
if scheme == "xen":
|
|
return "Xen on %s" % netloc
|
|
elif scheme == "qemu":
|
|
if path == "/session":
|
|
return "QEMU session on %s" % netloc
|
|
else:
|
|
return "QEMU system on %s" % netloc
|
|
elif scheme == "test":
|
|
return "Test on %s" % netloc
|
|
except Exception, e:
|
|
logging.warning("Cannot parse URI %s: %s" % (self.uri, str(e)))
|
|
|
|
return self.uri
|
|
|
|
def is_remote(self):
|
|
try:
|
|
(scheme, netloc, path, query, fragment) = self.uri_split()
|
|
if netloc == "":
|
|
return False
|
|
return True
|
|
except:
|
|
return True
|
|
|
|
def get_uri(self):
|
|
return self.uri
|
|
|
|
def get_vm(self, uuid):
|
|
return self.vms[uuid]
|
|
|
|
def get_net(self, uuid):
|
|
return self.nets[uuid]
|
|
|
|
def get_net_device(self, path):
|
|
return self.netdevs[path]
|
|
|
|
def close(self):
|
|
if self.vmm == None:
|
|
return
|
|
|
|
#self.vmm.close()
|
|
self.vmm = None
|
|
self.emit("disconnected", self.uri)
|
|
|
|
def list_vm_uuids(self):
|
|
return self.vms.keys()
|
|
|
|
def list_net_uuids(self):
|
|
return self.nets.keys()
|
|
|
|
def list_net_device_paths(self):
|
|
return self.netdevs.keys()
|
|
|
|
def get_host_info(self):
|
|
return self.hostinfo
|
|
|
|
def get_max_vcpus(self):
|
|
try:
|
|
return self.vmm.getMaxVcpus(self.get_type())
|
|
except Exception, e:
|
|
logging.debug('Unable to get max vcpu')
|
|
return 32;
|
|
|
|
def connect(self, name, callback):
|
|
handle_id = gobject.GObject.connect(self, name, callback)
|
|
|
|
if name == "vm-added":
|
|
for uuid in self.vms.keys():
|
|
self.emit("vm-added", self.uri, uuid)
|
|
|
|
return handle_id
|
|
|
|
def pretty_host_memory_size(self):
|
|
mem = self.host_memory_size()
|
|
if mem > (1024*1024):
|
|
return "%2.2f GB" % (mem/(1024.0*1024.0))
|
|
else:
|
|
return "%2.2f MB" % (mem/1024.0)
|
|
|
|
|
|
def host_memory_size(self):
|
|
return self.hostinfo[1]*1024
|
|
|
|
def host_architecture(self):
|
|
return self.hostinfo[0]
|
|
|
|
def host_active_processor_count(self):
|
|
return self.hostinfo[2]
|
|
|
|
def host_maximum_processor_count(self):
|
|
return self.hostinfo[4] * self.hostinfo[5] * self.hostinfo[6] * self.hostinfo[7]
|
|
|
|
def create_network(self, xml, start=True, autostart=True):
|
|
net = self.vmm.networkDefineXML(xml)
|
|
uuid = self.uuidstr(net.UUID())
|
|
self.nets[uuid] = vmmNetwork(self.config, self, net, uuid, False)
|
|
self.nets[uuid].start()
|
|
self.nets[uuid].set_active(True)
|
|
self.nets[uuid].set_autostart(True)
|
|
self.emit("net-added", self.uri, uuid)
|
|
self.emit("net-started", self.uri, uuid)
|
|
return self.nets[uuid]
|
|
|
|
def define_domain(self, xml):
|
|
self.vmm.defineXML(xml)
|
|
|
|
def restore(self, frm):
|
|
status = self.vmm.restore(frm)
|
|
if(status == 0):
|
|
os.remove(frm)
|
|
return status
|
|
|
|
def tick(self, noStatsUpdate=False):
|
|
if self.vmm == None:
|
|
return
|
|
|
|
if not self.is_active:
|
|
return
|
|
|
|
oldNets = self.nets
|
|
startNets = {}
|
|
stopNets = {}
|
|
self.nets = {}
|
|
newNets = {}
|
|
newActiveNetNames = []
|
|
newInactiveNetNames = []
|
|
try:
|
|
newActiveNetNames = self.vmm.listNetworks()
|
|
except:
|
|
logging.warn("Unable to list active networks")
|
|
try:
|
|
newInactiveNetNames = self.vmm.listDefinedNetworks()
|
|
except:
|
|
logging.warn("Unable to list inactive networks")
|
|
|
|
# check of net devices
|
|
newPaths = self.hal_iface.FindDeviceByCapability("net")
|
|
for newPath in newPaths:
|
|
self._device_added(newPath)
|
|
|
|
for name in newActiveNetNames:
|
|
net = self.vmm.networkLookupByName(name)
|
|
uuid = self.uuidstr(net.UUID())
|
|
if not oldNets.has_key(uuid):
|
|
self.nets[uuid] = vmmNetwork(self.config, self, net, uuid, True)
|
|
newNets[uuid] = self.nets[uuid]
|
|
startNets[uuid] = newNets[uuid]
|
|
else:
|
|
self.nets[uuid] = oldNets[uuid]
|
|
if not self.nets[uuid].is_active():
|
|
self.nets[uuid].set_active(True)
|
|
startNets[uuid] = self.nets[uuid]
|
|
del oldNets[uuid]
|
|
for name in newInactiveNetNames:
|
|
net = self.vmm.networkLookupByName(name)
|
|
uuid = self.uuidstr(net.UUID())
|
|
if not oldNets.has_key(uuid):
|
|
self.nets[uuid] = vmmNetwork(self.config, self, net, uuid, False)
|
|
newNets[uuid] = self.nets[uuid]
|
|
else:
|
|
self.nets[uuid] = oldNets[uuid]
|
|
if self.nets[uuid].is_active():
|
|
self.nets[uuid].set_active(False)
|
|
stopNets[uuid] = self.nets[uuid]
|
|
del oldNets[uuid]
|
|
|
|
oldActiveIDs = {}
|
|
oldInactiveNames = {}
|
|
for uuid in self.vms.keys():
|
|
# first pull out all the current inactive VMs we know about
|
|
vm = self.vms[uuid]
|
|
if vm.get_id() == -1:
|
|
oldInactiveNames[vm.get_name()] = vm
|
|
|
|
for uuid in self.activeUUIDs:
|
|
# Now get all the vms that were active the last time around and are still active
|
|
vm = self.vms[uuid]
|
|
if vm.get_id() != -1:
|
|
oldActiveIDs[vm.get_id()] = vm
|
|
|
|
# Now we can clear the list of actives from the last time through
|
|
self.activeUUIDs = []
|
|
|
|
newActiveIDs = self.vmm.listDomainsID()
|
|
newInactiveNames = []
|
|
try:
|
|
newInactiveNames = self.vmm.listDefinedDomains()
|
|
except:
|
|
logging.warn("Unable to list inactive domains")
|
|
|
|
newUUIDs = {}
|
|
oldUUIDs = {}
|
|
curUUIDs = {}
|
|
maybeNewUUIDs = {}
|
|
startedUUIDs = []
|
|
|
|
# NB in these first 2 loops, we go to great pains to
|
|
# avoid actually instantiating a new VM object so that
|
|
# the common case of 'no new/old VMs' avoids hitting
|
|
# XenD too much & thus slowing stuff down.
|
|
|
|
# Filter out active domains which haven't changed
|
|
if newActiveIDs != None:
|
|
for id in newActiveIDs:
|
|
if oldActiveIDs.has_key(id):
|
|
# No change, copy across existing VM object
|
|
vm = oldActiveIDs[id]
|
|
curUUIDs[vm.get_uuid()] = vm
|
|
self.activeUUIDs.append(vm.get_uuid())
|
|
else:
|
|
# May be a new VM, we have no choice but
|
|
# to create the wrapper so we can see
|
|
# if its a previously inactive domain.
|
|
vm = self.vmm.lookupByID(id)
|
|
uuid = self.uuidstr(vm.UUID())
|
|
maybeNewUUIDs[uuid] = vm
|
|
# also add the new or newly started VM to the "started" list
|
|
startedUUIDs.append(uuid)
|
|
#print "Maybe new active " + str(maybeNewUUIDs[uuid].get_name()) + " " + uuid
|
|
self.activeUUIDs.append(uuid)
|
|
|
|
# Filter out inactive domains which haven't changed
|
|
if newInactiveNames != None:
|
|
for name in newInactiveNames:
|
|
if oldInactiveNames.has_key(name):
|
|
# No change, copy across existing VM object
|
|
vm = oldInactiveNames[name]
|
|
#print "Existing inactive " + str(vm.get_name()) + " " + vm.get_uuid()
|
|
curUUIDs[vm.get_uuid()] = vm
|
|
else:
|
|
# May be a new VM, we have no choice but
|
|
# to create the wrapper so we can see
|
|
# if its a previously inactive domain.
|
|
try:
|
|
vm = self.vmm.lookupByName(name)
|
|
uuid = self.uuidstr(vm.UUID())
|
|
maybeNewUUIDs[uuid] = vm
|
|
except libvirt.libvirtError:
|
|
logging.debug("Couldn't fetch domain id " + str(id) + "; it probably went away")
|
|
#print "Maybe new inactive " + str(maybeNewUUIDs[uuid].get_name()) + " " + uuid
|
|
|
|
# At this point, maybeNewUUIDs has domains which are
|
|
# either completely new, or changed state.
|
|
|
|
# Filter out VMs which merely changed state, leaving
|
|
# only new domains
|
|
for uuid in maybeNewUUIDs.keys():
|
|
rawvm = maybeNewUUIDs[uuid]
|
|
if not(self.vms.has_key(uuid)):
|
|
#print "Completely new VM " + str(vm)
|
|
vm = vmmDomain(self.config, self, rawvm, uuid)
|
|
newUUIDs[vm.get_uuid()] = vm
|
|
curUUIDs[uuid] = vm
|
|
else:
|
|
vm = self.vms[uuid]
|
|
vm.release_handle()
|
|
vm.set_handle(rawvm)
|
|
curUUIDs[uuid] = vm
|
|
#print "Mere state change " + str(vm)
|
|
|
|
# Finalize list of domains which went away altogether
|
|
for uuid in self.vms.keys():
|
|
vm = self.vms[uuid]
|
|
if not(curUUIDs.has_key(uuid)):
|
|
#print "Completly old VM " + str(vm)
|
|
oldUUIDs[uuid] = vm
|
|
else:
|
|
#print "Mere state change " + str(vm)
|
|
pass
|
|
|
|
# We have our new master list
|
|
self.vms = curUUIDs
|
|
|
|
# Inform everyone what changed
|
|
for uuid in oldUUIDs:
|
|
self.emit("vm-removed", self.uri, uuid)
|
|
oldUUIDs[uuid].release_handle()
|
|
|
|
for uuid in newUUIDs:
|
|
self.emit("vm-added", self.uri, uuid)
|
|
|
|
for uuid in startedUUIDs:
|
|
self.emit("vm-started", self.uri, uuid)
|
|
|
|
for uuid in oldNets:
|
|
self.emit("net-removed", self.uri, uuid)
|
|
|
|
for uuid in newNets:
|
|
self.emit("net-added", self.uri, uuid)
|
|
|
|
for uuid in startNets:
|
|
self.emit("net-started", self.uri, uuid)
|
|
|
|
for uuid in stopNets:
|
|
self.emit("net-stopped", self.uri, uuid)
|
|
|
|
# Finally, we sample each domain
|
|
now = time()
|
|
self.hostinfo = self.vmm.getInfo()
|
|
|
|
updateVMs = self.vms
|
|
if noStatsUpdate:
|
|
updateVMs = newUUIDs
|
|
|
|
for uuid in updateVMs.keys():
|
|
self.vms[uuid].tick(now)
|
|
|
|
if not noStatsUpdate:
|
|
self.recalculate_stats(now)
|
|
|
|
return 1
|
|
|
|
def recalculate_stats(self, now):
|
|
expected = self.config.get_stats_history_length()
|
|
current = len(self.record)
|
|
if current > expected:
|
|
del self.record[expected:current]
|
|
|
|
mem = 0
|
|
cpuTime = 0
|
|
|
|
for uuid in self.vms:
|
|
vm = self.vms[uuid]
|
|
if vm.get_id() != -1:
|
|
cpuTime = cpuTime + vm.get_cputime()
|
|
mem = mem + vm.get_memory()
|
|
|
|
|
|
pcentCpuTime = 0
|
|
if len(self.record) > 0:
|
|
prevTimestamp = self.record[0]["timestamp"]
|
|
|
|
pcentCpuTime = (cpuTime) * 100.0 / ((now - prevTimestamp)*1000.0*1000.0*1000.0*self.host_active_processor_count())
|
|
# Due to timing diffs between getting wall time & getting
|
|
# the domain's time, its possible to go a tiny bit over
|
|
# 100% utilization. This freaks out users of the data, so
|
|
# we hard limit it.
|
|
if pcentCpuTime > 100.0:
|
|
pcentCpuTime = 100.0
|
|
# Enforce >= 0 just in case
|
|
if pcentCpuTime < 0.0:
|
|
pcentCpuTime = 0.0
|
|
|
|
pcentMem = mem * 100.0 / self.host_memory_size()
|
|
|
|
newStats = {
|
|
"timestamp": now,
|
|
"memory": mem,
|
|
"memoryPercent": pcentMem,
|
|
"cpuTime": cpuTime,
|
|
"cpuTimePercent": pcentCpuTime
|
|
}
|
|
|
|
self.record.insert(0, newStats)
|
|
self.emit("resources-sampled")
|
|
|
|
def cpu_time_vector(self):
|
|
vector = []
|
|
stats = self.record
|
|
for i in range(self.config.get_stats_history_length()+1):
|
|
if i < len(stats):
|
|
vector.append(stats[i]["cpuTimePercent"]/100.0)
|
|
else:
|
|
vector.append(0)
|
|
return vector
|
|
|
|
def cpu_time_vector_limit(self, limit):
|
|
cpudata = self.cpu_time_vector()
|
|
if len(cpudata) > limit:
|
|
cpudata = cpudata[0:limit]
|
|
return cpudata
|
|
|
|
def cpu_time_percentage(self):
|
|
if len(self.record) == 0:
|
|
return 0
|
|
return self.record[0]["cpuTimePercent"]
|
|
|
|
def current_memory(self):
|
|
if len(self.record) == 0:
|
|
return 0
|
|
return self.record[0]["memory"]
|
|
|
|
def pretty_current_memory(self):
|
|
mem = self.current_memory()
|
|
if mem > (1024*1024):
|
|
return "%2.2f GB" % (mem/(1024.0*1024.0))
|
|
else:
|
|
return "%2.2f MB" % (mem/1024.0)
|
|
|
|
def current_memory(self):
|
|
if len(self.record) == 0:
|
|
return 0
|
|
return self.record[0]["memory"]
|
|
|
|
def current_memory_percentage(self):
|
|
if len(self.record) == 0:
|
|
return 0
|
|
return self.record[0]["memoryPercent"]
|
|
|
|
def current_memory_vector(self):
|
|
vector = []
|
|
stats = self.record
|
|
for i in range(self.config.get_stats_history_length()+1):
|
|
if i < len(stats):
|
|
vector.append(stats[i]["memoryPercent"]/100.0)
|
|
else:
|
|
vector.append(0)
|
|
return vector
|
|
|
|
def uuidstr(self, rawuuid):
|
|
hex = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
|
|
uuid = []
|
|
for i in range(16):
|
|
uuid.append(hex[((ord(rawuuid[i]) >> 4) & 0xf)])
|
|
uuid.append(hex[(ord(rawuuid[i]) & 0xf)])
|
|
if i == 3 or i == 5 or i == 7 or i == 9:
|
|
uuid.append('-')
|
|
return "".join(uuid)
|
|
|
|
|
|
# Standard python urlparse is utterly braindead - refusing to parse URIs
|
|
# in any useful fashion unless the 'scheme' is in some pre-defined white
|
|
# list. Theis functions is a hacked version of urlparse
|
|
|
|
def uri_split(self):
|
|
uri = self.uri
|
|
netloc = query = fragment = ''
|
|
i = uri.find(":")
|
|
if i > 0:
|
|
scheme, uri = uri[:i].lower(), uri[i+1:]
|
|
if uri[:2] == '//':
|
|
netloc, uri = self._splitnetloc(uri, 2)
|
|
if '#' in uri:
|
|
uri, fragment = uri.split('#', 1)
|
|
if '?' in uri:
|
|
uri, query = uri.split('?', 1)
|
|
else:
|
|
scheme = uri.lower()
|
|
|
|
return scheme, netloc, uri, query, fragment
|
|
|
|
def _splitnetloc(self, url, start=0):
|
|
for c in '/?#': # the order is important!
|
|
delim = url.find(c, start)
|
|
if delim >= 0:
|
|
break
|
|
else:
|
|
delim = len(url)
|
|
return url[start:delim], url[delim:]
|
|
|
|
|
|
def is_active(self):
|
|
return self._active
|
|
def set_active(self, val):
|
|
self._active = val
|
|
active = property(is_active, set_active)
|
|
|
|
|
|
gobject.type_register(vmmConnection)
|
|
|