[libvirt] [libvirt-python PATCH 0/2] Python bindings for IOThreads

Going along with the recent libvirt IOThreads API additions, here's the Python patches... These would only be submitted once the libvirt code goes in... See the following for the libvirt patches: http://www.redhat.com/archives/libvir-list/2015-February/msg00593.html Reviewer's NOTE: - Alternatively to the error from patch 1, I could modify the code to not fail on <= 0, but rather just < 0 with the result being the following for a domain without IOThreads configured: $ python iothr.py [] $ Instead of: Traceback (most recent call last): File "iothr.py", line 6, in <module> print dom2.getIOThreadsInfo() File "/usr/lib64/python2.7/site-packages/libvirt.py", line 1197, in getIOThreadsInfo if ret is None: raise libvirtError ('virDomainGetIOThreadsInfo() failed', dom=self) libvirt.libvirtError: virDomainGetIOThreadsInfo() failed John Ferlan (2): Support virDomainGetIOThreadsInfo and virDomainIOThreadsInfoFree Support virDomainSetIOThreads generator.py | 6 ++ libvirt-override-api.xml | 14 ++++ libvirt-override.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++ sanitytest.py | 3 + 4 files changed, 186 insertions(+) -- 2.1.0

Add support for the libvirt_virDomainGetIOThreadsInfo method. This code mostly follows the libvirt_virDomainGetVcpuPinInfo method, but also takes some from the libvirt_virNodeGetCPUMap method with respect to building the cpumap into the returned tuple rather than two separate tuples which vcpu pinning generates Assuming two domains, one with IOThreads defined (eg, 'iothr-gst') and one without ('noiothr-gst'), execute the following in an 'iothr.py' file: import libvirt con=libvirt.open("qemu:///system") dom=con.lookupByName('iothr-gst') print dom.getIOThreadsInfo() dom2=con.lookupByName('noiothr-gst') print dom2.getIOThreadsInfo() $ python iothr.py [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [True, False, False, False], [])] Traceback (most recent call last): File "iothr.py", line 6, in <module> print dom2.getIOThreadsInfo() File "/usr/lib64/python2.7/site-packages/libvirt.py", line 1197, in getIOThreadsInfo if ret is None: raise libvirtError ('virDomainGetIOThreadsInfo() failed', dom=self) libvirt.libvirtError: virDomainGetIOThreadsInfo() failed $ Signed-off-by: John Ferlan <jferlan@redhat.com> --- generator.py | 5 +++ libvirt-override-api.xml | 6 +++ libvirt-override.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ sanitytest.py | 3 ++ 4 files changed, 110 insertions(+) diff --git a/generator.py b/generator.py index 0d48980..327c896 100755 --- a/generator.py +++ b/generator.py @@ -435,6 +435,7 @@ skip_impl = ( 'virDomainGetVcpuPinInfo', 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', + 'virDomainGetIOThreadsInfo', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', @@ -592,6 +593,7 @@ skip_function = ( 'virNetworkDHCPLeaseFree', # only useful in C, python code uses list 'virDomainStatsRecordListFree', # only useful in C, python uses dict 'virDomainFSInfoFree', # only useful in C, python code uses list + 'virDomainIOThreadsInfoFree', # only useful in C, python code uses list ) lxc_skip_function = ( @@ -1144,6 +1146,9 @@ def nameFixup(name, classe, type, file): elif name[0:20] == "virDomainGetCPUStats": func = name[9:] func = func[0:1].lower() + func[1:] + elif name[0:25] == "virDomainGetIOThreadsInfo": + func = name[9:] + func = func[0:1].lower() + func[1:] elif name[0:18] == "virDomainGetFSInfo": func = name[12:] func = func[0:2].lower() + func[2:] diff --git a/libvirt-override-api.xml b/libvirt-override-api.xml index 439cb40..e512311 100644 --- a/libvirt-override-api.xml +++ b/libvirt-override-api.xml @@ -278,6 +278,12 @@ <arg name='cpumap' type='unsigned char *' info='pointer to a bit map of real CPUs (in 8-bit bytes) (IN) Each bit set to 1 means that corresponding CPU is usable. Bytes are stored in little-endian order: CPU0-7, 8-15... In each byte, lowest CPU number is least significant bit.'/> <arg name='flags' type='int' info='flags to specify'/> </function> + <function name='virDomainGetIOThreadsInfo' file='python'> + <info>Query the CPU affinity setting of the IOThreads of the domain</info> + <arg name='domain' type='virDomainPtr' info='pointer to domain object, or NULL for Domain0'/> + <arg name='flags' type='int' info='an OR'ed set of virDomainModificationImpact'/> + <return type='char *' info="list of IOThreads information including the iothread_id, the cpumap, the cpumap length, number of associated resources, and a list of each resource assigned to the iothread_id."/> + </function> <function name='virDomainSetSchedulerParameters' file='python'> <info>Change the scheduler parameters</info> <return type='int' info='-1 in case of error, 0 in case of success.'/> diff --git a/libvirt-override.c b/libvirt-override.c index 88cb527..17f3bf3 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -1990,6 +1990,99 @@ libvirt_virDomainGetEmulatorPinInfo(PyObject *self ATTRIBUTE_UNUSED, } #endif /* LIBVIR_CHECK_VERSION(0, 10, 0) */ +#if LIBVIR_CHECK_VERSION(1, 2, 13) +static PyObject * +libvirt_virDomainGetIOThreadsInfo(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) { + virDomainPtr domain; + PyObject *pyobj_domain; + PyObject *py_retval = NULL; + PyObject *iothrtpl = NULL; + PyObject *iothrmap = NULL; + PyObject *resources = NULL; + virDomainIOThreadsInfoPtr *iothrinfo = NULL; + unsigned int flags; + size_t pcpu, i, j; + int niothreads, cpunum; + + if (!PyArg_ParseTuple(args, (char *)"OI:virDomainGetIOThreadsInfo", + &pyobj_domain, &flags)) + return NULL; + domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain); + + if ((cpunum = getPyNodeCPUCount(virDomainGetConnect(domain))) < 0) + return VIR_PY_NONE; + + LIBVIRT_BEGIN_ALLOW_THREADS; + niothreads = virDomainGetIOThreadsInfo(domain, &iothrinfo, flags); + LIBVIRT_END_ALLOW_THREADS; + /* On error or when there's no IOThreads, we return VIR_PY_NONE */ + if (niothreads <= 0) + goto cleanup; + + /* convert to a Python list */ + if ((py_retval = PyList_New(niothreads)) == NULL) + goto cleanup; + + for (i = 0; i < niothreads; i++) { + virDomainIOThreadsInfoPtr iothr = iothrinfo[i]; + + if (iothr == NULL) + goto cleanup; + if ((iothrtpl = PyTuple_New(3)) == NULL) + goto cleanup; + PyList_SetItem(py_retval, i, iothrtpl); + + /* 0: IOThread ID */ + PyTuple_SetItem(iothrtpl, 0, libvirt_uintWrap(iothr->iothread_id)); + + /* 1: CPU map */ + if ((iothrmap = PyList_New(cpunum)) == NULL) + goto cleanup; + + for (pcpu = 0; pcpu < cpunum; pcpu++) { + PyObject *pyused; + if ((pyused = PyBool_FromLong(VIR_CPU_USED(iothr->cpumap, + pcpu))) == NULL) + goto cleanup; + if (PyList_SetItem(iothrmap, pcpu, pyused) < 0) { + Py_XDECREF(pyused); + goto cleanup; + } + } + if (PyTuple_SetItem(iothrtpl, 1, iothrmap) < 0) + goto cleanup; + + /* 2: Resources - may be empty */ + if ((resources = PyList_New(0)) == NULL) + goto cleanup; + + PyTuple_SetItem(iothrtpl, 2, resources); + for (j = 0; j < iothr->nresources; j++) { + if (PyList_Append(resources, + libvirt_constcharPtrWrap(iothr->resources[j])) < 0) + goto cleanup; + } + } + + for (i = 0; i < niothreads; i++) + virDomainIOThreadsInfoFree(iothrinfo[i]); + VIR_FREE(iothrinfo); + + return py_retval; + +cleanup: + for (i = 0; i < niothreads; i++) + virDomainIOThreadsInfoFree(iothrinfo[i]); + VIR_FREE(iothrinfo); + Py_XDECREF(py_retval); + Py_XDECREF(iothrtpl); + Py_XDECREF(iothrmap); + Py_XDECREF(resources); + return VIR_PY_NONE; +} + +#endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */ /************************************************************************ * * @@ -8483,6 +8576,9 @@ static PyMethodDef libvirtMethods[] = { {(char *) "virDomainGetEmulatorPinInfo", libvirt_virDomainGetEmulatorPinInfo, METH_VARARGS, NULL}, {(char *) "virDomainPinEmulator", libvirt_virDomainPinEmulator, METH_VARARGS, NULL}, #endif /* LIBVIR_CHECK_VERSION(0, 10, 0) */ +#if LIBVIR_CHECK_VERSION(1, 2, 13) + {(char *) "virDomainGetIOThreadsInfo", libvirt_virDomainGetIOThreadsInfo, METH_VARARGS, NULL}, +#endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */ {(char *) "virConnectListStoragePools", libvirt_virConnectListStoragePools, METH_VARARGS, NULL}, {(char *) "virConnectListDefinedStoragePools", libvirt_virConnectListDefinedStoragePools, METH_VARARGS, NULL}, #if LIBVIR_CHECK_VERSION(0, 10, 2) diff --git a/sanitytest.py b/sanitytest.py index f021e5a..53209b8 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -142,6 +142,9 @@ for cname in wantfunctions: if name[0:19] == "virDomainFSInfoFree": continue + if name[0:26] == "virDomainIOThreadsInfoFree": + continue + if name[0:21] == "virDomainListGetStats": name = "virConnectDomainListGetStats" -- 2.1.0

On Thu, Feb 19, 2015 at 07:59:38AM -0500, John Ferlan wrote:
Add support for the libvirt_virDomainGetIOThreadsInfo method. This code mostly follows the libvirt_virDomainGetVcpuPinInfo method, but also takes some from the libvirt_virNodeGetCPUMap method with respect to building the cpumap into the returned tuple rather than two separate tuples which vcpu pinning generates
Assuming two domains, one with IOThreads defined (eg, 'iothr-gst') and one without ('noiothr-gst'), execute the following in an 'iothr.py' file:
import libvirt con=libvirt.open("qemu:///system") dom=con.lookupByName('iothr-gst') print dom.getIOThreadsInfo() dom2=con.lookupByName('noiothr-gst') print dom2.getIOThreadsInfo()
$ python iothr.py [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [True, False, False, False], [])] Traceback (most recent call last): File "iothr.py", line 6, in <module> print dom2.getIOThreadsInfo() File "/usr/lib64/python2.7/site-packages/libvirt.py", line 1197, in getIOThreadsInfo if ret is None: raise libvirtError ('virDomainGetIOThreadsInfo() failed', dom=self) libvirt.libvirtError: virDomainGetIOThreadsInfo() failed
$
Signed-off-by: John Ferlan <jferlan@redhat.com> --- generator.py | 5 +++ libvirt-override-api.xml | 6 +++ libvirt-override.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ sanitytest.py | 3 ++ 4 files changed, 110 insertions(+)
diff --git a/generator.py b/generator.py index 0d48980..327c896 100755 --- a/generator.py +++ b/generator.py @@ -435,6 +435,7 @@ skip_impl = ( 'virDomainGetVcpuPinInfo', 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', + 'virDomainGetIOThreadsInfo', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', @@ -592,6 +593,7 @@ skip_function = ( 'virNetworkDHCPLeaseFree', # only useful in C, python code uses list 'virDomainStatsRecordListFree', # only useful in C, python uses dict 'virDomainFSInfoFree', # only useful in C, python code uses list + 'virDomainIOThreadsInfoFree', # only useful in C, python code uses list )
lxc_skip_function = ( @@ -1144,6 +1146,9 @@ def nameFixup(name, classe, type, file): elif name[0:20] == "virDomainGetCPUStats": func = name[9:] func = func[0:1].lower() + func[1:] + elif name[0:25] == "virDomainGetIOThreadsInfo": + func = name[9:] + func = func[0:1].lower() + func[1:] elif name[0:18] == "virDomainGetFSInfo": func = name[12:] func = func[0:2].lower() + func[2:] diff --git a/libvirt-override-api.xml b/libvirt-override-api.xml index 439cb40..e512311 100644 --- a/libvirt-override-api.xml +++ b/libvirt-override-api.xml @@ -278,6 +278,12 @@ <arg name='cpumap' type='unsigned char *' info='pointer to a bit map of real CPUs (in 8-bit bytes) (IN) Each bit set to 1 means that corresponding CPU is usable. Bytes are stored in little-endian order: CPU0-7, 8-15... In each byte, lowest CPU number is least significant bit.'/> <arg name='flags' type='int' info='flags to specify'/> </function> + <function name='virDomainGetIOThreadsInfo' file='python'> + <info>Query the CPU affinity setting of the IOThreads of the domain</info> + <arg name='domain' type='virDomainPtr' info='pointer to domain object, or NULL for Domain0'/> + <arg name='flags' type='int' info='an OR'ed set of virDomainModificationImpact'/> + <return type='char *' info="list of IOThreads information including the iothread_id, the cpumap, the cpumap length, number of associated resources, and a list of each resource assigned to the iothread_id."/> + </function> <function name='virDomainSetSchedulerParameters' file='python'> <info>Change the scheduler parameters</info> <return type='int' info='-1 in case of error, 0 in case of success.'/> diff --git a/libvirt-override.c b/libvirt-override.c index 88cb527..17f3bf3 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -1990,6 +1990,99 @@ libvirt_virDomainGetEmulatorPinInfo(PyObject *self ATTRIBUTE_UNUSED, } #endif /* LIBVIR_CHECK_VERSION(0, 10, 0) */
+#if LIBVIR_CHECK_VERSION(1, 2, 13) +static PyObject * +libvirt_virDomainGetIOThreadsInfo(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) { + virDomainPtr domain; + PyObject *pyobj_domain; + PyObject *py_retval = NULL; + PyObject *iothrtpl = NULL; + PyObject *iothrmap = NULL; + PyObject *resources = NULL; + virDomainIOThreadsInfoPtr *iothrinfo = NULL; + unsigned int flags; + size_t pcpu, i, j; + int niothreads, cpunum; + + if (!PyArg_ParseTuple(args, (char *)"OI:virDomainGetIOThreadsInfo", + &pyobj_domain, &flags)) + return NULL; + domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain); + + if ((cpunum = getPyNodeCPUCount(virDomainGetConnect(domain))) < 0) + return VIR_PY_NONE; + + LIBVIRT_BEGIN_ALLOW_THREADS; + niothreads = virDomainGetIOThreadsInfo(domain, &iothrinfo, flags); + LIBVIRT_END_ALLOW_THREADS; + /* On error or when there's no IOThreads, we return VIR_PY_NONE */ + if (niothreads <= 0) + goto cleanup;
I'm not sure whether we should raise an exception if there are no iothreads defined for domain.
+ + /* convert to a Python list */ + if ((py_retval = PyList_New(niothreads)) == NULL) + goto cleanup;
PyList_New will returns NULL in case of error and sets an exception. In the case the exception is set we should also return NULL instead of None.
+ + for (i = 0; i < niothreads; i++) { + virDomainIOThreadsInfoPtr iothr = iothrinfo[i]; + + if (iothr == NULL) + goto cleanup; + if ((iothrtpl = PyTuple_New(3)) == NULL) + goto cleanup; + PyList_SetItem(py_retval, i, iothrtpl);
PyList_SetItem steals reference to iothrtpl object. In the cleanup section you call PY_XDECREF on py_retval which will correctly free the list, but also all the items inside the list including the iothrtpl tuple. Then you call PY_XDECREF on iothrtpl and it tries to free it again -> double_free. The common work-flow is: if (list = PyList_New()) goto cleanup; if (tuple = PyTuple_New()) goto cleanup; if (PyTuple_SetItem()) goto cleanup; if (PyList_SetItem(list, tuple)) goto cleanup; tuple = NULL; cleanup: PY_XDECREF(list); PY_XDECREF(tuple);
+ + /* 0: IOThread ID */ + PyTuple_SetItem(iothrtpl, 0, libvirt_uintWrap(iothr->iothread_id)); + + /* 1: CPU map */ + if ((iothrmap = PyList_New(cpunum)) == NULL) + goto cleanup; + + for (pcpu = 0; pcpu < cpunum; pcpu++) { + PyObject *pyused; + if ((pyused = PyBool_FromLong(VIR_CPU_USED(iothr->cpumap, + pcpu))) == NULL) + goto cleanup;
This one doesn't set an error thus it's correct to return VIR_PY_NONE.
+ if (PyList_SetItem(iothrmap, pcpu, pyused) < 0) { + Py_XDECREF(pyused); + goto cleanup;
The PyList_SetItem also sets in case of error an exception. The rule is if you sets an exception you should return NULL.
+ } + } + if (PyTuple_SetItem(iothrtpl, 1, iothrmap) < 0) + goto cleanup; + + /* 2: Resources - may be empty */ + if ((resources = PyList_New(0)) == NULL) + goto cleanup; + + PyTuple_SetItem(iothrtpl, 2, resources); + for (j = 0; j < iothr->nresources; j++) { + if (PyList_Append(resources, + libvirt_constcharPtrWrap(iothr->resources[j])) < 0) + goto cleanup; + } + } + + for (i = 0; i < niothreads; i++) + virDomainIOThreadsInfoFree(iothrinfo[i]); + VIR_FREE(iothrinfo); + + return py_retval; + +cleanup: + for (i = 0; i < niothreads; i++) + virDomainIOThreadsInfoFree(iothrinfo[i]); + VIR_FREE(iothrinfo); + Py_XDECREF(py_retval); + Py_XDECREF(iothrtpl); + Py_XDECREF(iothrmap); + Py_XDECREF(resources); + return VIR_PY_NONE; +} + +#endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */
/************************************************************************ * * @@ -8483,6 +8576,9 @@ static PyMethodDef libvirtMethods[] = { {(char *) "virDomainGetEmulatorPinInfo", libvirt_virDomainGetEmulatorPinInfo, METH_VARARGS, NULL}, {(char *) "virDomainPinEmulator", libvirt_virDomainPinEmulator, METH_VARARGS, NULL}, #endif /* LIBVIR_CHECK_VERSION(0, 10, 0) */ +#if LIBVIR_CHECK_VERSION(1, 2, 13) + {(char *) "virDomainGetIOThreadsInfo", libvirt_virDomainGetIOThreadsInfo, METH_VARARGS, NULL}, +#endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */ {(char *) "virConnectListStoragePools", libvirt_virConnectListStoragePools, METH_VARARGS, NULL}, {(char *) "virConnectListDefinedStoragePools", libvirt_virConnectListDefinedStoragePools, METH_VARARGS, NULL}, #if LIBVIR_CHECK_VERSION(0, 10, 2) diff --git a/sanitytest.py b/sanitytest.py index f021e5a..53209b8 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -142,6 +142,9 @@ for cname in wantfunctions: if name[0:19] == "virDomainFSInfoFree": continue
+ if name[0:26] == "virDomainIOThreadsInfoFree": + continue + if name[0:21] == "virDomainListGetStats": name = "virConnectDomainListGetStats"
-- 2.1.0
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On Thu, Feb 19, 2015 at 07:59:38AM -0500, John Ferlan wrote:
Add support for the libvirt_virDomainGetIOThreadsInfo method. This code mostly follows the libvirt_virDomainGetVcpuPinInfo method, but also takes some from the libvirt_virNodeGetCPUMap method with respect to building the cpumap into the returned tuple rather than two separate tuples which vcpu pinning generates
Assuming two domains, one with IOThreads defined (eg, 'iothr-gst') and one without ('noiothr-gst'), execute the following in an 'iothr.py' file:
import libvirt con=libvirt.open("qemu:///system") dom=con.lookupByName('iothr-gst') print dom.getIOThreadsInfo() dom2=con.lookupByName('noiothr-gst') print dom2.getIOThreadsInfo()
$ python iothr.py [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [True, False, False, False], [])] Traceback (most recent call last): File "iothr.py", line 6, in <module> print dom2.getIOThreadsInfo() File "/usr/lib64/python2.7/site-packages/libvirt.py", line 1197, in getIOThreadsInfo if ret is None: raise libvirtError ('virDomainGetIOThreadsInfo() failed', dom=self) libvirt.libvirtError: virDomainGetIOThreadsInfo() failed
$
Signed-off-by: John Ferlan <jferlan@redhat.com> --- generator.py | 5 +++ libvirt-override-api.xml | 6 +++ libvirt-override.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ sanitytest.py | 3 ++ 4 files changed, 110 insertions(+)
diff --git a/generator.py b/generator.py index 0d48980..327c896 100755 --- a/generator.py +++ b/generator.py @@ -435,6 +435,7 @@ skip_impl = ( 'virDomainGetVcpuPinInfo', 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', + 'virDomainGetIOThreadsInfo', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', @@ -592,6 +593,7 @@ skip_function = ( 'virNetworkDHCPLeaseFree', # only useful in C, python code uses list 'virDomainStatsRecordListFree', # only useful in C, python uses dict 'virDomainFSInfoFree', # only useful in C, python code uses list + 'virDomainIOThreadsInfoFree', # only useful in C, python code uses list )
lxc_skip_function = ( @@ -1144,6 +1146,9 @@ def nameFixup(name, classe, type, file): elif name[0:20] == "virDomainGetCPUStats": func = name[9:] func = func[0:1].lower() + func[1:] + elif name[0:25] == "virDomainGetIOThreadsInfo": + func = name[9:] + func = func[0:1].lower() + func[1:] elif name[0:18] == "virDomainGetFSInfo": func = name[12:] func = func[0:2].lower() + func[2:] diff --git a/sanitytest.py b/sanitytest.py index f021e5a..53209b8 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -142,6 +142,9 @@ for cname in wantfunctions: if name[0:19] == "virDomainFSInfoFree": continue
+ if name[0:26] == "virDomainIOThreadsInfoFree": + continue + if name[0:21] == "virDomainListGetStats": name = "virConnectDomainListGetStats"
-- 2.1.0
One more think, I would also squashed this patch in to make the test happy and also in Python binding we usually remove the "Get" from APIs. diff --git a/generator.py b/generator.py index c79acf1..aa140ed 100755 --- a/generator.py +++ b/generator.py @@ -1148,8 +1148,8 @@ def nameFixup(name, classe, type, file): func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:25] == "virDomainGetIOThreadsInfo": - func = name[9:] - func = func[0:1].lower() + func[1:] + func = name[12:] + func = func[0:2].lower() + func[2:] elif name[0:18] == "virDomainGetFSInfo": func = name[12:] func = func[0:2].lower() + func[2:] diff --git a/sanitytest.py b/sanitytest.py index 53209b8..0e6e0e5 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -279,6 +279,8 @@ for name in sorted(basicklassmap): func = "nwfilter" + func[8:] if func[0:8] == "fSFreeze" or func[0:6] == "fSThaw" or func[0:6] == "fSInfo": func = "fs" + func[2:] + if func[0:13] == "iOThreadsInfo": + func = "ioThreadsInfo" if klass == "virNetwork": func = func.replace("dHCP", "DHCP") Pavel
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 02/20/2015 12:42 PM, Pavel Hrdina wrote:
On Thu, Feb 19, 2015 at 07:59:38AM -0500, John Ferlan wrote:
Add support for the libvirt_virDomainGetIOThreadsInfo method. This code mostly follows the libvirt_virDomainGetVcpuPinInfo method, but also takes some from the libvirt_virNodeGetCPUMap method with respect to building the cpumap into the returned tuple rather than two separate tuples which vcpu pinning generates
Assuming two domains, one with IOThreads defined (eg, 'iothr-gst') and one without ('noiothr-gst'), execute the following in an 'iothr.py' file:
import libvirt con=libvirt.open("qemu:///system") dom=con.lookupByName('iothr-gst') print dom.getIOThreadsInfo() dom2=con.lookupByName('noiothr-gst') print dom2.getIOThreadsInfo()
$ python iothr.py [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [True, False, False, False], [])] Traceback (most recent call last): File "iothr.py", line 6, in <module> print dom2.getIOThreadsInfo() File "/usr/lib64/python2.7/site-packages/libvirt.py", line 1197, in getIOThreadsInfo if ret is None: raise libvirtError ('virDomainGetIOThreadsInfo() failed', dom=self) libvirt.libvirtError: virDomainGetIOThreadsInfo() failed
$
Signed-off-by: John Ferlan <jferlan@redhat.com> --- generator.py | 5 +++ libvirt-override-api.xml | 6 +++ libvirt-override.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ sanitytest.py | 3 ++ 4 files changed, 110 insertions(+)
diff --git a/generator.py b/generator.py index 0d48980..327c896 100755 --- a/generator.py +++ b/generator.py @@ -435,6 +435,7 @@ skip_impl = ( 'virDomainGetVcpuPinInfo', 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', + 'virDomainGetIOThreadsInfo', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', @@ -592,6 +593,7 @@ skip_function = ( 'virNetworkDHCPLeaseFree', # only useful in C, python code uses list 'virDomainStatsRecordListFree', # only useful in C, python uses dict 'virDomainFSInfoFree', # only useful in C, python code uses list + 'virDomainIOThreadsInfoFree', # only useful in C, python code uses list )
lxc_skip_function = ( @@ -1144,6 +1146,9 @@ def nameFixup(name, classe, type, file): elif name[0:20] == "virDomainGetCPUStats": func = name[9:] func = func[0:1].lower() + func[1:] + elif name[0:25] == "virDomainGetIOThreadsInfo": + func = name[9:] + func = func[0:1].lower() + func[1:] elif name[0:18] == "virDomainGetFSInfo": func = name[12:] func = func[0:2].lower() + func[2:] diff --git a/sanitytest.py b/sanitytest.py index f021e5a..53209b8 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -142,6 +142,9 @@ for cname in wantfunctions: if name[0:19] == "virDomainFSInfoFree": continue
+ if name[0:26] == "virDomainIOThreadsInfoFree": + continue + if name[0:21] == "virDomainListGetStats": name = "virConnectDomainListGetStats"
-- 2.1.0
One more think, I would also squashed this patch in to make the test happy and also in Python binding we usually remove the "Get" from APIs.
We we do have some, but I'm OK with changing things...
diff --git a/generator.py b/generator.py index c79acf1..aa140ed 100755 --- a/generator.py +++ b/generator.py @@ -1148,8 +1148,8 @@ def nameFixup(name, classe, type, file): func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:25] == "virDomainGetIOThreadsInfo": - func = name[9:] - func = func[0:1].lower() + func[1:] + func = name[12:] + func = func[0:2].lower() + func[2:] elif name[0:18] == "virDomainGetFSInfo": func = name[12:] func = func[0:2].lower() + func[2:] diff --git a/sanitytest.py b/sanitytest.py index 53209b8..0e6e0e5 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -279,6 +279,8 @@ for name in sorted(basicklassmap): func = "nwfilter" + func[8:] if func[0:8] == "fSFreeze" or func[0:6] == "fSThaw" or func[0:6] == "fSInfo": func = "fs" + func[2:] + if func[0:13] == "iOThreadsInfo": + func = "ioThreadsInfo"
if klass == "virNetwork": func = func.replace("dHCP", "DHCP")
OK - so we'll have 'dom.ioThreadsInfo" here and in patch 2 it'll be "dom.setIOThreads unless you think that should change too. I'll repost the series - it cannot go in until the libvirt series gets in, but at least it'll be ready. Other changes... ... I'll return an empty list if there are zero IOThreads rather than error ... Obviously it wasn't very clear to me how the PyList* interactions work exactly w/r/t Py_XDECREF - I was trying to copy other uses, but didn't do a great job at that. I was just happy that I got results that worked Thanks for the review John

On 02/20/2015 12:15 PM, John Ferlan wrote:
... Obviously it wasn't very clear to me how the PyList* interactions work exactly w/r/t Py_XDECREF - I was trying to copy other uses, but didn't do a great job at that. I was just happy that I got results that worked
Don't feel too bad; we have LOTS of bad examples that could use someone doing a full-blown audit and cleanup. It's just that it would be mind-numbing work, that no one seems to want to volunteer for. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

Support the libvirt_virDomainSetIOThreads method using code that mimics the existing libvirt_virDomainPinVcpuFlags method The following is a sample session assuming guest 'iothr-gst' has IOThreads configured (it's currently running, too)
import libvirt con=libvirt.open("qemu:///system") dom=con.lookupByName('iothr-gst') dom.getIOThreadsInfo() [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [False, False, False, True], [])] cpumap=(True,True,True,False) dom.setIOThreads(3,cpumap) 0 print dom.getIOThreadsInfo() [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [True, True, True, False], [])]
Signed-off-by: John Ferlan <jferlan@redhat.com> --- generator.py | 1 + libvirt-override-api.xml | 8 ++++++ libvirt-override.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/generator.py b/generator.py index 327c896..c79acf1 100755 --- a/generator.py +++ b/generator.py @@ -436,6 +436,7 @@ skip_impl = ( 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', 'virDomainGetIOThreadsInfo', + 'virDomainSetIOThreads', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', diff --git a/libvirt-override-api.xml b/libvirt-override-api.xml index e512311..19667b6 100644 --- a/libvirt-override-api.xml +++ b/libvirt-override-api.xml @@ -284,6 +284,14 @@ <arg name='flags' type='int' info='an OR'ed set of virDomainModificationImpact'/> <return type='char *' info="list of IOThreads information including the iothread_id, the cpumap, the cpumap length, number of associated resources, and a list of each resource assigned to the iothread_id."/> </function> + <function name='virDomainSetIOThreads' file='python'> + <info>Dynamically change the real CPUs which can be allocated to an IOThread. This function requires privileged access to the hypervisor.</info> + <return type='int' info='0 in case of success, -1 in case of failure.'/> + <arg name='domain' type='virDomainPtr' info='pointer to domain object'/> + <arg name='iothread_val' type='unsigned int' info='iothread_id number'/> + <arg name='cpumap' type='unsigned char *' info='pointer to a bit map of real CPUs (in 8-bit bytes) (IN) Each bit set to 1 means that corresponding CPU is usable. Bytes are stored in little-endian order: CPU0-7, 8-15... In each byte, lowest CPU number is least significant bit.'/> + <arg name='flags' type='int' info='an OR'ed set of virDomainIOThreadsFlags'/> + </function> <function name='virDomainSetSchedulerParameters' file='python'> <info>Change the scheduler parameters</info> <return type='int' info='-1 in case of error, 0 in case of success.'/> diff --git a/libvirt-override.c b/libvirt-override.c index 17f3bf3..f9af5a9 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -2082,6 +2082,72 @@ cleanup: return VIR_PY_NONE; } +static PyObject * +libvirt_virDomainSetIOThreads(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + virDomainPtr domain; + PyObject *pyobj_domain, *pycpumap; + PyObject *ret = NULL; + unsigned char *cpumap; + int cpumaplen, iothread_val, tuple_size, cpunum; + size_t i; + unsigned int flags; + int i_retval; + + if (!PyArg_ParseTuple(args, (char *)"OiOI:virDomainSetIOThread", + &pyobj_domain, &iothread_val, &pycpumap, &flags)) + return NULL; + domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain); + + if ((cpunum = getPyNodeCPUCount(virDomainGetConnect(domain))) < 0) + return VIR_PY_INT_FAIL; + + if (PyTuple_Check(pycpumap)) { + tuple_size = PyTuple_Size(pycpumap); + if (tuple_size == -1) + return ret; + } else { + PyErr_SetString(PyExc_TypeError, "Unexpected type, tuple is required"); + return ret; + } + + cpumaplen = VIR_CPU_MAPLEN(cpunum); + if (VIR_ALLOC_N(cpumap, cpumaplen) < 0) + return PyErr_NoMemory(); + + for (i = 0; i < tuple_size; i++) { + PyObject *flag = PyTuple_GetItem(pycpumap, i); + bool b; + + if (!flag || libvirt_boolUnwrap(flag, &b) < 0) + goto cleanup; + + if (b) + VIR_USE_CPU(cpumap, i); + else + VIR_UNUSE_CPU(cpumap, i); + } + + for (; i < cpunum; i++) + VIR_UNUSE_CPU(cpumap, i); + + flags |= VIR_DOMAIN_IOTHREADS_PIN; + LIBVIRT_BEGIN_ALLOW_THREADS; + i_retval = virDomainSetIOThreads(domain, iothread_val, + cpumap, cpumaplen, flags); + LIBVIRT_END_ALLOW_THREADS; + if (i_retval < 0) { + ret = VIR_PY_INT_FAIL; + goto cleanup; + } + ret = VIR_PY_INT_SUCCESS; + +cleanup: + VIR_FREE(cpumap); + return ret; +} + #endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */ /************************************************************************ @@ -8578,6 +8644,7 @@ static PyMethodDef libvirtMethods[] = { #endif /* LIBVIR_CHECK_VERSION(0, 10, 0) */ #if LIBVIR_CHECK_VERSION(1, 2, 13) {(char *) "virDomainGetIOThreadsInfo", libvirt_virDomainGetIOThreadsInfo, METH_VARARGS, NULL}, + {(char *) "virDomainSetIOThreads", libvirt_virDomainSetIOThreads, METH_VARARGS, NULL}, #endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */ {(char *) "virConnectListStoragePools", libvirt_virConnectListStoragePools, METH_VARARGS, NULL}, {(char *) "virConnectListDefinedStoragePools", libvirt_virConnectListDefinedStoragePools, METH_VARARGS, NULL}, -- 2.1.0

On Thu, Feb 19, 2015 at 07:59:39AM -0500, John Ferlan wrote:
Support the libvirt_virDomainSetIOThreads method using code that mimics the existing libvirt_virDomainPinVcpuFlags method
The following is a sample session assuming guest 'iothr-gst' has IOThreads configured (it's currently running, too)
import libvirt con=libvirt.open("qemu:///system") dom=con.lookupByName('iothr-gst') dom.getIOThreadsInfo() [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [False, False, False, True], [])] cpumap=(True,True,True,False) dom.setIOThreads(3,cpumap) 0 print dom.getIOThreadsInfo() [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [True, True, True, False], [])]
Signed-off-by: John Ferlan <jferlan@redhat.com> --- generator.py | 1 + libvirt-override-api.xml | 8 ++++++ libvirt-override.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+)
diff --git a/generator.py b/generator.py index 327c896..c79acf1 100755 --- a/generator.py +++ b/generator.py @@ -436,6 +436,7 @@ skip_impl = ( 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', 'virDomainGetIOThreadsInfo', + 'virDomainSetIOThreads', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', diff --git a/libvirt-override-api.xml b/libvirt-override-api.xml index e512311..19667b6 100644 --- a/libvirt-override-api.xml +++ b/libvirt-override-api.xml @@ -284,6 +284,14 @@ <arg name='flags' type='int' info='an OR'ed set of virDomainModificationImpact'/> <return type='char *' info="list of IOThreads information including the iothread_id, the cpumap, the cpumap length, number of associated resources, and a list of each resource assigned to the iothread_id."/> </function> + <function name='virDomainSetIOThreads' file='python'> + <info>Dynamically change the real CPUs which can be allocated to an IOThread. This function requires privileged access to the hypervisor.</info> + <return type='int' info='0 in case of success, -1 in case of failure.'/> + <arg name='domain' type='virDomainPtr' info='pointer to domain object'/> + <arg name='iothread_val' type='unsigned int' info='iothread_id number'/> + <arg name='cpumap' type='unsigned char *' info='pointer to a bit map of real CPUs (in 8-bit bytes) (IN) Each bit set to 1 means that corresponding CPU is usable. Bytes are stored in little-endian order: CPU0-7, 8-15... In each byte, lowest CPU number is least significant bit.'/> + <arg name='flags' type='int' info='an OR'ed set of virDomainIOThreadsFlags'/> + </function> <function name='virDomainSetSchedulerParameters' file='python'> <info>Change the scheduler parameters</info> <return type='int' info='-1 in case of error, 0 in case of success.'/> diff --git a/libvirt-override.c b/libvirt-override.c index 17f3bf3..f9af5a9 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -2082,6 +2082,72 @@ cleanup: return VIR_PY_NONE; }
+static PyObject * +libvirt_virDomainSetIOThreads(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + virDomainPtr domain; + PyObject *pyobj_domain, *pycpumap; + PyObject *ret = NULL; + unsigned char *cpumap; + int cpumaplen, iothread_val, tuple_size, cpunum; + size_t i; + unsigned int flags; + int i_retval; + + if (!PyArg_ParseTuple(args, (char *)"OiOI:virDomainSetIOThread", + &pyobj_domain, &iothread_val, &pycpumap, &flags)) + return NULL; + domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain); + + if ((cpunum = getPyNodeCPUCount(virDomainGetConnect(domain))) < 0) + return VIR_PY_INT_FAIL; + + if (PyTuple_Check(pycpumap)) { + tuple_size = PyTuple_Size(pycpumap); + if (tuple_size == -1)
This could be merged into one line as the condition before. And returning NULL is correct because the PyTuple_Size sets an exception in case of failure.
+ return ret; + } else { + PyErr_SetString(PyExc_TypeError, "Unexpected type, tuple is required"); + return ret; + } + + cpumaplen = VIR_CPU_MAPLEN(cpunum); + if (VIR_ALLOC_N(cpumap, cpumaplen) < 0) + return PyErr_NoMemory(); + + for (i = 0; i < tuple_size; i++) { + PyObject *flag = PyTuple_GetItem(pycpumap, i); + bool b; + + if (!flag || libvirt_boolUnwrap(flag, &b) < 0) + goto cleanup; + + if (b) + VIR_USE_CPU(cpumap, i); + else + VIR_UNUSE_CPU(cpumap, i); + } + + for (; i < cpunum; i++) + VIR_UNUSE_CPU(cpumap, i); + + flags |= VIR_DOMAIN_IOTHREADS_PIN; + LIBVIRT_BEGIN_ALLOW_THREADS; + i_retval = virDomainSetIOThreads(domain, iothread_val, + cpumap, cpumaplen, flags); + LIBVIRT_END_ALLOW_THREADS; + if (i_retval < 0) { + ret = VIR_PY_INT_FAIL; + goto cleanup; + } + ret = VIR_PY_INT_SUCCESS; + +cleanup: + VIR_FREE(cpumap); + return ret; +} + #endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */
/************************************************************************ @@ -8578,6 +8644,7 @@ static PyMethodDef libvirtMethods[] = { #endif /* LIBVIR_CHECK_VERSION(0, 10, 0) */ #if LIBVIR_CHECK_VERSION(1, 2, 13) {(char *) "virDomainGetIOThreadsInfo", libvirt_virDomainGetIOThreadsInfo, METH_VARARGS, NULL}, + {(char *) "virDomainSetIOThreads", libvirt_virDomainSetIOThreads, METH_VARARGS, NULL}, #endif /* LIBVIR_CHECK_VERSION(1, 2, 13) */ {(char *) "virConnectListStoragePools", libvirt_virConnectListStoragePools, METH_VARARGS, NULL}, {(char *) "virConnectListDefinedStoragePools", libvirt_virConnectListDefinedStoragePools, METH_VARARGS, NULL}, -- 2.1.0
ACK with the change proposed above. Pavel
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On Thu, Feb 19, 2015 at 07:59:39AM -0500, John Ferlan wrote:
Support the libvirt_virDomainSetIOThreads method using code that mimics the existing libvirt_virDomainPinVcpuFlags method
The following is a sample session assuming guest 'iothr-gst' has IOThreads configured (it's currently running, too)
import libvirt con=libvirt.open("qemu:///system") dom=con.lookupByName('iothr-gst') dom.getIOThreadsInfo() [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [False, False, False, True], [])] cpumap=(True,True,True,False) dom.setIOThreads(3,cpumap) 0 print dom.getIOThreadsInfo() [(1, [False, False, True, False], ['/home/vm-images/iothr-vol1']), (2, [False, False, False, True], ['/home/vm-images/iothr-vol2']), (3, [True, True, True, False], [])]
Signed-off-by: John Ferlan <jferlan@redhat.com> --- generator.py | 1 + libvirt-override-api.xml | 8 ++++++ libvirt-override.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+)
diff --git a/generator.py b/generator.py index 327c896..c79acf1 100755 --- a/generator.py +++ b/generator.py @@ -436,6 +436,7 @@ skip_impl = ( 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', 'virDomainGetIOThreadsInfo', + 'virDomainSetIOThreads', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', diff --git a/libvirt-override-api.xml b/libvirt-override-api.xml index e512311..19667b6 100644 --- a/libvirt-override-api.xml +++ b/libvirt-override-api.xml @@ -284,6 +284,14 @@ <arg name='flags' type='int' info='an OR'ed set of virDomainModificationImpact'/> <return type='char *' info="list of IOThreads information including the iothread_id, the cpumap, the cpumap length, number of associated resources, and a list of each resource assigned to the iothread_id."/> </function> + <function name='virDomainSetIOThreads' file='python'> + <info>Dynamically change the real CPUs which can be allocated to an IOThread. This function requires privileged access to the hypervisor.</info> + <return type='int' info='0 in case of success, -1 in case of failure.'/> + <arg name='domain' type='virDomainPtr' info='pointer to domain object'/> + <arg name='iothread_val' type='unsigned int' info='iothread_id number'/> + <arg name='cpumap' type='unsigned char *' info='pointer to a bit map of real CPUs (in 8-bit bytes) (IN) Each bit set to 1 means that corresponding CPU is usable. Bytes are stored in little-endian order: CPU0-7, 8-15... In each byte, lowest CPU number is least significant bit.'/> + <arg name='flags' type='int' info='an OR'ed set of virDomainIOThreadsFlags'/> + </function> <function name='virDomainSetSchedulerParameters' file='python'> <info>Change the scheduler parameters</info> <return type='int' info='-1 in case of error, 0 in case of success.'/> diff --git a/libvirt-override.c b/libvirt-override.c index 17f3bf3..f9af5a9 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -2082,6 +2082,72 @@ cleanup: return VIR_PY_NONE; }
+static PyObject * +libvirt_virDomainSetIOThreads(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + virDomainPtr domain; + PyObject *pyobj_domain, *pycpumap; + PyObject *ret = NULL; + unsigned char *cpumap; + int cpumaplen, iothread_val, tuple_size, cpunum; + size_t i; + unsigned int flags; + int i_retval; + + if (!PyArg_ParseTuple(args, (char *)"OiOI:virDomainSetIOThread", + &pyobj_domain, &iothread_val, &pycpumap, &flags)) + return NULL; + domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain); + + if ((cpunum = getPyNodeCPUCount(virDomainGetConnect(domain))) < 0) + return VIR_PY_INT_FAIL; + + if (PyTuple_Check(pycpumap)) { + tuple_size = PyTuple_Size(pycpumap); + if (tuple_size == -1) + return ret; + } else { + PyErr_SetString(PyExc_TypeError, "Unexpected type, tuple is required"); + return ret; + } + + cpumaplen = VIR_CPU_MAPLEN(cpunum); + if (VIR_ALLOC_N(cpumap, cpumaplen) < 0) + return PyErr_NoMemory(); + + for (i = 0; i < tuple_size; i++) { + PyObject *flag = PyTuple_GetItem(pycpumap, i); + bool b; + + if (!flag || libvirt_boolUnwrap(flag, &b) < 0) + goto cleanup; + + if (b) + VIR_USE_CPU(cpumap, i); + else + VIR_UNUSE_CPU(cpumap, i); + } + + for (; i < cpunum; i++) + VIR_UNUSE_CPU(cpumap, i); + + flags |= VIR_DOMAIN_IOTHREADS_PIN;
This hardcoding of flags in the python API is a great example of why it is unsatisfactory to overload multiplex different operations into one API, instead of just defining separate APis for each.
+ LIBVIRT_BEGIN_ALLOW_THREADS; + i_retval = virDomainSetIOThreads(domain, iothread_val, + cpumap, cpumaplen, flags);
Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (4)
-
Daniel P. Berrange
-
Eric Blake
-
John Ferlan
-
Pavel Hrdina