2013-03-18 05:06:52 +08:00
|
|
|
#
|
|
|
|
# Helper functions for determining if libvirt supports certain features
|
|
|
|
#
|
2014-03-13 19:52:51 +08:00
|
|
|
# Copyright 2009, 2013, 2014 Red Hat, Inc.
|
2013-03-18 05:06:52 +08:00
|
|
|
#
|
2018-04-04 21:35:41 +08:00
|
|
|
# This work is licensed under the GNU GPLv2 or later.
|
2018-03-21 03:00:02 +08:00
|
|
|
# See the COPYING file in the top-level directory.
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
import libvirt
|
|
|
|
|
2014-09-13 03:59:22 +08:00
|
|
|
from . import util
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
|
|
|
# Check that command is present in the python bindings, and return the
|
|
|
|
# the requested function
|
|
|
|
def _get_command(funcname, objname=None, obj=None):
|
|
|
|
if not obj:
|
|
|
|
obj = libvirt
|
|
|
|
|
|
|
|
if objname:
|
|
|
|
if not hasattr(libvirt, objname):
|
|
|
|
return None
|
|
|
|
obj = getattr(libvirt, objname)
|
|
|
|
|
|
|
|
if not hasattr(obj, funcname):
|
|
|
|
return None
|
|
|
|
|
|
|
|
return getattr(obj, funcname)
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2013-07-06 08:36:28 +08:00
|
|
|
# Make sure libvirt object 'objname' has function 'funcname'
|
2013-03-18 05:06:52 +08:00
|
|
|
def _has_command(funcname, objname=None, obj=None):
|
|
|
|
return bool(_get_command(funcname, objname, obj))
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2013-07-06 08:36:28 +08:00
|
|
|
# Make sure libvirt object has flag 'flag_name'
|
2013-03-18 05:06:52 +08:00
|
|
|
def _get_flag(flag_name):
|
|
|
|
return _get_command(flag_name)
|
|
|
|
|
2013-07-06 08:36:28 +08:00
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
# Try to call the passed function, and look for signs that libvirt or driver
|
|
|
|
# doesn't support it
|
2015-02-19 04:16:02 +08:00
|
|
|
def _try_command(func, run_args, check_all_error=False):
|
2013-03-18 05:06:52 +08:00
|
|
|
try:
|
2015-02-19 04:16:02 +08:00
|
|
|
func(*run_args)
|
2017-05-06 00:47:21 +08:00
|
|
|
except libvirt.libvirtError as e:
|
2013-07-06 23:20:28 +08:00
|
|
|
if util.is_error_nosupport(e):
|
2013-03-18 05:06:52 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
if check_all_error:
|
|
|
|
return False
|
2017-05-06 00:47:21 +08:00
|
|
|
except Exception as e:
|
2013-03-18 05:06:52 +08:00
|
|
|
# Other python exceptions likely mean the bindings are horked
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2013-07-06 23:20:28 +08:00
|
|
|
# Return the hypervisor version
|
2013-03-18 05:06:52 +08:00
|
|
|
def _split_function_name(function):
|
|
|
|
if not function:
|
|
|
|
return (None, None)
|
|
|
|
|
|
|
|
output = function.split(".")
|
|
|
|
if len(output) == 1:
|
|
|
|
return (None, output[0])
|
|
|
|
else:
|
|
|
|
return (output[0], output[1])
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2015-02-19 04:16:02 +08:00
|
|
|
def _check_function(function, flag, run_args, data):
|
2019-06-08 02:45:25 +08:00
|
|
|
# Make sure function is present in either libvirt module or
|
|
|
|
# object_name class
|
2014-02-11 06:02:22 +08:00
|
|
|
object_name, function_name = _split_function_name(function)
|
|
|
|
if not function_name:
|
|
|
|
return None
|
|
|
|
|
|
|
|
flag_tuple = ()
|
|
|
|
|
|
|
|
if not _has_command(function_name, objname=object_name):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if flag:
|
|
|
|
found_flag = _get_flag(flag)
|
|
|
|
if not bool(found_flag):
|
|
|
|
return False
|
|
|
|
flag_tuple = (found_flag,)
|
|
|
|
|
2015-02-19 04:16:02 +08:00
|
|
|
if run_args is None:
|
2014-02-11 06:02:22 +08:00
|
|
|
return None
|
|
|
|
|
|
|
|
# If function requires an object, make sure the passed obj
|
|
|
|
# is of the correct type
|
|
|
|
if object_name:
|
|
|
|
classobj = _get_command(object_name)
|
|
|
|
if not isinstance(data, classobj):
|
|
|
|
raise ValueError(
|
|
|
|
"Passed obj %s with args must be of type %s, was %s" %
|
|
|
|
(data, str(classobj), type(data)))
|
|
|
|
|
|
|
|
cmd = _get_command(function_name, obj=data)
|
|
|
|
|
|
|
|
# Function with args specified is all the proof we need
|
2015-02-19 04:16:02 +08:00
|
|
|
return _try_command(cmd, run_args + flag_tuple,
|
2014-02-11 06:02:22 +08:00
|
|
|
check_all_error=bool(flag_tuple))
|
|
|
|
|
|
|
|
|
2014-02-11 05:53:47 +08:00
|
|
|
def _version_str_to_int(verstr):
|
|
|
|
if verstr is None:
|
|
|
|
return None
|
|
|
|
if verstr == 0:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
if verstr.count(".") != 2:
|
|
|
|
raise RuntimeError("programming error: version string '%s' needs "
|
|
|
|
"two '.' in it.")
|
|
|
|
|
|
|
|
return ((int(verstr.split(".")[0]) * 1000000) +
|
|
|
|
(int(verstr.split(".")[1]) * 1000) + (int(verstr.split(".")[2])))
|
|
|
|
|
|
|
|
|
2013-08-10 03:56:18 +08:00
|
|
|
class _SupportCheck(object):
|
|
|
|
"""
|
|
|
|
@version: Minimum libvirt version required for this feature. Not used
|
2014-02-11 06:12:24 +08:00
|
|
|
if 'args' provided.
|
|
|
|
|
2013-08-10 03:56:18 +08:00
|
|
|
@function: Function name to check exists. If object not specified,
|
2014-02-11 06:12:24 +08:00
|
|
|
function is checked against libvirt module. If run_args is specified,
|
|
|
|
this function will actually be called, so beware.
|
|
|
|
|
|
|
|
@run_args: Argument tuple to actually test 'function' with, and check
|
|
|
|
for an 'unsupported' error from libvirt.
|
|
|
|
|
2013-08-10 03:56:18 +08:00
|
|
|
@flag: A flag to check exists. This will be appended to the argument
|
2019-03-07 02:05:47 +08:00
|
|
|
:list if run_args are provided, otherwise we will only check against
|
2014-02-11 06:12:24 +08:00
|
|
|
that the flag is present in the python bindings.
|
|
|
|
|
|
|
|
@hv_version: A dictionary with hypervisor names for keys, and
|
|
|
|
hypervisor versions as values. This is for saying 'this feature
|
|
|
|
is only supported with qemu version 1.5.0' or similar. If the
|
|
|
|
version is 0, then perform no version check.
|
|
|
|
|
|
|
|
@hv_libvirt_version: Similar to hv_version, but this will check
|
|
|
|
the version of libvirt for a specific hv key. Use this to say
|
|
|
|
'this feature is supported with qemu and libvirt version 1.0.0,
|
|
|
|
and xen with libvirt version 1.1.0'
|
2013-08-10 03:56:18 +08:00
|
|
|
"""
|
|
|
|
def __init__(self,
|
2014-02-11 06:12:24 +08:00
|
|
|
function=None, run_args=None, flag=None,
|
|
|
|
version=None, hv_version=None, hv_libvirt_version=None):
|
2013-08-10 03:56:18 +08:00
|
|
|
self.function = function
|
2014-02-11 06:12:24 +08:00
|
|
|
self.run_args = run_args
|
2013-08-10 03:56:18 +08:00
|
|
|
self.flag = flag
|
2014-02-11 05:53:47 +08:00
|
|
|
self.version = version
|
2014-02-11 06:12:24 +08:00
|
|
|
self.hv_version = hv_version or {}
|
|
|
|
self.hv_libvirt_version = hv_libvirt_version or {}
|
2013-08-10 03:56:18 +08:00
|
|
|
|
2017-10-11 19:35:46 +08:00
|
|
|
versions = ([self.version] + list(self.hv_libvirt_version.values()))
|
2014-02-11 05:53:47 +08:00
|
|
|
for vstr in versions:
|
|
|
|
v = _version_str_to_int(vstr)
|
2019-03-07 02:05:47 +08:00
|
|
|
if vstr is not None and v != 0 and v < 7003:
|
2014-02-11 05:37:17 +08:00
|
|
|
raise RuntimeError("programming error: Cannot enforce "
|
2019-03-07 02:05:47 +08:00
|
|
|
"support checks for libvirt versions less than 0.7.3, "
|
2014-02-11 05:53:47 +08:00
|
|
|
"since required APIs were not available. ver=%s" % vstr)
|
2014-02-11 05:37:17 +08:00
|
|
|
|
2019-06-08 02:48:59 +08:00
|
|
|
def __call__(self, virtconn, data=None):
|
|
|
|
"""
|
|
|
|
Attempt to determine if a specific libvirt feature is support given
|
|
|
|
the passed connection.
|
|
|
|
|
|
|
|
:param virtconn: VirtinstConnection to check feature on
|
|
|
|
:param feature: Feature type to check support for
|
|
|
|
:type feature: One of the SUPPORT_* flags
|
|
|
|
:param data: Option libvirt object to use in feature checking
|
|
|
|
:type data: Could be virDomain, virNetwork, virStoragePool, hv name, etc
|
|
|
|
|
|
|
|
:returns: True if feature is supported, False otherwise
|
|
|
|
"""
|
|
|
|
if "VirtinstConnection" in repr(data):
|
|
|
|
data = data.get_conn_for_api_arg()
|
|
|
|
|
2014-02-11 06:12:24 +08:00
|
|
|
ret = _check_function(self.function, self.flag, self.run_args, data)
|
2014-02-11 06:02:22 +08:00
|
|
|
if ret is not None:
|
|
|
|
return ret
|
2013-08-10 03:56:18 +08:00
|
|
|
|
|
|
|
# Do this after the function check, since there's an ordering issue
|
2018-03-21 00:18:35 +08:00
|
|
|
# with VirtinstConnection
|
2019-06-08 02:48:59 +08:00
|
|
|
hv_type = virtconn.get_uri_driver()
|
|
|
|
actual_libvirt_version = virtconn.daemon_version()
|
|
|
|
actual_hv_version = virtconn.conn_version()
|
2013-08-10 03:56:18 +08:00
|
|
|
|
|
|
|
# Check that local libvirt version is sufficient
|
2017-10-11 19:35:54 +08:00
|
|
|
v = _version_str_to_int(self.version)
|
|
|
|
if v and (v > actual_libvirt_version):
|
2013-08-10 03:56:18 +08:00
|
|
|
return False
|
|
|
|
|
2014-02-11 06:12:24 +08:00
|
|
|
if self.hv_version:
|
|
|
|
if hv_type not in self.hv_version:
|
|
|
|
if "all" not in self.hv_version:
|
2014-02-11 05:14:39 +08:00
|
|
|
return False
|
2014-02-11 06:12:24 +08:00
|
|
|
elif (actual_hv_version <
|
|
|
|
_version_str_to_int(self.hv_version[hv_type])):
|
2013-08-10 03:56:18 +08:00
|
|
|
return False
|
|
|
|
|
2014-02-11 06:12:24 +08:00
|
|
|
if self.hv_libvirt_version:
|
|
|
|
if hv_type not in self.hv_libvirt_version:
|
2017-12-19 02:01:30 +08:00
|
|
|
if "all" not in self.hv_libvirt_version:
|
2014-02-11 05:14:39 +08:00
|
|
|
return False
|
2014-02-11 06:12:24 +08:00
|
|
|
elif (actual_libvirt_version <
|
|
|
|
_version_str_to_int(self.hv_libvirt_version[hv_type])):
|
2013-08-10 03:56:18 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2019-06-08 02:48:59 +08:00
|
|
|
def _make(*args, **kwargs):
|
|
|
|
"""
|
|
|
|
Create a _SupportCheck from the passed args, then turn it into a
|
|
|
|
SupportCache method which captures and caches the returned support
|
|
|
|
value in self._cache
|
|
|
|
"""
|
|
|
|
# pylint: disable=protected-access
|
|
|
|
support_obj = _SupportCheck(*args, **kwargs)
|
2013-08-10 03:56:18 +08:00
|
|
|
|
2019-06-08 02:48:59 +08:00
|
|
|
def cache_wrapper(self, data=None):
|
|
|
|
if support_obj not in self._cache:
|
|
|
|
support_ret = support_obj(self._virtconn, data or self._virtconn)
|
|
|
|
self._cache[support_obj] = support_ret
|
|
|
|
return self._cache[support_obj]
|
2013-08-10 03:56:18 +08:00
|
|
|
|
2019-06-08 02:48:59 +08:00
|
|
|
return cache_wrapper
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2018-02-14 20:17:31 +08:00
|
|
|
|
2019-06-08 02:45:25 +08:00
|
|
|
class SupportCache:
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
2019-06-08 02:48:59 +08:00
|
|
|
Class containing all support checks and access APIs, and support for
|
|
|
|
caching returned results
|
2016-01-27 09:00:38 +08:00
|
|
|
"""
|
2019-06-08 04:39:33 +08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_libvirt_error_no_domain(err):
|
|
|
|
"""
|
|
|
|
Small helper to check if the passed exception is a libvirt error
|
|
|
|
with code VIR_ERR_NO_DOMAIN
|
|
|
|
"""
|
|
|
|
if not isinstance(err, libvirt.libvirtError):
|
|
|
|
return False
|
|
|
|
return err.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN
|
|
|
|
|
|
|
|
|
2019-06-08 02:48:59 +08:00
|
|
|
def __init__(self, virtconn):
|
|
|
|
self._cache = {}
|
|
|
|
self._virtconn = virtconn
|
2019-06-08 02:45:25 +08:00
|
|
|
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_storage = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listStoragePools", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_nodedev = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listDevices", run_args=(None, 0))
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_network = _make(function="virConnect.listNetworks", run_args=())
|
|
|
|
conn_interface = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listInterfaces", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_stream = _make(function="virConnect.newStream", run_args=(0,))
|
|
|
|
conn_listalldomains = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listAllDomains", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_listallnetworks = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listAllNetworks", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_listallstoragepools = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listAllStoragePools", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_listallinterfaces = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listAllInterfaces", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_listalldevices = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.listAllDevices", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_working_xen_events = _make(hv_version={"xen": "4.0.0", "all": 0})
|
2019-06-08 02:45:25 +08:00
|
|
|
# This is an arbitrary check to say whether it's a good idea to
|
|
|
|
# default to qcow2. It might be fine for xen or qemu older than the versions
|
|
|
|
# here, but until someone tests things I'm going to be a bit conservative.
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_default_qcow2 = _make(hv_version={"qemu": "1.2.0", "test": 0})
|
|
|
|
conn_autosocket = _make(hv_libvirt_version={"qemu": "1.0.6"})
|
|
|
|
conn_pm_disable = _make(hv_version={"qemu": "1.2.0", "test": 0})
|
|
|
|
conn_qcow2_lazy_refcounts = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
version="1.1.0", hv_version={"qemu": "1.2.0", "test": 0})
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_hyperv_vapic = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
version="1.1.0", hv_version={"qemu": "1.1.0", "test": 0})
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_hyperv_clock = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
version="1.2.2", hv_version={"qemu": "1.5.3", "test": 0})
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_domain_capabilities = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virConnect.getDomainCapabilities",
|
|
|
|
run_args=(None, None, None, None))
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_domain_reset = _make(version="0.9.7", hv_version={"qemu": 0})
|
|
|
|
conn_vmport = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
version="1.2.16", hv_version={"qemu": "2.2.0", "test": 0})
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_mem_stats_period = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virDomain.setMemoryStatsPeriod",
|
|
|
|
version="1.1.1", hv_version={"qemu": 0})
|
|
|
|
# spice GL is actually enabled with libvirt 1.3.3, but 3.1.0 is the
|
|
|
|
# first version that sorts out the qemu:///system + cgroup issues
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_spice_gl = _make(version="3.1.0",
|
2019-06-08 02:45:25 +08:00
|
|
|
hv_version={"qemu": "2.6.0", "test": 0})
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_spice_rendernode = _make(version="3.1.0",
|
2019-06-08 02:45:25 +08:00
|
|
|
hv_version={"qemu": "2.9.0", "test": 0})
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_video_virtio_accel3d = _make(version="1.3.0",
|
2019-06-08 02:45:25 +08:00
|
|
|
hv_version={"qemu": "2.5.0", "test": 0})
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_graphics_listen_none = _make(version="2.0.0")
|
|
|
|
conn_rng_urandom = _make(version="1.3.4")
|
|
|
|
conn_usb3_ports = _make(version="1.3.5")
|
|
|
|
conn_machvirt_pci_default = _make(version="3.0.0")
|
|
|
|
conn_qemu_xhci = _make(version="3.3.0", hv_version={"qemu": "2.9.0"})
|
|
|
|
conn_vnc_none_auth = _make(hv_version={"qemu": "2.9.0"})
|
|
|
|
conn_device_boot_order = _make(hv_version={"qemu": 0, "test": 0})
|
|
|
|
conn_riscv_virt_pci_default = _make(version="5.3.0", hv_version={"qemu": "4.0.0"})
|
2019-06-08 02:45:25 +08:00
|
|
|
|
|
|
|
# We choose qemu 2.11.0 as the first version to target for q35 default.
|
|
|
|
# That's not really based on anything except reasonably modern at the
|
|
|
|
# time of these patches.
|
2019-06-08 02:48:59 +08:00
|
|
|
qemu_q35_default = _make(hv_version={"qemu": "2.11.0", "test": "0"})
|
2019-06-08 02:45:25 +08:00
|
|
|
|
|
|
|
# This is for disk <driver name=qemu>. xen supports this, but it's
|
|
|
|
# limited to arbitrary new enough xen, since I know libxl can handle it
|
|
|
|
# but I don't think the old xend driver does.
|
2019-06-08 02:48:59 +08:00
|
|
|
conn_disk_driver_name_qemu = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
hv_version={"qemu": 0, "xen": "4.2.0"},
|
|
|
|
hv_libvirt_version={"qemu": 0, "xen": "1.1.0"})
|
|
|
|
|
|
|
|
# Domain checks
|
2019-06-08 02:48:59 +08:00
|
|
|
domain_xml_inactive = _make(function="virDomain.XMLDesc", run_args=(),
|
2019-06-08 02:45:25 +08:00
|
|
|
flag="VIR_DOMAIN_XML_INACTIVE")
|
2019-06-08 02:48:59 +08:00
|
|
|
domain_xml_secure = _make(function="virDomain.XMLDesc", run_args=(),
|
2019-06-08 02:45:25 +08:00
|
|
|
flag="VIR_DOMAIN_XML_SECURE")
|
2019-06-08 02:48:59 +08:00
|
|
|
domain_managed_save = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virDomain.hasManagedSaveImage",
|
|
|
|
run_args=(0,))
|
2019-06-08 02:48:59 +08:00
|
|
|
domain_job_info = _make(function="virDomain.jobInfo", run_args=())
|
|
|
|
domain_list_snapshots = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virDomain.listAllSnapshots", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
domain_memory_stats = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virDomain.memoryStats", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
domain_state = _make(function="virDomain.state", run_args=())
|
|
|
|
domain_open_graphics = _make(function="virDomain.openGraphicsFD",
|
2019-06-08 02:45:25 +08:00
|
|
|
version="1.2.8", hv_version={"qemu": 0})
|
|
|
|
|
|
|
|
# Pool checks
|
2019-06-08 02:48:59 +08:00
|
|
|
pool_isactive = _make(function="virStoragePool.isActive", run_args=())
|
|
|
|
pool_listallvolumes = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
function="virStoragePool.listAllVolumes", run_args=())
|
2019-06-08 02:48:59 +08:00
|
|
|
pool_metadata_prealloc = _make(
|
2019-06-08 02:45:25 +08:00
|
|
|
flag="VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA",
|
|
|
|
version="1.0.1")
|
|
|
|
|
|
|
|
# Network checks
|
2019-06-08 02:48:59 +08:00
|
|
|
net_isactive = _make(function="virNetwork.isActive", run_args=())
|
2019-06-08 02:45:25 +08:00
|
|
|
|
|
|
|
|
2019-06-08 02:48:59 +08:00
|
|
|
def _check_version(self, version):
|
2019-06-08 02:45:25 +08:00
|
|
|
"""
|
|
|
|
Check libvirt version. Useful for the test suite so we don't need
|
|
|
|
to keep adding new support checks.
|
|
|
|
"""
|
|
|
|
sobj = _SupportCheck(version=version)
|
2019-06-08 02:48:59 +08:00
|
|
|
return sobj(self._virtconn, None)
|