mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-03 20:01:16 +00:00
de3289e2b5
There was one attempt a year ago done by me to drop HAL [1] but it was never resolved. There was another time when Dan suggested to drop HAL driver [2] but it was decided to keep it around in case device assignment will be implemented for FreeBSD and the fact that virt-manager uses node device driver [3]. I checked git history and code and it doesn't look like bhyve supports device assignment so from that POV it should not block removing HAL. The argument about virt-manager is not strong as well because libvirt installed from FreeBSD packages doesn't have HAL support so it will not affect these users as well [4]. The only users affected by this change would be the ones compiling libvirt from GIT on FreeBSD. I looked into alternatives and there is libudev-devd package on FreeBSD but unfortunately it doesn't work as it doesn't list any devices when used with libvirt. It provides libudev APIs using devd. I also looked into devd directly and it provides some APIs but there are no APIs for device monitoring and events so that would have to be somehow done by libvirt. Main motivation for dropping HAL support is to replace libdbus with GLib dbus implementation and it cannot be done with HAL driver present in libvirt because HAL APIs heavily depends on symbols provided by libdbus. [1] <https://www.redhat.com/archives/libvir-list/2019-May/msg00203.html> [2] <https://www.redhat.com/archives/libvir-list/2016-April/msg00992.html> [3] <https://www.redhat.com/archives/libvir-list/2016-April/msg00994.html> [4] <https://svnweb.freebsd.org/ports/head/devel/libvirt/Makefile?view=markup> Signed-off-by: Pavel Hrdina <phrdina@redhat.com> Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
502 lines
16 KiB
Python
Executable File
502 lines
16 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2011-2019 Red Hat, Inc.
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
#
|
|
# This library 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
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library. If not, see
|
|
# <http://www.gnu.org/licenses/>.
|
|
|
|
import sys
|
|
import os.path
|
|
import re
|
|
|
|
if len(sys.argv) != 3:
|
|
print("syntax: %s TOP-SRCDIR TOP-BUILDDIR\n" % sys.argv[0], file=sys.stderr)
|
|
|
|
srcdir = sys.argv[1]
|
|
builddir = sys.argv[2]
|
|
|
|
symslibvirt = os.path.join(srcdir, "src", "libvirt_public.syms")
|
|
symsqemu = os.path.join(srcdir, "src", "libvirt_qemu.syms")
|
|
symslxc = os.path.join(srcdir, "src", "libvirt_lxc.syms")
|
|
drivertablefiles = [
|
|
os.path.join(srcdir, "src", "driver-hypervisor.h"),
|
|
os.path.join(srcdir, "src", "driver-interface.h"),
|
|
os.path.join(srcdir, "src", "driver-network.h"),
|
|
os.path.join(srcdir, "src", "driver-nodedev.h"),
|
|
os.path.join(srcdir, "src", "driver-nwfilter.h"),
|
|
os.path.join(srcdir, "src", "driver-secret.h"),
|
|
os.path.join(srcdir, "src", "driver-state.h"),
|
|
os.path.join(srcdir, "src", "driver-storage.h"),
|
|
os.path.join(srcdir, "src", "driver-stream.h"),
|
|
]
|
|
|
|
groupheaders = {
|
|
"virHypervisorDriver": "Hypervisor APIs",
|
|
"virNetworkDriver": "Virtual Network APIs",
|
|
"virInterfaceDriver": "Host Interface APIs",
|
|
"virNodeDeviceDriver": "Host Device APIs",
|
|
"virStorageDriver": "Storage Pool APIs",
|
|
"virSecretDriver": "Secret APIs",
|
|
"virNWFilterDriver": "Network Filter APIs",
|
|
}
|
|
|
|
|
|
srcs = []
|
|
for root, dirs, files in os.walk(os.path.join(srcdir, "src")):
|
|
for file in files:
|
|
if ((file.endswith("driver.c") and
|
|
not file.endswith("vbox_driver.c")) or
|
|
file.endswith("common.c") or
|
|
file.endswith("tmpl.c") or
|
|
file.endswith("monitor.c") or
|
|
file.endswith("udev.c")):
|
|
srcs.append(os.path.join(root, file))
|
|
|
|
|
|
# Map API functions to the header and documentation files they're in
|
|
# so that we can generate proper hyperlinks to their documentation.
|
|
#
|
|
# The function names are grep'd from the XML output of apibuild.py.
|
|
def getAPIFilenames(filename):
|
|
files = {}
|
|
|
|
with open(filename) as fh:
|
|
for line in fh:
|
|
res = re.search(r"function name='([^']+)' file='([^']+)'", line)
|
|
if res is not None:
|
|
files[res.group(1)] = res.group(2)
|
|
|
|
if len(files) == 0:
|
|
raise Exception(("No functions found in %s. " +
|
|
"Has the apibuild.py output changed?") %
|
|
filename)
|
|
|
|
return files
|
|
|
|
|
|
def parseSymsFile(apisref, prefix, filename, xmlfilename):
|
|
vers = None
|
|
prevvers = None
|
|
|
|
filenames = getAPIFilenames(xmlfilename)
|
|
|
|
with open(filename) as fh:
|
|
for line in fh:
|
|
line = line.strip()
|
|
|
|
if line == "":
|
|
continue
|
|
if line[0] == '#':
|
|
continue
|
|
if line.startswith("global:"):
|
|
continue
|
|
if line.startswith("local:"):
|
|
continue
|
|
|
|
groupstartmatch = re.search(r"^\s*%s_(\d+\.\d+\.\d+)\s*{\s*$" %
|
|
prefix, line)
|
|
groupendmatch1 = re.search(r"^\s*}\s*;\s*$", line)
|
|
groupendmatch2 = re.search(r"^\s*}\s*%s_(\d+\.\d+\.\d+)\s*;\s*$" %
|
|
prefix, line)
|
|
symbolmatch = re.search(r"^\s*(\w+)\s*;\s*$", line)
|
|
if groupstartmatch is not None:
|
|
if vers is not None:
|
|
raise Exception("malformed syms file when starting group")
|
|
|
|
vers = groupstartmatch.group(1)
|
|
elif groupendmatch1 is not None:
|
|
if prevvers is not None:
|
|
raise Exception("malformed syms file when ending group")
|
|
|
|
prevvers = vers
|
|
vers = None
|
|
elif groupendmatch2 is not None:
|
|
if groupendmatch2.group(1) != prevvers:
|
|
raise Exception(("malformed syms file %s != %s " +
|
|
"when ending group") %
|
|
(groupendmatch2.group(1), prevvers))
|
|
|
|
prevvers = vers
|
|
vers = None
|
|
elif symbolmatch is not None:
|
|
name = symbolmatch.group(1)
|
|
apisref[name] = {
|
|
"vers": vers,
|
|
"file": filenames.get(name),
|
|
}
|
|
else:
|
|
raise Exception("unexpected data %s" % line)
|
|
|
|
|
|
apis = {}
|
|
# Get the list of all public APIs and their corresponding version
|
|
parseSymsFile(apis, "LIBVIRT", symslibvirt,
|
|
os.path.join(builddir, "docs", "libvirt-api.xml"))
|
|
|
|
# And the same for the QEMU specific APIs
|
|
parseSymsFile(apis, "LIBVIRT_QEMU", symsqemu,
|
|
os.path.join(builddir, "docs", "libvirt-qemu-api.xml"))
|
|
|
|
# And the same for the LXC specific APIs
|
|
parseSymsFile(apis, "LIBVIRT_LXC", symslxc,
|
|
os.path.join(builddir, "docs", "libvirt-lxc-api.xml"))
|
|
|
|
|
|
# Some special things which aren't public APIs,
|
|
# but we want to report
|
|
apis["virConnectSupportsFeature"] = {
|
|
"vers": "0.3.2"
|
|
}
|
|
apis["virDomainMigratePrepare"] = {
|
|
"vers": "0.3.2"
|
|
}
|
|
apis["virDomainMigratePerform"] = {
|
|
"vers": "0.3.2"
|
|
}
|
|
apis["virDomainMigrateFinish"] = {
|
|
"vers": "0.3.2"
|
|
}
|
|
apis["virDomainMigratePrepare2"] = {
|
|
"vers": "0.5.0"
|
|
}
|
|
apis["virDomainMigrateFinish2"] = {
|
|
"vers": "0.5.0"
|
|
}
|
|
apis["virDomainMigratePrepareTunnel"] = {
|
|
"vers": "0.7.2"
|
|
}
|
|
|
|
apis["virDomainMigrateBegin3"] = {
|
|
"vers": "0.9.2"
|
|
}
|
|
apis["virDomainMigratePrepare3"] = {
|
|
"vers": "0.9.2"
|
|
}
|
|
apis["virDomainMigratePrepareTunnel3"] = {
|
|
"vers": "0.9.2"
|
|
}
|
|
apis["virDomainMigratePerform3"] = {
|
|
"vers": "0.9.2"
|
|
}
|
|
apis["virDomainMigrateFinish3"] = {
|
|
"vers": "0.9.2"
|
|
}
|
|
apis["virDomainMigrateConfirm3"] = {
|
|
"vers": "0.9.2"
|
|
}
|
|
|
|
apis["virDomainMigrateBegin3Params"] = {
|
|
"vers": "1.1.0"
|
|
}
|
|
apis["virDomainMigratePrepare3Params"] = {
|
|
"vers": "1.1.0"
|
|
}
|
|
apis["virDomainMigratePrepareTunnel3Params"] = {
|
|
"vers": "1.1.0"
|
|
}
|
|
apis["virDomainMigratePerform3Params"] = {
|
|
"vers": "1.1.0"
|
|
}
|
|
apis["virDomainMigrateFinish3Params"] = {
|
|
"vers": "1.1.0"
|
|
}
|
|
apis["virDomainMigrateConfirm3Params"] = {
|
|
"vers": "1.1.0"
|
|
}
|
|
|
|
|
|
# Now we want to get the mapping between public APIs
|
|
# and driver struct fields. This lets us later match
|
|
# update the driver impls with the public APis.
|
|
|
|
# Group name -> hash of APIs { fields -> api name }
|
|
groups = {}
|
|
ingrp = None
|
|
for drivertablefile in drivertablefiles:
|
|
with open(drivertablefile) as fh:
|
|
for line in fh:
|
|
starttablematch = re.search(r"struct _(vir\w*Driver)", line)
|
|
if starttablematch is not None:
|
|
grp = starttablematch.group(1)
|
|
if grp != "virStateDriver" and grp != "virStreamDriver":
|
|
ingrp = grp
|
|
groups[ingrp] = {
|
|
"apis": {},
|
|
"drivers": {}
|
|
}
|
|
elif ingrp is not None:
|
|
callbackmatch = re.search(r"^\s*vir(?:Drv)(\w+)\s+(\w+);\s*$",
|
|
line)
|
|
if callbackmatch is not None:
|
|
name = callbackmatch.group(1)
|
|
field = callbackmatch.group(2)
|
|
|
|
api = "vir" + name
|
|
if api in apis:
|
|
groups[ingrp]["apis"][field] = api
|
|
elif re.search(r"\w+(Open|Close|URIProbe)", api) is not None:
|
|
continue
|
|
else:
|
|
raise Exception(("driver %s does not have " +
|
|
"a public API") % name)
|
|
elif re.search(r"};", line):
|
|
ingrp = None
|
|
|
|
|
|
# Finally, we read all the primary driver files and extract
|
|
# the driver API tables from each one.
|
|
|
|
for src in srcs:
|
|
with open(src) as fh:
|
|
groupsre = "|".join(groups.keys())
|
|
|
|
ingrp = None
|
|
impl = None
|
|
for line in fh:
|
|
if ingrp is None:
|
|
m = re.search(r"^\s*(static\s+)?(" +
|
|
groupsre +
|
|
r")\s+(\w+)\s*=\s*{", line)
|
|
if m is None:
|
|
m = re.search(r"^\s*(static\s+)?(" +
|
|
groupsre +
|
|
r")\s+NAME\(\w+\)\s*=\s*{", line)
|
|
if m is not None:
|
|
ingrp = m.group(2)
|
|
impl = src
|
|
|
|
implmatch = re.search(r".*/node_device_(\w+)\.c", impl)
|
|
if implmatch is None:
|
|
implmatch = re.search(r".*/(\w+?)_((\w+)_)?(\w+)\.c", impl)
|
|
if implmatch is None:
|
|
raise Exception("Unexpected impl format '%s'" % impl)
|
|
impl = implmatch.group(1)
|
|
|
|
if impl in groups[ingrp]["drivers"]:
|
|
raise Exception(
|
|
"Group %s already contains %s" % (ingrp, impl))
|
|
|
|
groups[ingrp]["drivers"][impl] = {}
|
|
else:
|
|
callbackmatch = re.search(r"\s*\.(\w+)\s*=\s*(\w+)\s*,?\s*" +
|
|
r"(?:/\*\s*(\d+\.\d+\.\d+)\s*" +
|
|
r"(?:-\s*(\d+\.\d+\.\d+))?\s*\*/\s*)?$",
|
|
line)
|
|
if callbackmatch is not None:
|
|
api = callbackmatch.group(1)
|
|
meth = callbackmatch.group(2)
|
|
vers = callbackmatch.group(3)
|
|
deleted = callbackmatch.group(4)
|
|
|
|
if api == "no" or api == "name":
|
|
continue
|
|
|
|
if meth == "NULL" and deleted is None:
|
|
raise Exception(
|
|
("Method impl for %s is NULL, but " +
|
|
"no deleted version is provided") % api)
|
|
|
|
if meth != "NULL" and deleted is not None:
|
|
raise Exception(
|
|
("Method impl for %s is non-NULL, but " +
|
|
"deleted version is provided") % api)
|
|
|
|
if vers is None and api != "connectURIProbe":
|
|
raise Exception(
|
|
"Method %s in %s is missing version" %
|
|
(meth, src))
|
|
|
|
if api not in groups[ingrp]["apis"]:
|
|
if re.search(r"\w+(Open|Close|URIProbe)", api):
|
|
continue
|
|
|
|
raise Exception("Found unexpected method " +
|
|
"%s in %s" % (api, ingrp))
|
|
|
|
groups[ingrp]["drivers"][impl][api] = {
|
|
"vers": vers,
|
|
"deleted": deleted,
|
|
}
|
|
|
|
if (api == "domainMigratePrepare" or
|
|
api == "domainMigratePrepare2" or
|
|
api == "domainMigratePrepare3"):
|
|
if ("domainMigrate" not in
|
|
groups[ingrp]["drivers"][impl]):
|
|
groups[ingrp]["drivers"][impl]["domainMigrate"] = {
|
|
"vers": vers,
|
|
}
|
|
elif line.find("}") != -1:
|
|
ingrp = None
|
|
|
|
|
|
# The '.open' driver method is used for 3 public APIs, so we
|
|
# have a bit of manual fixup todo with the per-driver versioning
|
|
# and support matrix
|
|
|
|
groups["virHypervisorDriver"]["apis"]["openAuth"] = \
|
|
"virConnectOpenAuth"
|
|
groups["virHypervisorDriver"]["apis"]["openReadOnly"] = \
|
|
"virConnectOpenReadOnly"
|
|
groups["virHypervisorDriver"]["apis"]["domainMigrate"] = \
|
|
"virDomainMigrate"
|
|
|
|
openAuthVers = (0 * 1000 * 1000) + (4 * 1000) + 0
|
|
|
|
drivers = groups["virHypervisorDriver"]["drivers"]
|
|
for drv in drivers.keys():
|
|
openVersStr = drivers[drv]["connectOpen"]["vers"]
|
|
openVers = 0
|
|
if openVersStr != "Y":
|
|
openVersBits = openVersStr.split(".")
|
|
if len(openVersBits) != 3:
|
|
raise Exception("Expected 3 digit version for %s" % openVersStr)
|
|
openVers = ((int(openVersBits[0]) * 1000 * 1000) +
|
|
(int(openVersBits[1]) * 1000) +
|
|
int(openVersBits[2]))
|
|
|
|
# virConnectOpenReadOnly always matches virConnectOpen version
|
|
drivers[drv]["connectOpenReadOnly"] = \
|
|
drivers[drv]["connectOpen"]
|
|
|
|
# virConnectOpenAuth is always 0.4.0 if the driver existed
|
|
# before this time, otherwise it matches the version of
|
|
# the driver's virConnectOpen entry
|
|
if openVersStr == "Y" or openVers >= openAuthVers:
|
|
vers = openVersStr
|
|
else:
|
|
vers = "0.4.0"
|
|
drivers[drv]["connectOpenAuth"] = {
|
|
"vers": vers,
|
|
}
|
|
|
|
|
|
# Another special case for the virDomainCreateLinux which was replaced
|
|
# with virDomainCreateXML
|
|
groups["virHypervisorDriver"]["apis"]["domainCreateLinux"] = \
|
|
"virDomainCreateLinux"
|
|
|
|
createAPIVers = (0 * 1000 * 1000) + (0 * 1000) + 3
|
|
|
|
for drv in drivers.keys():
|
|
if "domainCreateXML" not in drivers[drv]:
|
|
continue
|
|
createVersStr = drivers[drv]["domainCreateXML"]["vers"]
|
|
createVers = 0
|
|
if createVersStr != "Y":
|
|
createVersBits = createVersStr.split(".")
|
|
if len(createVersBits) != 3:
|
|
raise Exception("Expected 3 digit version for %s" % createVersStr)
|
|
createVers = ((int(createVersBits[0]) * 1000 * 1000) +
|
|
(int(createVersBits[1]) * 1000) +
|
|
int(createVersBits[2]))
|
|
|
|
# virCreateLinux is always 0.0.3 if the driver existed
|
|
# before this time, otherwise it matches the version of
|
|
# the driver's virCreateXML entry
|
|
if createVersStr == "Y" or createVers >= createAPIVers:
|
|
vers = createVersStr
|
|
else:
|
|
vers = "0.0.3"
|
|
|
|
drivers[drv]["domainCreateLinux"] = {
|
|
"vers": vers,
|
|
}
|
|
|
|
|
|
# Finally we generate the HTML file with the tables
|
|
|
|
print('''<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE html>
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<body class="hvsupport">
|
|
<h1>libvirt API support matrix</h1>
|
|
|
|
<ul id="toc"></ul>
|
|
|
|
<p>
|
|
This page documents which <a href="html/">libvirt calls</a> work on
|
|
which libvirt drivers / hypervisors, and which version the API appeared
|
|
in. If a hypervisor driver later dropped support for the API, the version
|
|
when it was removed is also mentioned (highlighted in
|
|
<span class="removedhv">dark red</span>).
|
|
</p>
|
|
''')
|
|
|
|
for grp in sorted(groups.keys()):
|
|
print("<h2><a id=\"%s\">%s</a></h2>" % (grp, groupheaders[grp]))
|
|
print('''<table class="top_table">
|
|
<thead>
|
|
<tr>
|
|
<th>API</th>
|
|
<th>Version</th>''')
|
|
|
|
for drv in sorted(groups[grp]["drivers"].keys()):
|
|
print(" <th>%s</th>" % drv)
|
|
|
|
print('''</tr>
|
|
</thead>
|
|
<tbody>''')
|
|
|
|
row = 0
|
|
|
|
def sortkey(field):
|
|
return groups[grp]["apis"][field]
|
|
|
|
for field in sorted(groups[grp]["apis"].keys(), key=sortkey):
|
|
api = groups[grp]["apis"][field]
|
|
vers = apis[api]["vers"]
|
|
htmlgrp = apis[api].get("file")
|
|
print("<tr>")
|
|
|
|
if htmlgrp is not None:
|
|
print(('''<td>\n<a href=\"html/libvirt-%s.html#%s\">''' +
|
|
'''%s</a>\n</td>''') %
|
|
(htmlgrp, api, api))
|
|
else:
|
|
print("<td>\n%s</td>" % api)
|
|
|
|
print("<td>%s</td>" % vers)
|
|
|
|
for drv in sorted(groups[grp]["drivers"].keys()):
|
|
info = ""
|
|
if field in groups[grp]["drivers"][drv]:
|
|
vers = groups[grp]["drivers"][drv][field]["vers"]
|
|
if vers is not None:
|
|
info = info + vers
|
|
|
|
deleted = groups[grp]["drivers"][drv][field].get("deleted")
|
|
if deleted is not None:
|
|
info = info + (''' - <span class="removedhv">''' +
|
|
'''%s</span>''' % deleted)
|
|
|
|
print("<td>%s</td>" % info)
|
|
|
|
print("</tr>")
|
|
|
|
row = row + 1
|
|
if (row % 15) == 0:
|
|
print('''<tr>
|
|
<th>API</th>
|
|
<th>Version</th>''')
|
|
|
|
for drv in sorted(groups[grp]["drivers"].keys()):
|
|
print(" <th>%s</th>" % drv)
|
|
|
|
print("</tr>")
|
|
|
|
print("</tbody>\n</table>")
|
|
|
|
print("</body>\n</html>")
|