uri: Add a MagicURI class for handling magic virtinst URIs

And document it
This commit is contained in:
Cole Robinson 2015-09-06 10:36:17 -04:00
parent ed48b5d0a8
commit 1d7b74ba52
8 changed files with 214 additions and 202 deletions

View File

@ -17,18 +17,18 @@
import unittest
from virtinst import URISplit
from virtinst import URI
class TestURI(unittest.TestCase):
"""
Test virtinst URISplit module
Test virtinst URI module
"""
def _compare(self, uri, scheme='',
transport='', port='', username='', path='',
hostname='', query='', fragment='',
is_ipv6=False, host_is_ipv4_string=False):
uriinfo = URISplit(uri)
uriinfo = URI(uri)
self.assertEquals(scheme, uriinfo.scheme)
self.assertEquals(transport, uriinfo.transport)
self.assertEquals(port, uriinfo.port)
@ -39,7 +39,6 @@ class TestURI(unittest.TestCase):
self.assertEquals(fragment, uriinfo.fragment)
self.assertEquals(is_ipv6, uriinfo.is_ipv6)
self.assertEquals(host_is_ipv4_string, uriinfo.host_is_ipv4_string)
self.assertEquals(uri, uriinfo.rebuild_uri())
def testURIs(self):
self._compare("lxc://", scheme="lxc")

View File

@ -355,8 +355,6 @@ class vmmConnection(vmmGObject):
"""
if self._get_config_pretty_name():
return self._get_config_pretty_name()
if self._backend.fake_name():
return self._backend.fake_name()
pretty_map = {
"esx" : "ESX",

View File

@ -36,7 +36,7 @@ stable_defaults = _CLIConfig.stable_defaults
from . import util
from virtinst import support
from virtinst.uri import URISplit
from virtinst.uri import URI
from virtinst.osdict import OSDB
from virtinst.osxml import OSXML

View File

@ -17,7 +17,6 @@
# MA 02110-1301 USA.
import logging
import re
import weakref
import libvirt
@ -25,32 +24,13 @@ import libvirt
from . import pollhelpers
from . import support
from . import util
from . import URISplit
from . import Capabilities
from .cli import VirtOptionString
from .guest import Guest
from .nodedev import NodeDevice
from .storage import StoragePool, StorageVolume
from .uri import URI, MagicURI
from virtcli import CLIConfig
_virtinst_uri_magic = "__virtinst_test__"
def _sanitize_xml(xml):
import difflib
orig = xml
xml = re.sub("arch=\".*\"", "arch=\"i686\"", xml)
xml = re.sub("domain type=\".*\"", "domain type=\"test\"", xml)
xml = re.sub("machine type=\".*\"", "", xml)
xml = re.sub(">exe<", ">hvm<", xml)
diff = "\n".join(difflib.unified_diff(orig.split("\n"),
xml.split("\n")))
if diff:
logging.debug("virtinst test sanitizing diff\n:%s", diff)
return xml
class VirtualConnection(object):
"""
@ -60,30 +40,34 @@ class VirtualConnection(object):
- simplified API wrappers that handle new and old ways of doing things
"""
def __init__(self, uri):
self._initial_uri = uri or ""
_initial_uri = uri or ""
if MagicURI.uri_is_magic(_initial_uri):
self._magic_uri = MagicURI(_initial_uri)
self._open_uri = self._magic_uri.open_uri
self._uri = self._magic_uri.make_fake_uri()
self._fake_conn_predictable = self._magic_uri.predictable
self._fake_conn_remote = self._magic_uri.remote
self._fake_conn_session = self._magic_uri.session
self._fake_conn_version = self._magic_uri.conn_version
self._fake_libvirt_version = self._magic_uri.libvirt_version
else:
self._magic_uri = None
self._open_uri = _initial_uri
self._uri = _initial_uri
self._fake_conn_predictable = False
self._fake_conn_remote = False
self._fake_conn_session = False
self._fake_libvirt_version = None
self._fake_conn_version = None
self._fake_pretty_name = None
self._fake_libvirt_version = None
self._fake_conn_version = None
self._daemon_version = None
self._conn_version = None
if self._initial_uri.startswith(_virtinst_uri_magic):
# virtinst unit test URI handling
uri = self._initial_uri.replace(_virtinst_uri_magic, "")
ret = uri.split(",", 1)
self._open_uri = ret[0]
self._test_opts = VirtOptionString(
len(ret) > 1 and ret[1] or "", [], None).opts
self._early_virtinst_test_uri()
self._uri = self._virtinst_uri_make_fake()
else:
self._open_uri = self._initial_uri
self._uri = self._initial_uri
self._test_opts = {}
self._libvirtconn = None
self._urisplits = URISplit(self._uri)
self._uriobj = URI(self._uri)
self._caps = None
self._support_cache = {}
@ -142,6 +126,9 @@ class VirtualConnection(object):
self._uri = None
self._fetch_cache = {}
def fake_conn_predictable(self):
return self._fake_conn_predictable
def invalidate_caps(self):
self._caps = None
@ -160,11 +147,13 @@ class VirtualConnection(object):
(authcb_data, valid_auth_options)],
open_flags)
self._fixup_virtinst_test_uri(conn)
if self._magic_uri:
self._magic_uri.overwrite_conn_functions(conn)
self._libvirtconn = conn
if not self._open_uri:
self._uri = self._libvirtconn.getURI()
self._urisplits = URISplit(self._uri)
self._uriobj = URI(self._uri)
def set_keep_alive(self, interval, count):
if hasattr(self._libvirtconn, "setKeepAlive"):
@ -354,48 +343,43 @@ class VirtualConnection(object):
# Public URI bits #
###################
def fake_name(self):
return self._fake_pretty_name
def is_remote(self):
return (hasattr(self, "_virtinst__fake_conn_remote") or
self._urisplits.hostname)
return (self._fake_conn_remote or self._uriobj.hostname)
def is_session_uri(self):
return (hasattr(self, "_virtinst__fake_conn_session") or
self.get_uri_path() == "/session")
return (self._fake_conn_session or self.get_uri_path() == "/session")
def get_uri_hostname(self):
return self._urisplits.hostname
return self._uriobj.hostname
def get_uri_port(self):
return self._urisplits.port
return self._uriobj.port
def get_uri_username(self):
return self._urisplits.username
return self._uriobj.username
def get_uri_transport(self):
return self._urisplits.transport
return self._uriobj.transport
def get_uri_path(self):
return self._urisplits.path
return self._uriobj.path
def get_uri_driver(self):
return self._urisplits.scheme
return self._uriobj.scheme
def is_qemu(self):
return self._urisplits.scheme.startswith("qemu")
return self._uriobj.scheme.startswith("qemu")
def is_qemu_system(self):
return (self.is_qemu() and self._urisplits.path == "/system")
return (self.is_qemu() and self._uriobj.path == "/system")
def is_qemu_session(self):
return (self.is_qemu() and self.is_session_uri())
def is_really_test(self):
return URISplit(self._open_uri).scheme.startswith("test")
return URI(self._open_uri).scheme.startswith("test")
def is_test(self):
return self._urisplits.scheme.startswith("test")
return self._uriobj.scheme.startswith("test")
def is_xen(self):
return (self._urisplits.scheme.startswith("xen") or
self._urisplits.scheme.startswith("libxl"))
return (self._uriobj.scheme.startswith("xen") or
self._uriobj.scheme.startswith("libxl"))
def is_lxc(self):
return self._urisplits.scheme.startswith("lxc")
return self._uriobj.scheme.startswith("lxc")
def is_openvz(self):
return self._urisplits.scheme.startswith("openvz")
return self._uriobj.scheme.startswith("openvz")
def is_container(self):
return self.is_lxc() or self.is_openvz()
@ -417,7 +401,7 @@ class VirtualConnection(object):
return self._support_cache[key]
def support_remote_url_install(self):
if hasattr(self, "_virtinst__fake_conn"):
if self._magic_uri:
return False
return (self.check_support(self.SUPPORT_CONN_STREAM) and
self.check_support(self.SUPPORT_STREAM_UPLOAD))
@ -433,97 +417,3 @@ class VirtualConnection(object):
raise RuntimeError("Unknown cred type '%s', expected only "
"%s" % (cred[0], passwordcreds))
return passwordcb(creds)
def _virtinst_uri_make_fake(self):
if "qemu" in self._test_opts:
return "qemu+abc:///system"
elif "xen" in self._test_opts:
return "xen+abc:///"
elif "lxc" in self._test_opts:
return "lxc+abc:///"
return self._open_uri
def _early_virtinst_test_uri(self):
# Need tmpfile names to be deterministic
if not self._test_opts:
return
opts = self._test_opts
if "predictable" in opts:
opts.pop("predictable")
setattr(self, "_virtinst__fake_conn_predictable", True)
# Fake remote status
if "remote" in opts:
opts.pop("remote")
setattr(self, "_virtinst__fake_conn_remote", True)
if "session" in opts:
opts.pop("session")
setattr(self, "_virtinst__fake_conn_session", True)
if "prettyname" in opts:
self._fake_pretty_name = opts.pop("prettyname")
def _fixup_virtinst_test_uri(self, conn):
"""
This hack allows us to fake various drivers via passing a magic
URI string to virt-*. Helps with testing
"""
if not self._test_opts:
return
opts = self._test_opts.copy()
# Fake capabilities
if "caps" in opts:
capsxml = file(opts.pop("caps")).read()
conn.getCapabilities = lambda: capsxml
# Fake domcapabilities. This is insufficient since output should
# vary per type/arch/emulator combo, but it can be expanded later
# if needed
if "domcaps" in opts:
domcapsxml = file(opts.pop("domcaps")).read()
def fake_domcaps(emulator, arch, machine, virttype, flags=0):
ignore = emulator
ignore = flags
ignore = machine
ignore = virttype
ret = domcapsxml
if arch:
ret = re.sub("arch>.+</arch", "arch>%s</arch" % arch, ret)
return ret
conn.getDomainCapabilities = fake_domcaps
if ("qemu" in opts) or ("xen" in opts) or ("lxc" in opts):
opts.pop("qemu", None)
opts.pop("xen", None)
opts.pop("lxc", None)
self._fake_conn_version = 10000000000
origcreate = conn.createLinux
origdefine = conn.defineXML
def newcreate(xml, flags):
xml = _sanitize_xml(xml)
return origcreate(xml, flags)
def newdefine(xml):
xml = _sanitize_xml(xml)
return origdefine(xml)
conn.createLinux = newcreate
conn.defineXML = newdefine
# These need to come after the HV setter, since that sets a default
# conn version
if "connver" in opts:
self._fake_conn_version = int(opts.pop("connver"))
if "libver" in opts:
self._fake_libvirt_version = int(opts.pop("libver"))
if opts:
raise RuntimeError("Unhandled virtinst test uri options %s" % opts)
setattr(self, "_virtinst__fake_conn", True)

View File

@ -32,7 +32,6 @@ from . import diskbackend
from . import util
from .device import VirtualDevice
from .seclabel import Seclabel
from .uri import URISplit
from .xmlbuilder import XMLChildProperty, XMLProperty
@ -595,20 +594,22 @@ class VirtualDisk(VirtualDevice):
source_host_socket = XMLProperty("./source/host/@socket")
def _set_source_network_from_url(self, uri):
uriinfo = URISplit(uri)
if uriinfo.scheme:
self.source_protocol = uriinfo.scheme
if uriinfo.transport:
self.source_host_transport = uriinfo.transport
if uriinfo.hostname:
self.source_host_name = uriinfo.hostname
if uriinfo.port:
self.source_host_port = uriinfo.port
if uriinfo.path:
from .uri import URI
uriobj = URI(uri)
if uriobj.scheme:
self.source_protocol = uriobj.scheme
if uriobj.transport:
self.source_host_transport = uriobj.transport
if uriobj.hostname:
self.source_host_name = uriobj.hostname
if uriobj.port:
self.source_host_port = uriobj.port
if uriobj.path:
if self.source_host_transport:
self.source_host_socket = uriinfo.path
self.source_host_socket = uriobj.path
else:
self.source_name = uriinfo.path
self.source_name = uriobj.path
if self.source_name.startswith("/"):
self.source_name = self.source_name[1:]

View File

@ -97,7 +97,7 @@ class VirtualNetworkInterface(VirtualDevice):
Generate a random MAC that doesn't conflict with any VMs on
the connection.
"""
if hasattr(conn, "_virtinst__fake_conn_predictable"):
if conn.fake_conn_predictable():
# Testing hack
return "00:11:22:33:44:55"

View File

@ -17,10 +17,29 @@
# MA 02110-1301 USA.
#
import logging
import re
from .cli import VirtOptionString
class URISplit(object):
def _sanitize_xml(xml):
import difflib
orig = xml
xml = re.sub("arch=\".*\"", "arch=\"i686\"", xml)
xml = re.sub("domain type=\".*\"", "domain type=\"test\"", xml)
xml = re.sub("machine type=\".*\"", "", xml)
xml = re.sub(">exe<", ">hvm<", xml)
diff = "\n".join(difflib.unified_diff(orig.split("\n"),
xml.split("\n")))
if diff:
logging.debug("virtinst test sanitizing diff\n:%s", diff)
return xml
class URI(object):
"""
Parse an arbitrary URI into its individual parts
"""
@ -78,28 +97,133 @@ class URISplit(object):
return scheme, username, netloc, uri, query, fragment
class MagicURI(object):
"""
Handle magic virtinst URIs we use for the test suite and UI testing.
This allows a special URI to override various features like capabilities
XML, reported connection and libvirt versions, that enable testing
different code paths.
A magic URI has 3 parts:
1) Magic prefix __virtinst_test__
2) Actual openable URI, usually a test:/// URI
3) Comma separated options
The available options are:
* 'predictable': Generate predictable UUIDs, MAC addresses, and
temporary file names.
* 'remote': Have the code consider this as a remote URI
* 'session': Have the code consider this as a session URI
* 'connver=%d': Override the connection (hv) version
* 'libver=%d': Override the libvirt version
* 'caps=%s': Points to a file with capabilities XML, that will
be returned in conn.getCapabilities. Ex.
files in test/capabilities-xml/
* 'domcaps=%s': Points to a file with domain capabilities XML, that
will be returned in conn.getDomainCapabilities
* qemu or xen or lxc: Fake the specified hypervisor
See tests/utils.py for example URLs
"""
VIRTINST_URI_MAGIC_PREFIX = "__virtinst_test__"
@staticmethod
def uri_is_magic(uri):
return uri.startswith(MagicURI.VIRTINST_URI_MAGIC_PREFIX)
def __init__(self, uri):
if not self.uri_is_magic(uri):
raise RuntimeError("uri=%s is not virtinst magic URI" % uri)
uri = uri.replace(self.VIRTINST_URI_MAGIC_PREFIX, "")
ret = uri.split(",", 1)
self.open_uri = ret[0]
opts = VirtOptionString(len(ret) > 1 and ret[1] or "", [], None).opts
def pop_bool(field):
ret = field in opts
opts.pop(field, None)
return ret
self.predictable = pop_bool("predictable")
self.remote = pop_bool("remote")
self.session = pop_bool("session")
self.capsfile = opts.pop("caps", None)
self.domcapsfile = opts.pop("domcaps", None)
self.hv = None
if pop_bool("qemu"):
self.hv = "qemu"
if pop_bool("lxc"):
self.hv = "lxc"
if pop_bool("xen"):
self.hv = "xen"
self.conn_version = opts.pop("connver", None)
if self.conn_version:
self.conn_version = int(self.conn_version)
elif self.hv:
self.conn_version = 10000000000
self.libvirt_version = opts.pop("libver", None)
if self.libvirt_version:
self.libvirt_version = int(self.libvirt_version)
if opts:
raise RuntimeError("Unhandled virtinst test uri options %s" % opts)
##############
# Public API #
##############
def rebuild_uri(self):
ret = self.scheme
if self.transport:
ret += "+" + self.transport
ret += "://"
if self.username:
ret += self.username + "@"
if self.hostname:
host = self.hostname
if self.is_ipv6:
host = "[%s]" % self.hostname
ret += host
if self.port:
ret += ":" + self.port
if self.path:
ret += self.path
if self.query:
ret += "?" + self.query
if self.fragment:
ret += "#" + self.fragment
return ret
def make_fake_uri(self):
"""
If self.hv is set, we need to make a fake URI so that Connection
URI handling bits have something to work with.
"""
if self.hv:
return self.hv + "+abc:///system"
return self.open_uri
def overwrite_conn_functions(self, conn):
"""
After the connection is open, we need to stub out various functions
depending on what magic bits the user specified in the URI
"""
# Fake capabilities
if self.capsfile:
capsxml = file(self.capsfile).read()
conn.getCapabilities = lambda: capsxml
# Fake domcapabilities. This is insufficient since output should
# vary per type/arch/emulator combo, but it can be expanded later
# if needed
if self.domcapsfile:
domcapsxml = file(self.domcapsfile).read()
def fake_domcaps(emulator, arch, machine, virttype, flags=0):
ignore = emulator
ignore = flags
ignore = machine
ignore = virttype
ret = domcapsxml
if arch:
ret = re.sub("arch>.+</arch", "arch>%s</arch" % arch, ret)
return ret
conn.getDomainCapabilities = fake_domcaps
if self.hv:
origcreate = conn.createLinux
origdefine = conn.defineXML
def newcreate(xml, flags):
xml = _sanitize_xml(xml)
return origcreate(xml, flags)
def newdefine(xml):
xml = _sanitize_xml(xml)
return origdefine(xml)
conn.createLinux = newcreate
conn.defineXML = newdefine

View File

@ -309,7 +309,7 @@ def is_blktap_capable(conn):
def randomUUID(conn):
if hasattr(conn, "_virtinst__fake_conn_predictable"):
if conn.fake_conn_predictable():
# Testing hack
return "00000000-1111-2222-3333-444444444444"