[libvirt] [PATCH v4 3/9] pvs: add functions to list domains and get info

Dmitry Guryanov dguryanov at parallels.com
Fri Apr 20 15:49:12 UTC 2012


PVS driver is 'stateless', like vmware or openvz drivers.
It collects information about domains during startup using
command-line utility prlctl. VMs in PVS identified by UUIDs
or unique names, which can be used as respective fields in
virDomainDef structure. Currently only basic info, like
description, virtual cpus number and memory amount implemented.
Quering devices information will be added in the next patches.

PVS does't support non-persistent domains - you can't run
a domain having only disk image, it must always be registered
in system.

Functions for quering domain info have been just copied from
test driver with some changes - they extract needed data from
previouly created list of virDomainObj objects.

changes in v4:
    * fix indent in preprocessor directives in pvs_driver.h
    * add pvs_driver.c to POTFILES.in

Signed-off-by: Dmitry Guryanov <dguryanov at parallels.com>
---
 po/POTFILES.in       |    1 +
 src/Makefile.am      |    3 +-
 src/pvs/pvs_driver.c |  516 +++++++++++++++++++++++++++++++++++++++++++++++++-
 src/pvs/pvs_driver.h |   14 ++
 src/pvs/pvs_utils.c  |  101 ++++++++++
 5 files changed, 633 insertions(+), 2 deletions(-)
 create mode 100644 src/pvs/pvs_utils.c

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4c49200..fb70cf7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -164,6 +164,7 @@ src/xenapi/xenapi_driver.c
 src/xenapi/xenapi_utils.c
 src/xenxs/xen_sxpr.c
 src/xenxs/xen_xm.c
+src/pvs/pvs_driver.c
 tools/console.c
 tools/libvirt-guests.init.sh
 tools/virsh.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 3cbd385..bc9efcf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -467,7 +467,8 @@ HYPERV_DRIVER_EXTRA_DIST =							\
 
 PVS_DRIVER_SOURCES =								\
 		pvs/pvs_driver.h						\
-		pvs/pvs_driver.c
+		pvs/pvs_driver.c						\
+		pvs/pvs_utils.c
 
 NETWORK_DRIVER_SOURCES =					\
 		network/bridge_driver.h network/bridge_driver.c
diff --git a/src/pvs/pvs_driver.c b/src/pvs/pvs_driver.c
index 3e48a76..736aa55 100644
--- a/src/pvs/pvs_driver.c
+++ b/src/pvs/pvs_driver.c
@@ -50,12 +50,13 @@
 #include "configmake.h"
 #include "storage_file.h"
 #include "nodeinfo.h"
-#include "json.h"
+#include "domain_conf.h"
 
 #include "pvs_driver.h"
 
 #define VIR_FROM_THIS VIR_FROM_PVS
 
+void pvsFreeDomObj(void *p);
 static virCapsPtr pvsBuildCapabilities(void);
 static int pvsClose(virConnectPtr conn);
 
@@ -77,6 +78,14 @@ pvsDefaultConsoleType(const char *ostype ATTRIBUTE_UNUSED)
     return VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL;
 }
 
+void
+pvsFreeDomObj(void *p)
+{
+    pvsDomObjPtr pdom = (pvsDomObjPtr) p;
+
+    VIR_FREE(pdom);
+};
+
 static virCapsPtr
 pvsBuildCapabilities(void)
 {
@@ -125,6 +134,218 @@ pvsGetCapabilities(virConnectPtr conn)
     return xml;
 }
 
+/*
+ * Must be called with privconn->lock held
+ */
+static virDomainObjPtr
+pvsLoadDomain(pvsConnPtr privconn, virJSONValuePtr jobj)
+{
+    virDomainObjPtr dom = NULL;
+    virDomainDefPtr def = NULL;
+    pvsDomObjPtr pdom = NULL;
+    virJSONValuePtr jobj2, jobj3;
+    const char *tmp;
+    char *endptr;
+    unsigned long mem;
+    unsigned int x;
+
+    if (VIR_ALLOC(def) < 0)
+        goto no_memory;
+
+    def->virtType = VIR_DOMAIN_VIRT_PVS;
+    def->id = -1;
+
+    tmp = virJSONValueObjectGetString(jobj, "Name");
+    if (!tmp) {
+        pvsParseError();
+        goto cleanup;
+    }
+    if (!(def->name = strdup(tmp)))
+        goto no_memory;
+
+    tmp = virJSONValueObjectGetString(jobj, "ID");
+    if (!tmp) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    if (virUUIDParse(tmp, def->uuid) < 0) {
+        pvsError(VIR_ERR_INTERNAL_ERROR, "%s",
+                 _("UUID in config file malformed"));
+        goto cleanup;
+    }
+
+    tmp = virJSONValueObjectGetString(jobj, "Description");
+    if (!tmp) {
+        pvsParseError();
+        goto cleanup;
+    }
+    if (!(def->description = strdup(tmp)))
+        goto no_memory;
+
+    jobj2 = virJSONValueObjectGet(jobj, "Hardware");
+    if (!jobj2) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    jobj3 = virJSONValueObjectGet(jobj2, "cpu");
+    if (!jobj3) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    if (virJSONValueObjectGetNumberUint(jobj3, "cpus", &x) < 0) {
+        pvsParseError();
+        goto cleanup;
+    }
+    def->vcpus = x;
+    def->maxvcpus = x;
+
+    jobj3 = virJSONValueObjectGet(jobj2, "memory");
+    if (!jobj3) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    tmp = virJSONValueObjectGetString(jobj3, "size");
+
+    if (virStrToLong_ul(tmp, &endptr, 10, &mem) < 0) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    if (!STREQ(endptr, "Mb")) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    def->mem.max_balloon = mem;
+    def->mem.max_balloon <<= 10;
+    def->mem.cur_balloon = def->mem.max_balloon;
+
+    if (!(def->os.type = strdup("hvm")))
+        goto no_memory;
+
+    if (!(def->os.init = strdup("/sbin/init")))
+        goto no_memory;
+
+    if (!(dom = virDomainAssignDef(privconn->caps,
+                                   &privconn->domains, def, false)))
+        goto cleanup;
+    /* dom is locked here */
+
+    if (VIR_ALLOC(pdom) < 0)
+        goto no_memory_unlock;
+    dom->privateDataFreeFunc = pvsFreeDomObj;
+    dom->privateData = pdom;
+
+    if (virJSONValueObjectGetNumberUint(jobj, "EnvID", &x) < 0)
+        goto cleanup_unlock;
+    pdom->id = x;
+    tmp = virJSONValueObjectGetString(jobj, "ID");
+    if (!tmp) {
+        pvsParseError();
+        goto cleanup_unlock;
+    }
+    if (!(pdom->uuid = strdup(tmp)))
+        goto no_memory_unlock;
+
+    tmp = virJSONValueObjectGetString(jobj, "OS");
+    if (!tmp)
+        goto cleanup_unlock;
+    if (!(pdom->os = strdup(tmp)))
+        goto no_memory_unlock;
+
+    dom->persistent = 1;
+
+    tmp = virJSONValueObjectGetString(jobj, "State");
+    if (!tmp) {
+        pvsParseError();
+        goto cleanup_unlock;
+    }
+
+    /* TODO: handle all possible states */
+    if (STREQ(tmp, "running")) {
+        virDomainObjSetState(dom, VIR_DOMAIN_RUNNING,
+                             VIR_DOMAIN_RUNNING_BOOTED);
+        def->id = pdom->id;
+    }
+
+    tmp = virJSONValueObjectGetString(jobj, "Autostart");
+    if (!tmp) {
+        pvsParseError();
+        goto cleanup_unlock;
+    }
+    if (STREQ(tmp, "on"))
+        dom->autostart = 1;
+    else
+        dom->autostart = 0;
+
+    virDomainObjUnlock(dom);
+
+    return dom;
+
+  no_memory_unlock:
+    virReportOOMError();
+  cleanup_unlock:
+    virDomainObjUnlock(dom);
+    /* domain list was locked, so nobody could get 'dom'. It has only
+     * one reference and virDomainObjUnref return 0 here */
+    if (virDomainObjUnref(dom))
+        pvsError(VIR_ERR_INTERNAL_ERROR, _("Can't free virDomainObj"));
+    return NULL;
+  no_memory:
+    virReportOOMError();
+  cleanup:
+    virDomainDefFree(def);
+    return NULL;
+}
+
+/*
+ * Must be called with privconn->lock held
+ */
+static int
+pvsLoadDomains(pvsConnPtr privconn, const char *domain_name)
+{
+    int count, i;
+    virJSONValuePtr jobj;
+    virJSONValuePtr jobj2;
+    virDomainObjPtr dom = NULL;
+    int ret = -1;
+
+    jobj = pvsParseOutput(PRLCTL, "list", "-j", "-a",
+                          "-i", "-H", domain_name, NULL);
+    if (!jobj) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    count = virJSONValueArraySize(jobj);
+    if (count < 1) {
+        pvsParseError();
+        goto cleanup;
+    }
+
+    for (i = 0; i < count; i++) {
+        jobj2 = virJSONValueArrayGet(jobj, i);
+        if (!jobj2) {
+            pvsParseError();
+            goto cleanup;
+        }
+
+        dom = pvsLoadDomain(privconn, jobj2);
+        if (!dom)
+            goto cleanup;
+    }
+
+    ret = 0;
+
+  cleanup:
+    virJSONValueFree(jobj);
+    return ret;
+}
+
 static int
 pvsOpenDefault(virConnectPtr conn)
 {
@@ -150,6 +371,9 @@ pvsOpenDefault(virConnectPtr conn)
     if (virDomainObjListInit(&privconn->domains) < 0)
         goto error;
 
+    if (pvsLoadDomains(privconn, NULL))
+        goto error;
+
     return VIR_DRV_OPEN_SUCCESS;
 
   error:
@@ -245,6 +469,283 @@ pvsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer)
     return 0;
 }
 
+static int
+pvsListDomains(virConnectPtr conn, int *ids, int maxids)
+{
+    pvsConnPtr privconn = conn->privateData;
+    int n;
+
+    pvsDriverLock(privconn);
+    n = virDomainObjListGetActiveIDs(&privconn->domains, ids, maxids);
+    pvsDriverUnlock(privconn);
+
+    return n;
+}
+
+static int
+pvsNumOfDomains(virConnectPtr conn)
+{
+    pvsConnPtr privconn = conn->privateData;
+    int count;
+
+    pvsDriverLock(privconn);
+    count = virDomainObjListNumOfDomains(&privconn->domains, 1);
+    pvsDriverUnlock(privconn);
+
+    return count;
+}
+
+static int
+pvsListDefinedDomains(virConnectPtr conn, char **const names, int maxnames)
+{
+    pvsConnPtr privconn = conn->privateData;
+    int n;
+
+    pvsDriverLock(privconn);
+    memset(names, 0, sizeof(*names) * maxnames);
+    n = virDomainObjListGetInactiveNames(&privconn->domains, names,
+                                         maxnames);
+    pvsDriverUnlock(privconn);
+
+    return n;
+}
+
+static int
+pvsNumOfDefinedDomains(virConnectPtr conn)
+{
+    pvsConnPtr privconn = conn->privateData;
+    int count;
+
+    pvsDriverLock(privconn);
+    count = virDomainObjListNumOfDomains(&privconn->domains, 0);
+    pvsDriverUnlock(privconn);
+
+    return count;
+}
+
+static virDomainPtr
+pvsLookupDomainByID(virConnectPtr conn, int id)
+{
+    pvsConnPtr privconn = conn->privateData;
+    virDomainPtr ret = NULL;
+    virDomainObjPtr dom;
+
+    pvsDriverLock(privconn);
+    dom = virDomainFindByID(&privconn->domains, id);
+    pvsDriverUnlock(privconn);
+
+    if (dom == NULL) {
+        pvsError(VIR_ERR_NO_DOMAIN, NULL);
+        goto cleanup;
+    }
+
+    ret = virGetDomain(conn, dom->def->name, dom->def->uuid);
+    if (ret)
+        ret->id = dom->def->id;
+
+  cleanup:
+    if (dom)
+        virDomainObjUnlock(dom);
+    return ret;
+}
+
+static virDomainPtr
+pvsLookupDomainByUUID(virConnectPtr conn, const unsigned char *uuid)
+{
+    pvsConnPtr privconn = conn->privateData;
+    virDomainPtr ret = NULL;
+    virDomainObjPtr dom;
+
+    pvsDriverLock(privconn);
+    dom = virDomainFindByUUID(&privconn->domains, uuid);
+    pvsDriverUnlock(privconn);
+
+    if (dom == NULL) {
+        pvsError(VIR_ERR_NO_DOMAIN, NULL);
+        goto cleanup;
+    }
+
+    ret = virGetDomain(conn, dom->def->name, dom->def->uuid);
+    if (ret)
+        ret->id = dom->def->id;
+
+  cleanup:
+    if (dom)
+        virDomainObjUnlock(dom);
+    return ret;
+}
+
+static virDomainPtr
+pvsLookupDomainByName(virConnectPtr conn, const char *name)
+{
+    pvsConnPtr privconn = conn->privateData;
+    virDomainPtr ret = NULL;
+    virDomainObjPtr dom;
+
+    pvsDriverLock(privconn);
+    dom = virDomainFindByName(&privconn->domains, name);
+    pvsDriverUnlock(privconn);
+
+    if (dom == NULL) {
+        pvsError(VIR_ERR_NO_DOMAIN, NULL);
+        goto cleanup;
+    }
+
+    ret = virGetDomain(conn, dom->def->name, dom->def->uuid);
+    if (ret)
+        ret->id = dom->def->id;
+
+  cleanup:
+    if (dom)
+        virDomainObjUnlock(dom);
+    return ret;
+}
+
+static int
+pvsGetDomainInfo(virDomainPtr domain, virDomainInfoPtr info)
+{
+    pvsConnPtr privconn = domain->conn->privateData;
+    virDomainObjPtr privdom;
+    int ret = -1;
+
+    pvsDriverLock(privconn);
+    privdom = virDomainFindByName(&privconn->domains, domain->name);
+    pvsDriverUnlock(privconn);
+
+    if (privdom == NULL) {
+        pvsError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto cleanup;
+    }
+
+    info->state = virDomainObjGetState(privdom, NULL);
+    info->memory = privdom->def->mem.cur_balloon;
+    info->maxMem = privdom->def->mem.max_balloon;
+    info->nrVirtCpu = privdom->def->vcpus;
+    info->cpuTime = 0;
+    ret = 0;
+
+  cleanup:
+    if (privdom)
+        virDomainObjUnlock(privdom);
+    return ret;
+}
+
+static char *
+pvsGetOSType(virDomainPtr dom)
+{
+    pvsConnPtr privconn = dom->conn->privateData;
+    virDomainObjPtr privdom;
+    pvsDomObjPtr pdom;
+
+    char *ret = NULL;
+
+    pvsDriverLock(privconn);
+    privdom = virDomainFindByName(&privconn->domains, dom->name);
+    if (privdom == NULL) {
+        pvsError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto cleanup;
+    }
+
+    pdom = privdom->privateData;
+
+    if (!(ret = strdup(pdom->os)))
+        virReportOOMError();
+
+  cleanup:
+    if (privdom)
+        virDomainObjUnlock(privdom);
+    pvsDriverUnlock(privconn);
+    return ret;
+}
+
+static int
+pvsDomainIsPersistent(virDomainPtr dom ATTRIBUTE_UNUSED)
+{
+    return 1;
+}
+
+static int
+pvsDomainGetState(virDomainPtr domain,
+                  int *state, int *reason, unsigned int flags)
+{
+    pvsConnPtr privconn = domain->conn->privateData;
+    virDomainObjPtr privdom;
+    int ret = -1;
+    virCheckFlags(0, -1);
+
+    pvsDriverLock(privconn);
+    privdom = virDomainFindByName(&privconn->domains, domain->name);
+    pvsDriverUnlock(privconn);
+
+    if (privdom == NULL) {
+        pvsError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto cleanup;
+    }
+
+    *state = virDomainObjGetState(privdom, reason);
+    ret = 0;
+
+  cleanup:
+    if (privdom)
+        virDomainObjUnlock(privdom);
+    return ret;
+}
+
+static char *
+pvsDomainGetXMLDesc(virDomainPtr domain, unsigned int flags)
+{
+    pvsConnPtr privconn = domain->conn->privateData;
+    virDomainDefPtr def;
+    virDomainObjPtr privdom;
+    char *ret = NULL;
+
+    /* Flags checked by virDomainDefFormat */
+
+    pvsDriverLock(privconn);
+    privdom = virDomainFindByName(&privconn->domains, domain->name);
+    pvsDriverUnlock(privconn);
+
+    if (privdom == NULL) {
+        pvsError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto cleanup;
+    }
+
+    def = (flags & VIR_DOMAIN_XML_INACTIVE) &&
+        privdom->newDef ? privdom->newDef : privdom->def;
+
+    ret = virDomainDefFormat(def, flags);
+
+  cleanup:
+    if (privdom)
+        virDomainObjUnlock(privdom);
+    return ret;
+}
+
+static int
+pvsDomainGetAutostart(virDomainPtr domain, int *autostart)
+{
+    pvsConnPtr privconn = domain->conn->privateData;
+    virDomainObjPtr privdom;
+    int ret = -1;
+
+    pvsDriverLock(privconn);
+    privdom = virDomainFindByName(&privconn->domains, domain->name);
+    pvsDriverUnlock(privconn);
+
+    if (privdom == NULL) {
+        pvsError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto cleanup;
+    }
+
+    *autostart = privdom->autostart;
+    ret = 0;
+
+  cleanup:
+    if (privdom)
+        virDomainObjUnlock(privdom);
+    return ret;
+}
+
 static virDriver pvsDriver = {
     .no = VIR_DRV_PVS,
     .name = "PVS",
@@ -254,6 +755,19 @@ static virDriver pvsDriver = {
     .getHostname = virGetHostname,      /* 0.9.12 */
     .nodeGetInfo = nodeGetInfo,      /* 0.9.12 */
     .getCapabilities = pvsGetCapabilities,      /* 0.9.12 */
+    .listDomains = pvsListDomains,      /* 0.9.12 */
+    .numOfDomains = pvsNumOfDomains,    /* 0.9.12 */
+    .listDefinedDomains = pvsListDefinedDomains,        /* 0.9.12 */
+    .numOfDefinedDomains = pvsNumOfDefinedDomains,      /* 0.9.12 */
+    .domainLookupByID = pvsLookupDomainByID,    /* 0.9.12 */
+    .domainLookupByUUID = pvsLookupDomainByUUID,        /* 0.9.12 */
+    .domainLookupByName = pvsLookupDomainByName,        /* 0.9.12 */
+    .domainGetOSType = pvsGetOSType,    /* 0.9.12 */
+    .domainGetInfo = pvsGetDomainInfo,  /* 0.9.12 */
+    .domainGetState = pvsDomainGetState,        /* 0.9.12 */
+    .domainGetXMLDesc = pvsDomainGetXMLDesc,    /* 0.9.12 */
+    .domainIsPersistent = pvsDomainIsPersistent,        /* 0.9.12 */
+    .domainGetAutostart = pvsDomainGetAutostart,        /* 0.9.12 */
 };
 
 /**
diff --git a/src/pvs/pvs_driver.h b/src/pvs/pvs_driver.h
index f5021e3..2420ce6 100644
--- a/src/pvs/pvs_driver.h
+++ b/src/pvs/pvs_driver.h
@@ -28,11 +28,23 @@
 # include "storage_conf.h"
 # include "domain_event.h"
 
+# include "json.h"
+
 # define pvsError(code, ...)                                         \
         virReportErrorHelper(VIR_FROM_TEST, code, __FILE__,         \
                              __FUNCTION__, __LINE__, __VA_ARGS__)
 # define PRLCTL      "prlctl"
+# define pvsParseError()                                                          \
+        virReportErrorHelper(VIR_FROM_TEST, VIR_ERR_OPERATION_FAILED, __FILE__,  \
+                             __FUNCTION__, __LINE__, "Can't parse prlctl output")
+
+struct pvsDomObj {
+    int id;
+    char *uuid;
+    char *os;
+};
 
+typedef struct pvsDomObj *pvsDomObjPtr;
 
 struct _pvsConn {
     virMutex lock;
@@ -48,4 +60,6 @@ typedef struct _pvsConn *pvsConnPtr;
 
 int pvsRegister(void);
 
+virJSONValuePtr pvsParseOutput(const char *binary, ...);
+
 #endif
diff --git a/src/pvs/pvs_utils.c b/src/pvs/pvs_utils.c
new file mode 100644
index 0000000..5d7411c
--- /dev/null
+++ b/src/pvs/pvs_utils.c
@@ -0,0 +1,101 @@
+/*
+ * pvs_utils.c: core driver functions for managing
+ * Parallels Virtuozzo Server hosts
+ *
+ * Copyright (C) 2012 Parallels, 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+
+#include "command.h"
+#include "virterror_internal.h"
+#include "memory.h"
+
+#include "pvs_driver.h"
+
+static int
+pvsDoCmdRun(char **outbuf, const char *binary, va_list list)
+{
+    virCommandPtr cmd = virCommandNew(binary);
+    const char *arg;
+    int exitstatus;
+    char *scmd = NULL;
+    char *sstatus = NULL;
+    int ret = -1;
+
+    while ((arg = va_arg(list, const char *)) != NULL)
+        virCommandAddArg(cmd, arg);
+
+    if (outbuf)
+        virCommandSetOutputBuffer(cmd, outbuf);
+
+    scmd = virCommandToString(cmd);
+    if (!scmd)
+        goto cleanup;
+
+    if (virCommandRun(cmd, &exitstatus)) {
+        pvsError(VIR_ERR_INTERNAL_ERROR,
+                 _("Failed to execute command '%s'"), scmd);
+        goto cleanup;
+    }
+
+    if (exitstatus) {
+        sstatus = virCommandTranslateStatus(exitstatus);
+        pvsError(VIR_ERR_INTERNAL_ERROR,
+                 _("Command '%s' finished with errors: %s"), scmd, sstatus);
+        VIR_FREE(sstatus);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+  cleanup:
+    VIR_FREE(scmd);
+    virCommandFree(cmd);
+    if (ret)
+        VIR_FREE(*outbuf);
+    return ret;
+}
+
+/*
+ * Run command and parse its JSON output, return
+ * pointer to virJSONValue or NULL in case of error.
+ */
+virJSONValuePtr
+pvsParseOutput(const char *binary, ...)
+{
+    char *outbuf;
+    virJSONValuePtr jobj = NULL;
+    va_list list;
+    int ret;
+
+    va_start(list, binary);
+    ret = pvsDoCmdRun(&outbuf, binary, list);
+    va_end(list);
+    if (ret)
+        return NULL;
+
+    jobj = virJSONValueFromString(outbuf);
+    if (!jobj)
+        pvsError(VIR_ERR_INTERNAL_ERROR, "%s: %s",
+                 _("invalid output from prlctl"), outbuf);
+
+    return jobj;
+}
-- 
1.7.1




More information about the libvir-list mailing list