[libvirt] [PATCH 1/8] parallels: add driver skeleton

Parallels Cloud Server is a cloud-ready virtualization solution that allows users to simultaneously run multiple virtual machines and containers on the same physical server. More information can be found here: http://www.parallels.com/products/pcs/ Also beta version of Parallels Cloud Server can be downloaded there. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- configure.ac | 61 ++++++--- docs/drvparallels.html.in | 28 ++++ include/libvirt/virterror.h | 1 + libvirt.spec.in | 9 +- mingw-libvirt.spec.in | 6 + po/POTFILES.in | 1 + src/Makefile.am | 13 ++ src/conf/domain_conf.c | 3 +- src/conf/domain_conf.h | 1 + src/driver.h | 1 + src/libvirt.c | 9 ++ src/parallels/parallels_driver.c | 287 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_driver.h | 28 ++++ src/util/virterror.c | 3 +- 14 files changed, 429 insertions(+), 22 deletions(-) create mode 100644 docs/drvparallels.html.in create mode 100644 src/parallels/parallels_driver.c create mode 100644 src/parallels/parallels_driver.h diff --git a/configure.ac b/configure.ac index 3cc7b3c..400ac3b 100644 --- a/configure.ac +++ b/configure.ac @@ -330,6 +330,8 @@ AC_ARG_WITH([esx], AC_HELP_STRING([--with-esx], [add ESX support @<:@default=check@:>@]),[],[with_esx=check]) AC_ARG_WITH([hyperv], AC_HELP_STRING([--with-hyperv], [add Hyper-V support @<:@default=check@:>@]),[],[with_hyperv=check]) +AC_ARG_WITH([parallels], + AC_HELP_STRING([--with-parallels], [add Parallels Cloud Server support @<:@default=check@:>@]),[],[with_parallels=check]) AC_ARG_WITH([test], AC_HELP_STRING([--with-test], [add test driver support @<:@default=yes@:>@]),[],[with_test=yes]) AC_ARG_WITH([remote], @@ -788,6 +790,26 @@ fi AM_CONDITIONAL([WITH_LXC], [test "$with_lxc" = "yes"]) dnl +dnl Checks for the Parallels driver +dnl + +if test "$with_parallels" = "check"; then + with_parallels=$with_linux + if test ! $host_cpu = 'x86_64'; then + with_parallels=no + fi +fi + +if test "$with_parallels" = "yes" && test "$with_linux" = "no"; then + AC_MSG_ERROR([The Parallels driver can be enabled on Linux only.]) +fi + +if test "$with_parallels" = "yes"; then + AC_DEFINE_UNQUOTED([WITH_PARALLELS], 1, [whether Parallels driver is enabled]) +fi +AM_CONDITIONAL([WITH_PARALLELS], [test "$with_parallels" = "yes"]) + +dnl dnl check for shell that understands <> redirection without truncation, dnl needed by src/qemu/qemu_monitor_{text,json}.c. dnl @@ -2824,25 +2846,26 @@ AC_MSG_NOTICE([=====================]) AC_MSG_NOTICE([]) AC_MSG_NOTICE([Drivers]) AC_MSG_NOTICE([]) -AC_MSG_NOTICE([ Xen: $with_xen]) -AC_MSG_NOTICE([ QEMU: $with_qemu]) -AC_MSG_NOTICE([ UML: $with_uml]) -AC_MSG_NOTICE([ OpenVZ: $with_openvz]) -AC_MSG_NOTICE([ VMware: $with_vmware]) -AC_MSG_NOTICE([ VBox: $with_vbox]) -AC_MSG_NOTICE([ XenAPI: $with_xenapi]) -AC_MSG_NOTICE([xenlight: $with_libxl]) -AC_MSG_NOTICE([ LXC: $with_lxc]) -AC_MSG_NOTICE([ PHYP: $with_phyp]) -AC_MSG_NOTICE([ ESX: $with_esx]) -AC_MSG_NOTICE([ Hyper-V: $with_hyperv]) -AC_MSG_NOTICE([ Test: $with_test]) -AC_MSG_NOTICE([ Remote: $with_remote]) -AC_MSG_NOTICE([ Network: $with_network]) -AC_MSG_NOTICE([Libvirtd: $with_libvirtd]) -AC_MSG_NOTICE([ netcf: $with_netcf]) -AC_MSG_NOTICE([ macvtap: $with_macvtap]) -AC_MSG_NOTICE([virtport: $with_virtualport]) +AC_MSG_NOTICE([ Xen: $with_xen]) +AC_MSG_NOTICE([ QEMU: $with_qemu]) +AC_MSG_NOTICE([ UML: $with_uml]) +AC_MSG_NOTICE([ OpenVZ: $with_openvz]) +AC_MSG_NOTICE([ VMware: $with_vmware]) +AC_MSG_NOTICE([ VBox: $with_vbox]) +AC_MSG_NOTICE([ XenAPI: $with_xenapi]) +AC_MSG_NOTICE([ xenlight: $with_libxl]) +AC_MSG_NOTICE([ LXC: $with_lxc]) +AC_MSG_NOTICE([ PHYP: $with_phyp]) +AC_MSG_NOTICE([ ESX: $with_esx]) +AC_MSG_NOTICE([ Hyper-V: $with_hyperv]) +AC_MSG_NOTICE([Parallels: $with_parallels]) +AC_MSG_NOTICE([ Test: $with_test]) +AC_MSG_NOTICE([ Remote: $with_remote]) +AC_MSG_NOTICE([ Network: $with_network]) +AC_MSG_NOTICE([ Libvirtd: $with_libvirtd]) +AC_MSG_NOTICE([ netcf: $with_netcf]) +AC_MSG_NOTICE([ macvtap: $with_macvtap]) +AC_MSG_NOTICE([ virtport: $with_virtualport]) AC_MSG_NOTICE([]) AC_MSG_NOTICE([Storage Drivers]) AC_MSG_NOTICE([]) diff --git a/docs/drvparallels.html.in b/docs/drvparallels.html.in new file mode 100644 index 0000000..40a0fe5 --- /dev/null +++ b/docs/drvparallels.html.in @@ -0,0 +1,28 @@ +<html><body> + <h1>Parallels Cloud Server driver</h1> + <ul id="toc"></ul> + <p> + The libvirt Parallels driver can manage Parallels Cloud Server starting from version 6.0. + </p> + + + <h2><a name="project">Project Links</a></h2> + <ul> + <li> + The <a href="http://www.parallels.com/products/server/baremetal/sp/">Parallels Cloud Server</a> Virtualization Solution. + </li> + </ul> + + + <h2><a name="uri">Connections to the Parallels Cloud Server driver</a></h2> + <p> + The libvirt Parallels driver is a single-instance privileged driver, with a driver name of 'parallels'. Some example connection URIs for the libvirt driver are: + </p> +<pre> +parallels:///default (local access) +parallels+unix:///default (local access) +parallels://example.com/default (remote access, TLS/x509) +parallels+tcp://example.com/default (remote access, SASl/Kerberos) +parallels+ssh://root@example.com/default (remote access, SSH tunnelled) +</pre> +</body></html> diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index 0e0bc9c..1c58c66 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -97,6 +97,7 @@ typedef enum { VIR_FROM_URI = 45, /* Error from URI handling */ VIR_FROM_AUTH = 46, /* Error from auth handling */ VIR_FROM_DBUS = 47, /* Error from DBus */ + VIR_FROM_PARALLELS = 48, /* Error from Parallels */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST diff --git a/libvirt.spec.in b/libvirt.spec.in index cfcfc1c..c642f80 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -67,6 +67,7 @@ %define with_esx 0%{!?_without_esx:1} %define with_hyperv 0%{!?_without_hyperv:1} %define with_xenapi 0%{!?_without_xenapi:1} +%define with_parallels 0%{!?_without_parallels:1} # Then the secondary host drivers, which run inside libvirtd %define with_network 0%{!?_without_network:%{server_drivers}} @@ -136,6 +137,7 @@ %define with_xenapi 0 %define with_libxl 0 %define with_hyperv 0 +%define with_parallels 0 %endif # Fedora 17 / RHEL-7 are first where we use systemd. Although earlier @@ -1068,6 +1070,10 @@ of recent versions of Linux (and other OSes). %define _without_vmware --without-vmware %endif +%if ! %{with_parallels} +%define _without_parallels --without-parallels +%endif + %if ! %{with_polkit} %define _without_polkit --without-polkit %endif @@ -1210,6 +1216,7 @@ autoreconf -if %{?_without_esx} \ %{?_without_hyperv} \ %{?_without_vmware} \ + %{?_without_parallels} \ %{?_without_network} \ %{?_with_rhel5_api} \ %{?_without_storage_fs} \ @@ -1401,7 +1408,7 @@ fi /sbin/chkconfig --add libvirtd if [ "$1" -ge "1" ]; then - /sbin/service libvirtd condrestart > /dev/null 2>&1 + /sbin/service libvirtd condrestart > /dev/null 2>&1 fi %endif diff --git a/mingw-libvirt.spec.in b/mingw-libvirt.spec.in index d2a8cf3..4695895 100644 --- a/mingw-libvirt.spec.in +++ b/mingw-libvirt.spec.in @@ -13,6 +13,7 @@ # missing libwsman, so can't build hyper-v %define with_hyperv 0%{!?_without_hyperv:0} %define with_xenapi 0%{!?_without_xenapi:1} +%define with_parallels 0%{!?_without_parallels:0} # RHEL ships ESX but not PowerHypervisor, HyperV, or libxenserver (xenapi) %if 0%{?rhel} @@ -125,6 +126,10 @@ MinGW Windows libvirt virtualization library, static version. %define _without_xenapi --without-xenapi %endif +%if ! %{with_parallels} +%define _without_parallels --without-parallels +%endif + %if 0%{?enable_autotools} autoreconf -if %endif @@ -148,6 +153,7 @@ autoreconf -if %{?_without_esx} \ %{?_without_hyperv} \ --without-vmware \ + --without-parallels \ --without-netcf \ --without-audit \ --without-dtrace diff --git a/po/POTFILES.in b/po/POTFILES.in index 0f32918..c4a84a9 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -64,6 +64,7 @@ src/nwfilter/nwfilter_learnipaddr.c src/openvz/openvz_conf.c src/openvz/openvz_driver.c src/openvz/openvz_util.c +src/parallels/parallels_driver.c src/phyp/phyp_driver.c src/qemu/qemu_agent.c src/qemu/qemu_bridge_filter.c diff --git a/src/Makefile.am b/src/Makefile.am index 93fcf3b..ca0c930 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -483,6 +483,10 @@ HYPERV_DRIVER_EXTRA_DIST = \ hyperv/hyperv_wmi_generator.py \ $(HYPERV_DRIVER_GENERATED) +PARALLELS_DRIVER_SOURCES = \ + parallels/parallels_driver.h \ + parallels/parallels_driver.c + NETWORK_DRIVER_SOURCES = \ network/bridge_driver.h network/bridge_driver.c @@ -921,6 +925,14 @@ libvirt_driver_hyperv_la_LIBADD = $(OPENWSMAN_LIBS) libvirt_driver_hyperv_la_SOURCES = $(HYPERV_DRIVER_SOURCES) endif +if WITH_PARALLELS +noinst_LTLIBRARIES += libvirt_driver_parallels.la +libvirt_la_BUILT_LIBADD += libvirt_driver_parallels.la +libvirt_driver_parallels_la_CFLAGS = \ + -I$(top_srcdir)/src/conf $(AM_CFLAGS) +libvirt_driver_parallels_la_SOURCES = $(PARALLELS_DRIVER_SOURCES) +endif + if WITH_NETWORK noinst_LTLIBRARIES += libvirt_driver_network_impl.la libvirt_driver_network_la_SOURCES = @@ -1132,6 +1144,7 @@ EXTRA_DIST += \ $(ESX_DRIVER_EXTRA_DIST) \ $(HYPERV_DRIVER_SOURCES) \ $(HYPERV_DRIVER_EXTRA_DIST) \ + $(PARALLELS_DRIVER_SOURCES) \ $(NETWORK_DRIVER_SOURCES) \ $(INTERFACE_DRIVER_SOURCES) \ $(STORAGE_DRIVER_SOURCES) \ diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index c53722a..e695417 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -93,7 +93,8 @@ VIR_ENUM_IMPL(virDomainVirt, VIR_DOMAIN_VIRT_LAST, "vmware", "hyperv", "vbox", - "phyp") + "phyp", + "parallels") VIR_ENUM_IMPL(virDomainBoot, VIR_DOMAIN_BOOT_LAST, "fd", diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index bc02caf..0db1693 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -160,6 +160,7 @@ enum virDomainVirtType { VIR_DOMAIN_VIRT_HYPERV, VIR_DOMAIN_VIRT_VBOX, VIR_DOMAIN_VIRT_PHYP, + VIR_DOMAIN_VIRT_PARALLELS, VIR_DOMAIN_VIRT_LAST, }; diff --git a/src/driver.h b/src/driver.h index 46d9846..aab9766 100644 --- a/src/driver.h +++ b/src/driver.h @@ -31,6 +31,7 @@ typedef enum { VIR_DRV_VMWARE = 13, VIR_DRV_LIBXL = 14, VIR_DRV_HYPERV = 15, + VIR_DRV_PARALLELS = 16, } virDrvNo; diff --git a/src/libvirt.c b/src/libvirt.c index 8315b4f..e3cc832 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -72,6 +72,9 @@ #ifdef WITH_XENAPI # include "xenapi/xenapi_driver.h" #endif +#ifdef WITH_PARALLELS +# include "parallels/parallels_driver.h" +#endif #define VIR_FROM_THIS VIR_FROM_NONE @@ -443,6 +446,9 @@ virInitialize(void) #ifdef WITH_XENAPI if (xenapiRegister() == -1) return -1; #endif +#ifdef WITH_PARALLELS + if (parallelsRegister() == -1) return -1; +#endif #ifdef WITH_REMOTE if (remoteRegister () == -1) return -1; #endif @@ -1144,6 +1150,9 @@ do_open (const char *name, #ifndef WITH_XENAPI STRCASEEQ(ret->uri->scheme, "xenapi") || #endif +#ifndef WITH_PARALLELS + STRCASEEQ(ret->uri->scheme, "parallels") || +#endif false)) { virReportErrorHelper(VIR_FROM_NONE, VIR_ERR_CONFIG_UNSUPPORTED, __FILE__, __FUNCTION__, __LINE__, diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c new file mode 100644 index 0000000..00a9074 --- /dev/null +++ b/src/parallels/parallels_driver.c @@ -0,0 +1,287 @@ +/* + * parallels_driver.c: core driver functions for managing + * Parallels Cloud 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 <sys/types.h> +#include <sys/poll.h> +#include <limits.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/utsname.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <paths.h> +#include <pwd.h> +#include <stdio.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/statvfs.h> + +#include "datatypes.h" +#include "virterror_internal.h" +#include "memory.h" +#include "util.h" +#include "logging.h" +#include "command.h" +#include "configmake.h" +#include "storage_file.h" +#include "nodeinfo.h" +#include "json.h" +#include "domain_conf.h" +#include "storage_conf.h" +#include "domain_event.h" + +#include "parallels_driver.h" + +#define VIR_FROM_THIS VIR_FROM_PARALLELS + +#define PRLCTL "prlctl" +#define PARALLELS_DEFAULT_ARCH "x86_64" + +struct _parallelsConn { + virMutex lock; + virDomainObjList domains; + virStoragePoolObjList pools; + virCapsPtr caps; + virDomainEventStatePtr domainEventState; +}; + +typedef struct _parallelsConn parallelsConn; +typedef struct _parallelsConn *parallelsConnPtr; + +static int parallelsClose(virConnectPtr conn); + +static void +parallelsDriverLock(parallelsConnPtr driver) +{ + virMutexLock(&driver->lock); +} + +static void +parallelsDriverUnlock(parallelsConnPtr driver) +{ + virMutexUnlock(&driver->lock); +} + +static int +parallelsDefaultConsoleType(const char *ostype ATTRIBUTE_UNUSED) +{ + return VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL; +} + +static virCapsPtr +parallelsBuildCapabilities(void) +{ + virCapsPtr caps; + virCapsGuestPtr guest; + struct utsname utsname; + uname(&utsname); + + if ((caps = virCapabilitiesNew(utsname.machine, 0, 0)) == NULL) + goto no_memory; + + if (nodeCapsInitNUMA(caps) < 0) + goto no_memory; + + virCapabilitiesSetMacPrefix(caps, (unsigned char[]) { + 0x42, 0x1C, 0x00}); + + if ((guest = virCapabilitiesAddGuest(caps, "hvm", PARALLELS_DEFAULT_ARCH, + 64, "parallels", + NULL, 0, NULL)) == NULL) + goto no_memory; + + if (virCapabilitiesAddGuestDomain(guest, + "parallels", NULL, NULL, 0, NULL) == NULL) + goto no_memory; + + caps->defaultConsoleTargetType = parallelsDefaultConsoleType; + return caps; + + no_memory: + virReportOOMError(); + virCapabilitiesFree(caps); + return NULL; +} + +static char * +parallelsGetCapabilities(virConnectPtr conn) +{ + parallelsConnPtr privconn = conn->privateData; + char *xml; + + parallelsDriverLock(privconn); + if ((xml = virCapabilitiesFormatXML(privconn->caps)) == NULL) + virReportOOMError(); + parallelsDriverUnlock(privconn); + return xml; +} + +static int +parallelsOpenDefault(virConnectPtr conn) +{ + parallelsConnPtr privconn; + + if (VIR_ALLOC(privconn) < 0) { + virReportOOMError(); + return VIR_DRV_OPEN_ERROR; + } + if (virMutexInit(&privconn->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot initialize mutex")); + goto error; + } + + if (!(privconn->caps = parallelsBuildCapabilities())) + goto error; + + if (virDomainObjListInit(&privconn->domains) < 0) + goto error; + + conn->privateData = privconn; + + return VIR_DRV_OPEN_SUCCESS; + + error: + virDomainObjListDeinit(&privconn->domains); + virCapabilitiesFree(privconn->caps); + virStoragePoolObjListFree(&privconn->pools); + VIR_FREE(privconn); + return VIR_DRV_OPEN_ERROR; +} + +static virDrvOpenStatus +parallelsOpen(virConnectPtr conn, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + unsigned int flags) +{ + int ret; + parallelsConnPtr privconn; + virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); + + if (!conn->uri) + return VIR_DRV_OPEN_DECLINED; + + if (!conn->uri->scheme || STRNEQ(conn->uri->scheme, "parallels")) + return VIR_DRV_OPEN_DECLINED; + + /* Remote driver should handle these. */ + if (conn->uri->server) + return VIR_DRV_OPEN_DECLINED; + + /* From this point on, the connection is for us. */ + if (!conn->uri->path || + conn->uri->path[0] == '\0' || + (conn->uri->path[0] == '/' && conn->uri->path[1] == '\0')) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("parallelsOpen: supply a path or use " + "parallels:///default")); + return VIR_DRV_OPEN_ERROR; + } + + if (STREQ(conn->uri->path, "/default")) + ret = parallelsOpenDefault(conn); + else + return VIR_DRV_OPEN_DECLINED; + + if (ret != VIR_DRV_OPEN_SUCCESS) + return ret; + + privconn = conn->privateData; + parallelsDriverLock(privconn); + privconn->domainEventState = virDomainEventStateNew(); + if (!privconn->domainEventState) { + parallelsDriverUnlock(privconn); + parallelsClose(conn); + return VIR_DRV_OPEN_ERROR; + } + + parallelsDriverUnlock(privconn); + return VIR_DRV_OPEN_SUCCESS; +} + +static int +parallelsClose(virConnectPtr conn) +{ + parallelsConnPtr privconn = conn->privateData; + + parallelsDriverLock(privconn); + virCapabilitiesFree(privconn->caps); + virDomainObjListDeinit(&privconn->domains); + virDomainEventStateFree(privconn->domainEventState); + conn->privateData = NULL; + + parallelsDriverUnlock(privconn); + virMutexDestroy(&privconn->lock); + + VIR_FREE(privconn); + return 0; +} + +static int +parallelsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer) +{ + /* TODO */ + *hvVer = 6; + return 0; +} + +static virDriver parallelsDriver = { + .no = VIR_DRV_PARALLELS, + .name = "Parallels", + .open = parallelsOpen, /* 0.10.0 */ + .close = parallelsClose, /* 0.10.0 */ + .version = parallelsGetVersion, /* 0.10.0 */ + .getHostname = virGetHostname, /* 0.10.0 */ + .nodeGetInfo = nodeGetInfo, /* 0.10.0 */ + .getCapabilities = parallelsGetCapabilities, /* 0.10.0 */ +}; + +/** + * parallelsRegister: + * + * Registers the parallels driver + */ +int +parallelsRegister(void) +{ + char *prlctl_path; + + prlctl_path = virFindFileInPath(PRLCTL); + if (!prlctl_path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Can't find prlctl command in the PATH env")); + return VIR_DRV_OPEN_ERROR; + } + + VIR_FREE(prlctl_path); + + if (virRegisterDriver(¶llelsDriver) < 0) + return -1; + + return 0; +} diff --git a/src/parallels/parallels_driver.h b/src/parallels/parallels_driver.h new file mode 100644 index 0000000..0a021c9 --- /dev/null +++ b/src/parallels/parallels_driver.h @@ -0,0 +1,28 @@ +/* + * parallels_driver.c: core driver functions for managing + * Parallels Cloud 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 + * + */ + +#ifndef PARALLELS_DRIVER_H +# define PARALLELS_DRIVER_H + +int parallelsRegister(void); + +#endif diff --git a/src/util/virterror.c b/src/util/virterror.c index b5c6853..8cf738a 100644 --- a/src/util/virterror.c +++ b/src/util/virterror.c @@ -99,7 +99,8 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "URI Utils", /* 45 */ "Authentication Utils", - "DBus Utils" + "DBus Utils", + "Parallels Cloud Server" ) -- 1.7.1

Parallels driver is 'stateless', like vmware or openvz drivers. It collects information about domains during startup using command-line utility prlctl. VMs in Parallels are 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, is implemented. Querying devices information will be added in the next patches. Parallels doesn't support non-persistent domains - you can't run a domain having only disk image, it must always be registered in system. Functions for querying domain info have been just copied from test driver with some changes - they extract needed data from previously created list of virDomainObj objects. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/parallels/parallels_driver.c | 569 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_utils.c | 92 ++++++ src/parallels/parallels_utils.h | 29 ++ 5 files changed, 693 insertions(+), 1 deletions(-) create mode 100644 src/parallels/parallels_utils.c create mode 100644 src/parallels/parallels_utils.h diff --git a/po/POTFILES.in b/po/POTFILES.in index c4a84a9..e952f2d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -65,6 +65,7 @@ src/openvz/openvz_conf.c src/openvz/openvz_driver.c src/openvz/openvz_util.c src/parallels/parallels_driver.c +src/parallels/parallels_utils.c src/phyp/phyp_driver.c src/qemu/qemu_agent.c src/qemu/qemu_bridge_filter.c diff --git a/src/Makefile.am b/src/Makefile.am index ca0c930..eac8d99 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -485,7 +485,8 @@ HYPERV_DRIVER_EXTRA_DIST = \ PARALLELS_DRIVER_SOURCES = \ parallels/parallels_driver.h \ - parallels/parallels_driver.c + parallels/parallels_driver.c \ + parallels/parallels_utils.c NETWORK_DRIVER_SOURCES = \ network/bridge_driver.h network/bridge_driver.c diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index 00a9074..c716b25 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -54,14 +54,28 @@ #include "domain_conf.h" #include "storage_conf.h" #include "domain_event.h" +#include "virdomainlist.h" #include "parallels_driver.h" +#include "parallels_utils.h" #define VIR_FROM_THIS VIR_FROM_PARALLELS #define PRLCTL "prlctl" #define PARALLELS_DEFAULT_ARCH "x86_64" +#define parallelsDomNotFoundError(domain) \ + do { \ + char uuidstr[VIR_UUID_STRING_BUFLEN]; \ + virUUIDFormat(domain->uuid, uuidstr); \ + virReportError(VIR_ERR_NO_DOMAIN, \ + _("no domain with matching uuid '%s'"), uuidstr); \ + } while (0) + +#define parallelsParseError() \ + virReportErrorHelper(VIR_FROM_TEST, VIR_ERR_OPERATION_FAILED, __FILE__, \ + __FUNCTION__, __LINE__, _("Can't parse prlctl output")) + struct _parallelsConn { virMutex lock; virDomainObjList domains; @@ -73,6 +87,14 @@ struct _parallelsConn { typedef struct _parallelsConn parallelsConn; typedef struct _parallelsConn *parallelsConnPtr; +struct parallelsDomObj { + int id; + char *uuid; + char *os; +}; + +typedef struct parallelsDomObj *parallelsDomObjPtr; + static int parallelsClose(virConnectPtr conn); static void @@ -93,6 +115,19 @@ parallelsDefaultConsoleType(const char *ostype ATTRIBUTE_UNUSED) return VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL; } +static void +parallelsDomObjFreePrivate(void *p) +{ + parallelsDomObjPtr pdom = p; + + if (!pdom) + return; + + VIR_FREE(pdom->uuid); + VIR_FREE(pdom->os); + VIR_FREE(p); +}; + static virCapsPtr parallelsBuildCapabilities(void) { @@ -141,6 +176,209 @@ parallelsGetCapabilities(virConnectPtr conn) return xml; } +/* + * Must be called with privconn->lock held + */ +static virDomainObjPtr +parallelsLoadDomain(parallelsConnPtr privconn, virJSONValuePtr jobj) +{ + virDomainObjPtr dom = NULL; + virDomainDefPtr def = NULL; + parallelsDomObjPtr pdom = NULL; + virJSONValuePtr jobj2, jobj3; + const char *tmp; + char *endptr; + unsigned long mem; + unsigned int x; + const char *autostart; + const char *state; + + if (VIR_ALLOC(def) < 0) + goto no_memory; + + def->virtType = VIR_DOMAIN_VIRT_PARALLELS; + def->id = -1; + + if (!(tmp = virJSONValueObjectGetString(jobj, "Name"))) { + parallelsParseError(); + goto cleanup; + } + if (!(def->name = strdup(tmp))) + goto no_memory; + + if (!(tmp = virJSONValueObjectGetString(jobj, "ID"))) { + parallelsParseError(); + goto cleanup; + } + + if (virUUIDParse(tmp, def->uuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("UUID in config file malformed")); + goto cleanup; + } + + if (!(tmp = virJSONValueObjectGetString(jobj, "Description"))) { + parallelsParseError(); + goto cleanup; + } + if (!(def->description = strdup(tmp))) + goto no_memory; + + if (!(jobj2 = virJSONValueObjectGet(jobj, "Hardware"))) { + parallelsParseError(); + goto cleanup; + } + + if (!(jobj3 = virJSONValueObjectGet(jobj2, "cpu"))) { + parallelsParseError(); + goto cleanup; + } + + if (virJSONValueObjectGetNumberUint(jobj3, "cpus", &x) < 0) { + parallelsParseError(); + goto cleanup; + } + def->vcpus = x; + def->maxvcpus = x; + + if (!(jobj3 = virJSONValueObjectGet(jobj2, "memory"))) { + parallelsParseError(); + goto cleanup; + } + + if (!(tmp = virJSONValueObjectGetString(jobj3, "size"))) { + parallelsParseError(); + goto cleanup; + } + + if (virStrToLong_ul(tmp, &endptr, 10, &mem) < 0) { + parallelsParseError(); + goto cleanup; + } + + if (!STREQ(endptr, "Mb")) { + parallelsParseError(); + 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.arch = strdup(PARALLELS_DEFAULT_ARCH))) + goto no_memory; + + if (VIR_ALLOC(pdom) < 0) + goto no_memory; + + if (virJSONValueObjectGetNumberUint(jobj, "EnvID", &x) < 0) + goto cleanup; + pdom->id = x; + if (!(tmp = virJSONValueObjectGetString(jobj, "ID"))) { + parallelsParseError(); + goto cleanup; + } + if (!(pdom->uuid = strdup(tmp))) + goto no_memory; + + if (!(tmp = virJSONValueObjectGetString(jobj, "OS"))) + goto cleanup; + if (!(pdom->os = strdup(tmp))) + goto no_memory; + + if (!(state = virJSONValueObjectGetString(jobj, "State"))) { + parallelsParseError(); + goto cleanup; + } + + if (!(autostart = virJSONValueObjectGetString(jobj, "Autostart"))) { + parallelsParseError(); + goto cleanup; + } + + if (!(dom = virDomainAssignDef(privconn->caps, + &privconn->domains, def, false))) + goto cleanup; + /* dom is locked here */ + + dom->privateDataFreeFunc = parallelsDomObjFreePrivate; + dom->privateData = pdom; + dom->persistent = 1; + + /* TODO: handle all possible states */ + if (STREQ(state, "running")) { + virDomainObjSetState(dom, VIR_DOMAIN_RUNNING, + VIR_DOMAIN_RUNNING_BOOTED); + def->id = pdom->id; + } + + if (STREQ(autostart, "on")) + dom->autostart = 1; + else + dom->autostart = 0; + + virDomainObjUnlock(dom); + + return dom; + + no_memory: + virReportOOMError(); + cleanup: + virDomainDefFree(def); + parallelsDomObjFreePrivate(pdom); + return NULL; +} + +/* + * Must be called with privconn->lock held + * + * if domain_name is NULL - load information about all + * registered domains. + */ +static int +parallelsLoadDomains(parallelsConnPtr privconn, const char *domain_name) +{ + int count, i; + virJSONValuePtr jobj; + virJSONValuePtr jobj2; + virDomainObjPtr dom = NULL; + int ret = -1; + + jobj = parallelsParseOutput(PRLCTL, "list", "-j", "-a", "-i", "-H", + "--vmtype", "vm", domain_name, NULL); + if (!jobj) { + parallelsParseError(); + goto cleanup; + } + + count = virJSONValueArraySize(jobj); + if (count < 0) { + parallelsParseError(); + goto cleanup; + } + + for (i = 0; i < count; i++) { + jobj2 = virJSONValueArrayGet(jobj, i); + if (!jobj2) { + parallelsParseError(); + goto cleanup; + } + + dom = parallelsLoadDomain(privconn, jobj2); + if (!dom) + goto cleanup; + } + + ret = 0; + + cleanup: + virJSONValueFree(jobj); + return ret; +} + static int parallelsOpenDefault(virConnectPtr conn) { @@ -164,6 +402,9 @@ parallelsOpenDefault(virConnectPtr conn) conn->privateData = privconn; + if (parallelsLoadDomains(privconn, NULL)) + goto error; + return VIR_DRV_OPEN_SUCCESS; error: @@ -250,6 +491,320 @@ parallelsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer) return 0; } +static int +parallelsListDomains(virConnectPtr conn, int *ids, int maxids) +{ + parallelsConnPtr privconn = conn->privateData; + int n; + + parallelsDriverLock(privconn); + n = virDomainObjListGetActiveIDs(&privconn->domains, ids, maxids); + parallelsDriverUnlock(privconn); + + return n; +} + +static int +parallelsNumOfDomains(virConnectPtr conn) +{ + parallelsConnPtr privconn = conn->privateData; + int count; + + parallelsDriverLock(privconn); + count = virDomainObjListNumOfDomains(&privconn->domains, 1); + parallelsDriverUnlock(privconn); + + return count; +} + +static int +parallelsListDefinedDomains(virConnectPtr conn, char **const names, int maxnames) +{ + parallelsConnPtr privconn = conn->privateData; + int n; + + parallelsDriverLock(privconn); + memset(names, 0, sizeof(*names) * maxnames); + n = virDomainObjListGetInactiveNames(&privconn->domains, names, + maxnames); + parallelsDriverUnlock(privconn); + + return n; +} + +static int +parallelsNumOfDefinedDomains(virConnectPtr conn) +{ + parallelsConnPtr privconn = conn->privateData; + int count; + + parallelsDriverLock(privconn); + count = virDomainObjListNumOfDomains(&privconn->domains, 0); + parallelsDriverUnlock(privconn); + + return count; +} + +static int +parallelsListAllDomains(virConnectPtr conn, + virDomainPtr **domains, + unsigned int flags) +{ + parallelsConnPtr privconn = conn->privateData; + int ret = -1; + + virCheckFlags(VIR_CONNECT_LIST_FILTERS_ALL, -1); + parallelsDriverLock(privconn); + ret = virDomainList(conn, privconn->domains.objs, domains, flags); + parallelsDriverUnlock(privconn); + + return ret; +} + +static virDomainPtr +parallelsLookupDomainByID(virConnectPtr conn, int id) +{ + parallelsConnPtr privconn = conn->privateData; + virDomainPtr ret = NULL; + virDomainObjPtr dom; + + parallelsDriverLock(privconn); + dom = virDomainFindByID(&privconn->domains, id); + parallelsDriverUnlock(privconn); + + if (dom == NULL) { + virReportError(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 +parallelsLookupDomainByUUID(virConnectPtr conn, const unsigned char *uuid) +{ + parallelsConnPtr privconn = conn->privateData; + virDomainPtr ret = NULL; + virDomainObjPtr dom; + + parallelsDriverLock(privconn); + dom = virDomainFindByUUID(&privconn->domains, uuid); + parallelsDriverUnlock(privconn); + + if (dom == NULL) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(uuid, uuidstr); + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + 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 +parallelsLookupDomainByName(virConnectPtr conn, const char *name) +{ + parallelsConnPtr privconn = conn->privateData; + virDomainPtr ret = NULL; + virDomainObjPtr dom; + + parallelsDriverLock(privconn); + dom = virDomainFindByName(&privconn->domains, name); + parallelsDriverUnlock(privconn); + + if (dom == NULL) { + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching name '%s'"), name); + 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 +parallelsGetDomainInfo(virDomainPtr domain, virDomainInfoPtr info) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + int ret = -1; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + 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 * +parallelsGetOSType(virDomainPtr domain) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + parallelsDomObjPtr pdom; + + char *ret = NULL; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + pdom = privdom->privateData; + + if (!(ret = strdup(pdom->os))) + virReportOOMError(); + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + parallelsDriverUnlock(privconn); + return ret; +} + +static int +parallelsDomainIsPersistent(virDomainPtr domain) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + int ret = -1; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + ret = 1; + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + parallelsDriverUnlock(privconn); + return ret; +} + +static int +parallelsDomainGetState(virDomainPtr domain, + int *state, int *reason, unsigned int flags) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + int ret = -1; + virCheckFlags(0, -1); + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + *state = virDomainObjGetState(privdom, reason); + ret = 0; + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + return ret; +} + +static char * +parallelsDomainGetXMLDesc(virDomainPtr domain, unsigned int flags) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainDefPtr def; + virDomainObjPtr privdom; + char *ret = NULL; + + /* Flags checked by virDomainDefFormat */ + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + 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 +parallelsDomainGetAutostart(virDomainPtr domain, int *autostart) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + int ret = -1; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + *autostart = privdom->autostart; + ret = 0; + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + return ret; +} + static virDriver parallelsDriver = { .no = VIR_DRV_PARALLELS, .name = "Parallels", @@ -259,6 +814,20 @@ static virDriver parallelsDriver = { .getHostname = virGetHostname, /* 0.10.0 */ .nodeGetInfo = nodeGetInfo, /* 0.10.0 */ .getCapabilities = parallelsGetCapabilities, /* 0.10.0 */ + .listDomains = parallelsListDomains, /* 0.10.0 */ + .numOfDomains = parallelsNumOfDomains, /* 0.10.0 */ + .listDefinedDomains = parallelsListDefinedDomains, /* 0.10.0 */ + .numOfDefinedDomains = parallelsNumOfDefinedDomains, /* 0.10.0 */ + .listAllDomains = parallelsListAllDomains, /* 0.10.0 */ + .domainLookupByID = parallelsLookupDomainByID, /* 0.10.0 */ + .domainLookupByUUID = parallelsLookupDomainByUUID, /* 0.10.0 */ + .domainLookupByName = parallelsLookupDomainByName, /* 0.10.0 */ + .domainGetOSType = parallelsGetOSType, /* 0.10.0 */ + .domainGetInfo = parallelsGetDomainInfo, /* 0.10.0 */ + .domainGetState = parallelsDomainGetState, /* 0.10.0 */ + .domainGetXMLDesc = parallelsDomainGetXMLDesc, /* 0.10.0 */ + .domainIsPersistent = parallelsDomainIsPersistent, /* 0.10.0 */ + .domainGetAutostart = parallelsDomainGetAutostart, /* 0.10.0 */ }; /** diff --git a/src/parallels/parallels_utils.c b/src/parallels/parallels_utils.c new file mode 100644 index 0000000..102ccfc --- /dev/null +++ b/src/parallels/parallels_utils.c @@ -0,0 +1,92 @@ +/* + * parallels_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 "json.h" + +#include "parallels_utils.h" + +#define VIR_FROM_THIS VIR_FROM_PARALLELS + +static int +parallelsDoCmdRun(char **outbuf, const char *binary, va_list list) +{ + virCommandPtr cmd = virCommandNew(binary); + const char *arg; + char *scmd = 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, NULL)) + 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 +parallelsParseOutput(const char *binary, ...) +{ + char *outbuf; + virJSONValuePtr jobj = NULL; + va_list list; + int ret; + + va_start(list, binary); + ret = parallelsDoCmdRun(&outbuf, binary, list); + va_end(list); + if (ret) + return NULL; + + jobj = virJSONValueFromString(outbuf); + if (!jobj) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid output from prlctl: %s"), outbuf); + + VIR_FREE(outbuf); + return jobj; +} diff --git a/src/parallels/parallels_utils.h b/src/parallels/parallels_utils.h new file mode 100644 index 0000000..a17cbab --- /dev/null +++ b/src/parallels/parallels_utils.h @@ -0,0 +1,29 @@ +/* + * parallels_utils.h: 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 + * + */ + +#ifndef PARALLELS_UTILS_H +# define PARALLELS_UTILS_H + +virJSONValuePtr parallelsParseOutput(const char *binary, ...) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL; + +#endif -- 1.7.1

On Thu, Jul 26, 2012 at 10:32:01PM +0400, Dmitry Guryanov wrote:
+static virDomainObjPtr +parallelsLoadDomain(parallelsConnPtr privconn, virJSONValuePtr jobj) +{
+ if (!(def->os.type = strdup("hvm"))) + goto no_memory; + + if (!(def->os.arch = strdup(PARALLELS_DEFAULT_ARCH))) + goto no_memory;
How you answer my question in the previous patch, might impact what you need todo here.
+static char * +parallelsGetOSType(virDomainPtr domain) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + parallelsDomObjPtr pdom; + + char *ret = NULL; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + pdom = privdom->privateData; + + if (!(ret = strdup(pdom->os))) + virReportOOMError();
What sort of value is 'pdom->os' holding. The name of this particular libvirt method is somewhat misleading and does *not* in fact refer to the guest operating system name. It is actually just wanting the value from def->os.type, which is 'hvm' or 'exe' etc
diff --git a/src/parallels/parallels_utils.c b/src/parallels/parallels_utils.c new file mode 100644 index 0000000..102ccfc --- /dev/null +++ b/src/parallels/parallels_utils.c @@ -0,0 +1,92 @@ +/* + * parallels_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 "json.h" + +#include "parallels_utils.h" + +#define VIR_FROM_THIS VIR_FROM_PARALLELS + +static int +parallelsDoCmdRun(char **outbuf, const char *binary, va_list list) +{ + virCommandPtr cmd = virCommandNew(binary); + const char *arg; + char *scmd = NULL; + int ret = -1; + + while ((arg = va_arg(list, const char *)) != NULL) + virCommandAddArg(cmd, arg);
I'd actually be happy for you to add a 'virCommandNewVAList' API to src/util/command.{c,h} to contain this code, since we already have one that takes '...' args 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 :|

On 07/27/2012 01:23 PM, Daniel P. Berrange wrote:
On Thu, Jul 26, 2012 at 10:32:01PM +0400, Dmitry Guryanov wrote:
+static virDomainObjPtr +parallelsLoadDomain(parallelsConnPtr privconn, virJSONValuePtr jobj) +{
+ if (!(def->os.type = strdup("hvm"))) + goto no_memory; + + if (!(def->os.arch = strdup(PARALLELS_DEFAULT_ARCH))) + goto no_memory; How you answer my question in the previous patch, might impact what you need todo here. We support only x86_64 and there is a check in configure.ac.
+static char * +parallelsGetOSType(virDomainPtr domain) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + parallelsDomObjPtr pdom; + + char *ret = NULL; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + pdom = privdom->privateData; + + if (!(ret = strdup(pdom->os))) + virReportOOMError(); What sort of value is 'pdom->os' holding. The name of this particular libvirt method is somewhat misleading and does *not* in fact refer to the guest operating system name. It is actually just wanting the value from def->os.type, which is 'hvm' or 'exe' etc Yes, I thought that this is Guest OS name, need to be changed to "hvm".
diff --git a/src/parallels/parallels_utils.c b/src/parallels/parallels_utils.c new file mode 100644 index 0000000..102ccfc --- /dev/null +++ b/src/parallels/parallels_utils.c @@ -0,0 +1,92 @@ +/* + * parallels_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 "json.h" + +#include "parallels_utils.h" + +#define VIR_FROM_THIS VIR_FROM_PARALLELS + +static int +parallelsDoCmdRun(char **outbuf, const char *binary, va_list list) +{ + virCommandPtr cmd = virCommandNew(binary); + const char *arg; + char *scmd = NULL; + int ret = -1; + + while ((arg = va_arg(list, const char *)) != NULL) + virCommandAddArg(cmd, arg); I'd actually be happy for you to add a 'virCommandNewVAList' API to src/util/command.{c,h} to contain this code, since we already have one that takes '...' args
OK
Regards, Daniel
-- Dmitry Guryanov

On Thu, Jul 26, 2012 at 10:32:01PM +0400, Dmitry Guryanov wrote:
Parallels driver is 'stateless', like vmware or openvz drivers. It collects information about domains during startup using command-line utility prlctl. VMs in Parallels are 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, is implemented. Querying devices information will be added in the next patches.
Parallels doesn't support non-persistent domains - you can't run a domain having only disk image, it must always be registered in system.
Functions for querying domain info have been just copied from test driver with some changes - they extract needed data from previously created list of virDomainObj objects.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com>
diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index 00a9074..c716b25 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -54,14 +54,28 @@ #include "domain_conf.h" #include "storage_conf.h" #include "domain_event.h" +#include "virdomainlist.h"
#include "parallels_driver.h" +#include "parallels_utils.h"
#define VIR_FROM_THIS VIR_FROM_PARALLELS
#define PRLCTL "prlctl" #define PARALLELS_DEFAULT_ARCH "x86_64"
+#define parallelsDomNotFoundError(domain) \ + do { \ + char uuidstr[VIR_UUID_STRING_BUFLEN]; \ + virUUIDFormat(domain->uuid, uuidstr); \ + virReportError(VIR_ERR_NO_DOMAIN, \ + _("no domain with matching uuid '%s'"), uuidstr); \ + } while (0) + +#define parallelsParseError() \ + virReportErrorHelper(VIR_FROM_TEST, VIR_ERR_OPERATION_FAILED, __FILE__, \ + __FUNCTION__, __LINE__, _("Can't parse prlctl output"))
ITYM to s/VIR_FROM_TEST/VIR_FROM_THIS/ 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 :|

Add functions for create/shutdown/destroy and suspend/resume domain. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 143 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_utils.c | 18 +++++ src/parallels/parallels_utils.h | 2 + 3 files changed, 163 insertions(+), 0 deletions(-) diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index c716b25..8c20d27 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -128,6 +128,12 @@ parallelsDomObjFreePrivate(void *p) VIR_FREE(p); }; +static void +parallelsDomainEventQueue(parallelsConnPtr driver, virDomainEventPtr event) +{ + virDomainEventStateQueue(driver->domainEventState, event); +} + static virCapsPtr parallelsBuildCapabilities(void) { @@ -805,6 +811,138 @@ parallelsDomainGetAutostart(virDomainPtr domain, int *autostart) return ret; } +typedef int (*parallelsChangeStateFunc) (virDomainObjPtr privdom); +#define PARALLELS_UUID(x) (((parallelsDomObjPtr)(x->privateData))->uuid) + +static int +parallelsDomainChangeState(virDomainPtr domain, + virDomainState req_state, const char *req_state_name, + parallelsChangeStateFunc chstate, + virDomainState new_state, int reason, + int event_type, int event_detail) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + virDomainEventPtr event = NULL; + int state; + int ret = -1; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + state = virDomainObjGetState(privdom, NULL); + if (state != req_state) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("domain '%s' not %s"), + privdom->def->name, req_state_name); + goto cleanup; + } + + if (chstate(privdom)) + goto cleanup; + + virDomainObjSetState(privdom, new_state, reason); + + event = virDomainEventNewFromObj(privdom, event_type, event_detail); + ret = 0; + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + + if (event) { + parallelsDriverLock(privconn); + parallelsDomainEventQueue(privconn, event); + parallelsDriverUnlock(privconn); + } + return ret; +} + +static int parallelsPause(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "pause", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsPauseDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsPause, + VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_USER, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); +} + +static int parallelsResume(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "resume", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsResumeDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_PAUSED, "paused", + parallelsResume, + VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_UNPAUSED, + VIR_DOMAIN_EVENT_RESUMED, + VIR_DOMAIN_EVENT_RESUMED_UNPAUSED); +} + +static int parallelsStart(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "start", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsDomainCreate(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_SHUTOFF, "stopped", + parallelsStart, + VIR_DOMAIN_RUNNING, VIR_DOMAIN_EVENT_STARTED_BOOTED, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_BOOTED); +} + +static int parallelsKill(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), "--kill", NULL); +} + +static int +parallelsDestroyDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsKill, + VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_DESTROYED, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_DESTROYED); +} + +static int parallelsStop(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsShutdownDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsStop, + VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_SHUTDOWN, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN); +} + static virDriver parallelsDriver = { .no = VIR_DRV_PARALLELS, .name = "Parallels", @@ -828,6 +966,11 @@ static virDriver parallelsDriver = { .domainGetXMLDesc = parallelsDomainGetXMLDesc, /* 0.10.0 */ .domainIsPersistent = parallelsDomainIsPersistent, /* 0.10.0 */ .domainGetAutostart = parallelsDomainGetAutostart, /* 0.10.0 */ + .domainSuspend = parallelsPauseDomain, /* 0.10.0 */ + .domainResume = parallelsResumeDomain, /* 0.10.0 */ + .domainDestroy = parallelsDestroyDomain, /* 0.10.0 */ + .domainShutdown = parallelsShutdownDomain, /* 0.10.0 */ + .domainCreate = parallelsDomainCreate, /* 0.10.0 */ }; /** diff --git a/src/parallels/parallels_utils.c b/src/parallels/parallels_utils.c index 102ccfc..78d45e0 100644 --- a/src/parallels/parallels_utils.c +++ b/src/parallels/parallels_utils.c @@ -90,3 +90,21 @@ parallelsParseOutput(const char *binary, ...) VIR_FREE(outbuf); return jobj; } + +/* + * Run prlctl command and check for errors + * + * Return value is 0 in case of success, else - -1 + */ +int +parallelsCmdRun(const char *binary, ...) +{ + int ret; + va_list list; + + va_start(list, binary); + ret = parallelsDoCmdRun(NULL, binary, list); + va_end(list); + + return ret; +} diff --git a/src/parallels/parallels_utils.h b/src/parallels/parallels_utils.h index a17cbab..d997ad3 100644 --- a/src/parallels/parallels_utils.h +++ b/src/parallels/parallels_utils.h @@ -25,5 +25,7 @@ virJSONValuePtr parallelsParseOutput(const char *binary, ...) ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL; +int parallelsCmdRun(const char *binary, ...) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL; #endif -- 1.7.1

On Thu, Jul 26, 2012 at 10:32:02PM +0400, Dmitry Guryanov wrote:
Add functions for create/shutdown/destroy and suspend/resume domain.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 143 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_utils.c | 18 +++++ src/parallels/parallels_utils.h | 2 + 3 files changed, 163 insertions(+), 0 deletions(-)
diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index c716b25..8c20d27 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -128,6 +128,12 @@ parallelsDomObjFreePrivate(void *p) VIR_FREE(p); };
+static void +parallelsDomainEventQueue(parallelsConnPtr driver, virDomainEventPtr event) +{ + virDomainEventStateQueue(driver->domainEventState, event); +}
I don't see any code which ever deals with dispatching of the domain event state. Is this something you're just preparing now, so that you can wire up events in a future patch ?
+ static virCapsPtr parallelsBuildCapabilities(void) { @@ -805,6 +811,138 @@ parallelsDomainGetAutostart(virDomainPtr domain, int *autostart) return ret; }
+typedef int (*parallelsChangeStateFunc) (virDomainObjPtr privdom); +#define PARALLELS_UUID(x) (((parallelsDomObjPtr)(x->privateData))->uuid) + +static int +parallelsDomainChangeState(virDomainPtr domain, + virDomainState req_state, const char *req_state_name, + parallelsChangeStateFunc chstate, + virDomainState new_state, int reason, + int event_type, int event_detail) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + virDomainEventPtr event = NULL; + int state; + int ret = -1; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + state = virDomainObjGetState(privdom, NULL); + if (state != req_state) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("domain '%s' not %s"), + privdom->def->name, req_state_name); + goto cleanup; + } + + if (chstate(privdom)) + goto cleanup; + + virDomainObjSetState(privdom, new_state, reason); + + event = virDomainEventNewFromObj(privdom, event_type, event_detail); + ret = 0; + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + + if (event) { + parallelsDriverLock(privconn); + parallelsDomainEventQueue(privconn, event); + parallelsDriverUnlock(privconn); + } + return ret; +}
One comment I'd have here is that the event dispatch only works within the context of this single virConnectPtr instance. If you have 2 active connections to libvirt and one starts a guest, the other connection won't see any event. Likewise if someone starts/stops something using the parallels tools directly no events will get queued. Is there any way we can obtain some kind of notification from the core parallels software stack ? Even if it is indirect eg with UserModeLinux we detect stop/start events only, using an inotify watch on the directory containing the UML monitor socket :-)
+ +static int parallelsPause(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "pause", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsPauseDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsPause, + VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_USER, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); +} + +static int parallelsResume(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "resume", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsResumeDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_PAUSED, "paused", + parallelsResume, + VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_UNPAUSED, + VIR_DOMAIN_EVENT_RESUMED, + VIR_DOMAIN_EVENT_RESUMED_UNPAUSED); +} + +static int parallelsStart(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "start", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsDomainCreate(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_SHUTOFF, "stopped", + parallelsStart, + VIR_DOMAIN_RUNNING, VIR_DOMAIN_EVENT_STARTED_BOOTED, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_BOOTED); +} + +static int parallelsKill(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), "--kill", NULL); +} + +static int +parallelsDestroyDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsKill, + VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_DESTROYED, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_DESTROYED); +} + +static int parallelsStop(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsShutdownDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsStop, + VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_SHUTDOWN, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN); +} + static virDriver parallelsDriver = { .no = VIR_DRV_PARALLELS, .name = "Parallels", @@ -828,6 +966,11 @@ static virDriver parallelsDriver = { .domainGetXMLDesc = parallelsDomainGetXMLDesc, /* 0.10.0 */ .domainIsPersistent = parallelsDomainIsPersistent, /* 0.10.0 */ .domainGetAutostart = parallelsDomainGetAutostart, /* 0.10.0 */ + .domainSuspend = parallelsPauseDomain, /* 0.10.0 */ + .domainResume = parallelsResumeDomain, /* 0.10.0 */ + .domainDestroy = parallelsDestroyDomain, /* 0.10.0 */ + .domainShutdown = parallelsShutdownDomain, /* 0.10.0 */ + .domainCreate = parallelsDomainCreate, /* 0.10.0 */ };
I think I'd be happier if the creation /queueing of the virDomainEventPtr parts where commented out, at least until we have dispatch of them wired up, so the queue doesn't build up unbounded 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 :|

On 07/27/2012 01:32 PM, Daniel P. Berrange wrote:
On Thu, Jul 26, 2012 at 10:32:02PM +0400, Dmitry Guryanov wrote:
Add functions for create/shutdown/destroy and suspend/resume domain.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 143 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_utils.c | 18 +++++ src/parallels/parallels_utils.h | 2 + 3 files changed, 163 insertions(+), 0 deletions(-)
diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index c716b25..8c20d27 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -128,6 +128,12 @@ parallelsDomObjFreePrivate(void *p) VIR_FREE(p); };
+static void +parallelsDomainEventQueue(parallelsConnPtr driver, virDomainEventPtr event) +{ + virDomainEventStateQueue(driver->domainEventState, event); +} I don't see any code which ever deals with dispatching of the domain event state. Is this something you're just preparing now, so that you can wire up events in a future patch ?
+ static virCapsPtr parallelsBuildCapabilities(void) { @@ -805,6 +811,138 @@ parallelsDomainGetAutostart(virDomainPtr domain, int *autostart) return ret; }
+typedef int (*parallelsChangeStateFunc) (virDomainObjPtr privdom); +#define PARALLELS_UUID(x) (((parallelsDomObjPtr)(x->privateData))->uuid) + +static int +parallelsDomainChangeState(virDomainPtr domain, + virDomainState req_state, const char *req_state_name, + parallelsChangeStateFunc chstate, + virDomainState new_state, int reason, + int event_type, int event_detail) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + virDomainEventPtr event = NULL; + int state; + int ret = -1; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + state = virDomainObjGetState(privdom, NULL); + if (state != req_state) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("domain '%s' not %s"), + privdom->def->name, req_state_name); + goto cleanup; + } + + if (chstate(privdom)) + goto cleanup; + + virDomainObjSetState(privdom, new_state, reason); + + event = virDomainEventNewFromObj(privdom, event_type, event_detail); + ret = 0; + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + + if (event) { + parallelsDriverLock(privconn); + parallelsDomainEventQueue(privconn, event); + parallelsDriverUnlock(privconn); + } + return ret; +} One comment I'd have here is that the event dispatch only works within the context of this single virConnectPtr instance. If you have 2 active connections to libvirt and one starts a guest, the other connection won't see any event. Likewise if someone starts/stops something using the parallels tools directly no events will get queued.
Is there any way we can obtain some kind of notification from the core parallels software stack ? Even if it is indirect eg with UserModeLinux we detect stop/start events only, using an inotify watch on the directory containing the UML monitor socket :-) I thought that events, issued with virDomainEventStateQueue affect all connections ... I think it's better to remove all code, which deals with evens then.
It's possible now to listen for event using proprietary SDK, but not with prlctl utility. We can add some mode to prlctl, in which it will print all events to stdout for example, until terminated by signal.
+ +static int parallelsPause(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "pause", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsPauseDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsPause, + VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_USER, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); +} + +static int parallelsResume(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "resume", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsResumeDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_PAUSED, "paused", + parallelsResume, + VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_UNPAUSED, + VIR_DOMAIN_EVENT_RESUMED, + VIR_DOMAIN_EVENT_RESUMED_UNPAUSED); +} + +static int parallelsStart(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "start", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsDomainCreate(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_SHUTOFF, "stopped", + parallelsStart, + VIR_DOMAIN_RUNNING, VIR_DOMAIN_EVENT_STARTED_BOOTED, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_BOOTED); +} + +static int parallelsKill(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), "--kill", NULL); +} + +static int +parallelsDestroyDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsKill, + VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_DESTROYED, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_DESTROYED); +} + +static int parallelsStop(virDomainObjPtr privdom) +{ + return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), NULL); +} + +static int +parallelsShutdownDomain(virDomainPtr domain) +{ + return parallelsDomainChangeState(domain, + VIR_DOMAIN_RUNNING, "running", + parallelsStop, + VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_SHUTDOWN, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN); +} + static virDriver parallelsDriver = { .no = VIR_DRV_PARALLELS, .name = "Parallels", @@ -828,6 +966,11 @@ static virDriver parallelsDriver = { .domainGetXMLDesc = parallelsDomainGetXMLDesc, /* 0.10.0 */ .domainIsPersistent = parallelsDomainIsPersistent, /* 0.10.0 */ .domainGetAutostart = parallelsDomainGetAutostart, /* 0.10.0 */ + .domainSuspend = parallelsPauseDomain, /* 0.10.0 */ + .domainResume = parallelsResumeDomain, /* 0.10.0 */ + .domainDestroy = parallelsDestroyDomain, /* 0.10.0 */ + .domainShutdown = parallelsShutdownDomain, /* 0.10.0 */ + .domainCreate = parallelsDomainCreate, /* 0.10.0 */ };
I think I'd be happier if the creation /queueing of the virDomainEventPtr parts where commented out, at least until we have dispatch of them wired up, so the queue doesn't build up unbounded
Daniel
-- Dmitry Guryanov

On Fri, Jul 27, 2012 at 02:26:44PM +0400, Dmitry Guryanov wrote:
On 07/27/2012 01:32 PM, Daniel P. Berrange wrote:
On Thu, Jul 26, 2012 at 10:32:02PM +0400, Dmitry Guryanov wrote:
Add functions for create/shutdown/destroy and suspend/resume domain.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 143 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_utils.c | 18 +++++ src/parallels/parallels_utils.h | 2 + 3 files changed, 163 insertions(+), 0 deletions(-)
diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index c716b25..8c20d27 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -128,6 +128,12 @@ parallelsDomObjFreePrivate(void *p) VIR_FREE(p); }; +static void +parallelsDomainEventQueue(parallelsConnPtr driver, virDomainEventPtr event) +{ + virDomainEventStateQueue(driver->domainEventState, event); +} I don't see any code which ever deals with dispatching of the domain event state. Is this something you're just preparing now, so that you can wire up events in a future patch ?
+ static virCapsPtr parallelsBuildCapabilities(void) { @@ -805,6 +811,138 @@ parallelsDomainGetAutostart(virDomainPtr domain, int *autostart) return ret; } +typedef int (*parallelsChangeStateFunc) (virDomainObjPtr privdom); +#define PARALLELS_UUID(x) (((parallelsDomObjPtr)(x->privateData))->uuid) + +static int +parallelsDomainChangeState(virDomainPtr domain, + virDomainState req_state, const char *req_state_name, + parallelsChangeStateFunc chstate, + virDomainState new_state, int reason, + int event_type, int event_detail) +{ + parallelsConnPtr privconn = domain->conn->privateData; + virDomainObjPtr privdom; + virDomainEventPtr event = NULL; + int state; + int ret = -1; + + parallelsDriverLock(privconn); + privdom = virDomainFindByUUID(&privconn->domains, domain->uuid); + parallelsDriverUnlock(privconn); + + if (privdom == NULL) { + parallelsDomNotFoundError(domain); + goto cleanup; + } + + state = virDomainObjGetState(privdom, NULL); + if (state != req_state) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("domain '%s' not %s"), + privdom->def->name, req_state_name); + goto cleanup; + } + + if (chstate(privdom)) + goto cleanup; + + virDomainObjSetState(privdom, new_state, reason); + + event = virDomainEventNewFromObj(privdom, event_type, event_detail); + ret = 0; + + cleanup: + if (privdom) + virDomainObjUnlock(privdom); + + if (event) { + parallelsDriverLock(privconn); + parallelsDomainEventQueue(privconn, event); + parallelsDriverUnlock(privconn); + } + return ret; +} One comment I'd have here is that the event dispatch only works within the context of this single virConnectPtr instance. If you have 2 active connections to libvirt and one starts a guest, the other connection won't see any event. Likewise if someone starts/stops something using the parallels tools directly no events will get queued.
Is there any way we can obtain some kind of notification from the core parallels software stack ? Even if it is indirect eg with UserModeLinux we detect stop/start events only, using an inotify watch on the directory containing the UML monitor socket :-) I thought that events, issued with virDomainEventStateQueue affect all connections ... I think it's better to remove all code, which deals with evens then.
That only happens if you are creating a stateful driver like QEMU or LXC inside libvirtd. Your parallels driver is stateless and so runs client side, rather than in libvirtd. 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 :|

Add support of collecting information about serial ports. This change is needed mostly as an example, support of other devices will be added later. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 116 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 116 insertions(+), 0 deletions(-) diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index 8c20d27..ee31e6d 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -182,6 +182,119 @@ parallelsGetCapabilities(virConnectPtr conn) return xml; } +static int +parallelsGetSerialInfo(virDomainChrDefPtr chr, + const char *name, virJSONValuePtr value) +{ + const char *tmp; + + chr->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL; + chr->targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL; + if (virStrToLong_i(name + strlen("serial"), + NULL, 10, &chr->target.port) < 0) { + parallelsParseError(); + return -1; + } + + if (virJSONValueObjectHasKey(value, "output")) { + chr->source.type = VIR_DOMAIN_CHR_TYPE_FILE; + + tmp = virJSONValueObjectGetString(value, "output"); + if (!tmp) { + parallelsParseError(); + return -1; + } + + if (!(chr->source.data.file.path = strdup(tmp))) + goto no_memory; + } else if (virJSONValueObjectHasKey(value, "socket")) { + chr->source.type = VIR_DOMAIN_CHR_TYPE_UNIX; + + tmp = virJSONValueObjectGetString(value, "socket"); + if (!tmp) { + parallelsParseError(); + return -1; + } + + if (!(chr->source.data.nix.path = strdup(tmp))) + goto no_memory; + chr->source.data.nix.listen = false; + } else if (virJSONValueObjectHasKey(value, "real")) { + chr->source.type = VIR_DOMAIN_CHR_TYPE_DEV; + + tmp = virJSONValueObjectGetString(value, "real"); + if (!tmp) { + parallelsParseError(); + return -1; + } + + if (!(chr->source.data.file.path = strdup(tmp))) + goto no_memory; + } else { + parallelsParseError(); + return -1; + } + + return 0; + + no_memory: + virReportOOMError(); + return -1; +} + +static int +parallelsAddSerialInfo(virDomainDefPtr def, + const char *key, virJSONValuePtr value) +{ + virDomainChrDefPtr chr = NULL; + + if (!(chr = virDomainChrDefNew())) + goto no_memory; + + if (parallelsGetSerialInfo(chr, key, value)) + goto cleanup; + + if (VIR_REALLOC_N(def->serials, def->nserials + 1) < 0) + goto no_memory; + + def->serials[def->nserials++] = chr; + + return 0; + + no_memory: + virReportOOMError(); + cleanup: + virDomainChrDefFree(chr); + return -1; +} + +static int +parallelsAddDomainHardware(virDomainDefPtr def, virJSONValuePtr jobj) +{ + int n, i; + virJSONValuePtr value; + const char *key; + + n = virJSONValueObjectKeysNumber(jobj); + if (n < 1) + goto cleanup; + + for (i = 0; i < n; i++) { + key = virJSONValueObjectGetKey(jobj, i); + value = virJSONValueObjectGetValue(jobj, i); + + if (STRPREFIX(key, "serial")) { + if (parallelsAddSerialInfo(def, key, value)) + goto cleanup; + } + } + + return 0; + + cleanup: + return -1; +} + /* * Must be called with privconn->lock held */ @@ -305,6 +418,9 @@ parallelsLoadDomain(parallelsConnPtr privconn, virJSONValuePtr jobj) goto cleanup; } + if (parallelsAddDomainHardware(def, jobj2) < 0) + goto cleanup; + if (!(dom = virDomainAssignDef(privconn->caps, &privconn->domains, def, false))) goto cleanup; -- 1.7.1

Add support for reading VNC parameters of the VM. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 60 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 60 insertions(+), 0 deletions(-) diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index ee31e6d..8340b69 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -295,6 +295,63 @@ parallelsAddDomainHardware(virDomainDefPtr def, virJSONValuePtr jobj) return -1; } +static int +parallelsAddVNCInfo(virDomainDefPtr def, virJSONValuePtr jobj_root) +{ + const char *tmp; + unsigned int port; + virJSONValuePtr jobj; + int ret = -1; + virDomainGraphicsDefPtr gr = NULL; + + jobj = virJSONValueObjectGet(jobj_root, "Remote display"); + if (!jobj) { + parallelsParseError(); + goto cleanup; + } + + tmp = virJSONValueObjectGetString(jobj, "mode"); + if (!tmp) { + parallelsParseError(); + goto cleanup; + } + + if (STREQ(tmp, "off")) { + ret = 0; + goto cleanup; + } + + if (VIR_ALLOC(gr) < 0) + goto no_memory; + + if (virJSONValueObjectGetNumberUint(jobj, "port", &port) < 0) { + parallelsParseError(); + goto cleanup; + } + + /* TODO: handle non-auto vnc mode */ + gr->type = VIR_DOMAIN_GRAPHICS_TYPE_VNC; + gr->data.vnc.port = port; + gr->data.vnc.autoport = 0; + gr->data.vnc.keymap = NULL; + gr->data.vnc.socket = NULL; + gr->data.vnc.auth.passwd = NULL; + gr->data.vnc.auth.expires = 0; + gr->data.vnc.auth.connected = 0; + + if (VIR_REALLOC_N(def->graphics, def->ngraphics + 1) < 0) + goto no_memory; + + def->graphics[def->ngraphics++] = gr; + return 0; + + no_memory: + virReportOOMError(); + cleanup: + virDomainGraphicsDefFree(gr); + return ret; +} + /* * Must be called with privconn->lock held */ @@ -421,6 +478,9 @@ parallelsLoadDomain(parallelsConnPtr privconn, virJSONValuePtr jobj) if (parallelsAddDomainHardware(def, jobj2) < 0) goto cleanup; + if (parallelsAddVNCInfo(def, jobj) < 0) + goto cleanup; + if (!(dom = virDomainAssignDef(privconn->caps, &privconn->domains, def, false))) goto cleanup; -- 1.7.1

On Thu, Jul 26, 2012 at 10:34:32PM +0400, Dmitry Guryanov wrote:
Add support for reading VNC parameters of the VM.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 60 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 60 insertions(+), 0 deletions(-)
diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index ee31e6d..8340b69 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -295,6 +295,63 @@ parallelsAddDomainHardware(virDomainDefPtr def, virJSONValuePtr jobj) return -1; }
+static int +parallelsAddVNCInfo(virDomainDefPtr def, virJSONValuePtr jobj_root) +{ + const char *tmp; + unsigned int port; + virJSONValuePtr jobj; + int ret = -1; + virDomainGraphicsDefPtr gr = NULL; + + jobj = virJSONValueObjectGet(jobj_root, "Remote display"); + if (!jobj) { + parallelsParseError(); + goto cleanup; + } + + tmp = virJSONValueObjectGetString(jobj, "mode"); + if (!tmp) { + parallelsParseError(); + goto cleanup; + } + + if (STREQ(tmp, "off")) { + ret = 0; + goto cleanup; + } + + if (VIR_ALLOC(gr) < 0) + goto no_memory; + + if (virJSONValueObjectGetNumberUint(jobj, "port", &port) < 0) { + parallelsParseError(); + goto cleanup; + } + + /* TODO: handle non-auto vnc mode */ + gr->type = VIR_DOMAIN_GRAPHICS_TYPE_VNC; + gr->data.vnc.port = port; + gr->data.vnc.autoport = 0; + gr->data.vnc.keymap = NULL; + gr->data.vnc.socket = NULL; + gr->data.vnc.auth.passwd = NULL; + gr->data.vnc.auth.expires = 0; + gr->data.vnc.auth.connected = 0;
Do you have any information about what address the VNC server listens on ? eg 127.0.0.1 vs 0.0.0.0, or something else. If so, its desirable to fill in the listen address details. No worries if it isn't easily available though.
+ + if (VIR_REALLOC_N(def->graphics, def->ngraphics + 1) < 0) + goto no_memory; + + def->graphics[def->ngraphics++] = gr; + return 0; + + no_memory: + virReportOOMError(); + cleanup: + virDomainGraphicsDefFree(gr); + return ret; +} + /* * Must be called with privconn->lock held */ @@ -421,6 +478,9 @@ parallelsLoadDomain(parallelsConnPtr privconn, virJSONValuePtr jobj) if (parallelsAddDomainHardware(def, jobj2) < 0) goto cleanup;
+ if (parallelsAddVNCInfo(def, jobj) < 0) + goto cleanup; + if (!(dom = virDomainAssignDef(privconn->caps, &privconn->domains, def, false))) goto cleanup;
ACK, regardless of my above comment 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 :|

Add parallelsDomainDefineXML function, it works only for existing domains for the present. It's too hard to convert libvirt's XML domain configuration into Parallel's one, so I've decided to compare virDomainDef structures: current domain definition and the one created from XML, given to the function. And change only different parameters. Currently only name, description, number of cpus, memory amount and video memory can be changed. Video device and console added, because libvirt supposes that VM must always have one video device, if there are some graphics and one console. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 486 +++++++++++++++++++++++++++++++++++++- 1 files changed, 482 insertions(+), 4 deletions(-) diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index 8340b69..ae4bcef 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -243,7 +243,7 @@ parallelsGetSerialInfo(virDomainChrDefPtr chr, } static int -parallelsAddSerialInfo(virDomainDefPtr def, +parallelsAddSerialInfo(virDomainChrDefPtr **serials, int *nserials, const char *key, virJSONValuePtr value) { virDomainChrDefPtr chr = NULL; @@ -254,10 +254,10 @@ parallelsAddSerialInfo(virDomainDefPtr def, if (parallelsGetSerialInfo(chr, key, value)) goto cleanup; - if (VIR_REALLOC_N(def->serials, def->nserials + 1) < 0) + if (VIR_REALLOC_N(*serials, *nserials + 1) < 0) goto no_memory; - def->serials[def->nserials++] = chr; + (*serials)[(*nserials)++] = chr; return 0; @@ -269,6 +269,55 @@ parallelsAddSerialInfo(virDomainDefPtr def, } static int +parallelsAddVideoInfo(virDomainDefPtr def, virJSONValuePtr value) +{ + virDomainVideoDefPtr video = NULL; + virDomainVideoAccelDefPtr accel = NULL; + const char *tmp; + char *endptr; + unsigned long mem; + + if (!(tmp = virJSONValueObjectGetString(value, "size"))) { + parallelsParseError(); + goto cleanup; + } + + if (virStrToLong_ul(tmp, &endptr, 10, &mem) < 0) { + parallelsParseError(); + goto cleanup; + } + + if (!STREQ(endptr, "Mb")) { + parallelsParseError(); + goto cleanup; + } + + if (VIR_ALLOC(video) < 0) + goto no_memory; + + if (VIR_ALLOC(accel) < 0) + goto no_memory; + + if (VIR_REALLOC_N(def->videos, def->nvideos) < 0) + goto no_memory; + + def->videos[def->nvideos++] = video; + + video->type = VIR_DOMAIN_VIDEO_TYPE_VGA; + video->vram = mem << 20; + video->heads = 1; + video->accel = accel; + + return 0; + +no_memory: + virReportOOMError(); +cleanup: + virDomainVideoDefFree(video); + return -1; +} + +static int parallelsAddDomainHardware(virDomainDefPtr def, virJSONValuePtr jobj) { int n, i; @@ -284,7 +333,16 @@ parallelsAddDomainHardware(virDomainDefPtr def, virJSONValuePtr jobj) value = virJSONValueObjectGetValue(jobj, i); if (STRPREFIX(key, "serial")) { - if (parallelsAddSerialInfo(def, key, value)) + if (parallelsAddSerialInfo(&def->serials, + &def->nserials, key, value)) + goto cleanup; + if (def->nconsoles == 0) { + if (parallelsAddSerialInfo(&def->consoles, + &def->nconsoles, key, value)) + goto cleanup; + } + } else if (STREQ(key, "video")) { + if (parallelsAddVideoInfo(def, value)) goto cleanup; } } @@ -1119,6 +1177,425 @@ parallelsShutdownDomain(virDomainPtr domain) VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN); } +static int +parallelsApplyGraphicsParams(virDomainGraphicsDefPtr *oldgraphics, int nold, + virDomainGraphicsDefPtr *newgraphics, int nnew) +{ + virDomainGraphicsDefPtr new, old; + + /* parallels server supports only 1 VNC display per VM */ + if (nold != nnew || nnew > 1) + goto error; + + if (nnew == 0) + return 0; + + if (newgraphics[0]->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC) + goto error; + + old = oldgraphics[0]; + new = newgraphics[0]; + + if (old->data.vnc.port != new->data.vnc.port || + old->data.vnc.autoport != new->data.vnc.autoport || + new->data.vnc.keymap != NULL || + new->data.vnc.socket != NULL || + !STREQ_NULLABLE(old->data.vnc.auth.passwd, new->data.vnc.auth.passwd) || + old->data.vnc.auth.expires != new->data.vnc.auth.expires || + old->data.vnc.auth.validTo != new->data.vnc.auth.validTo || + old->data.vnc.auth.connected != new->data.vnc.auth.connected) { + + goto error; + } + + return 0; +error: + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing display parameters is not supported " + "by parallels driver")); + return -1; +} + +static int +parallelsApplySerialParams(virDomainChrDefPtr *oldserials, int nold, + virDomainChrDefPtr *newserials, int nnew) +{ + if (nold != nnew) + goto error; + + for (int i = 0; i < nold; i++) { + virDomainChrDefPtr oldserial = oldserials[i]; + virDomainChrDefPtr newserial = NULL; + + for (int j = 0; j < nnew; j++) { + if (newserials[j]->target.port == oldserial->target.port) { + newserial = newserials[j]; + break; + } + } + + if (!newserial) + goto error; + + if (oldserial->source.type != newserial->source.type) + goto error; + + if ((newserial->source.type == VIR_DOMAIN_CHR_TYPE_DEV || + newserial->source.type == VIR_DOMAIN_CHR_TYPE_FILE) && + !STREQ_NULLABLE(oldserial->source.data.file.path, + newserial->source.data.file.path)) + goto error; + if(newserial->source.type == VIR_DOMAIN_CHR_TYPE_UNIX && + (!STREQ_NULLABLE(oldserial->source.data.nix.path, + newserial->source.data.nix.path) || + oldserial->source.data.nix.listen == newserial->source.data.nix.listen)) { + + goto error; + } + } + + return 0; +error: + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing serial device parameters is " + "not supported by parallels driver")); + return -1; +} + +static int +parallelsApplyVideoParams(parallelsDomObjPtr pdom, + virDomainVideoDefPtr *oldvideos, int nold, + virDomainVideoDefPtr *newvideos, int nnew) +{ + virDomainVideoDefPtr old, new; + char str_vram[32]; + + if (nold != 1 || nnew != 1) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Only one video device is " + "supported by parallels driver")); + return -1; + } + + old = oldvideos[0]; + new = newvideos[0]; + if (new->type != VIR_DOMAIN_VIDEO_TYPE_VGA) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Only VGA video device is " + "supported by parallels driver")); + return -1; + } + + if (new->heads != 1) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Only one monitor is supported by parallels driver")); + return -1; + } + + /* old->accel must be always non-NULL */ + if (new->accel == NULL || + old->accel->support2d != new->accel->support2d || + old->accel->support3d != new->accel->support3d) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Changing video acceleration parameters is " + "not supported by parallels driver")); + return -1; + } + + if (old->vram != new->vram) { + if (new->vram % (1 << 20) != 0) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Video RAM size should be multiple of 1Mb.")); + return -1; + } + + snprintf(str_vram, 31, "%d", new->vram >> 20); + str_vram[31] = '\0'; + + if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, + "--videosize", str_vram, NULL)) + return -1; + } + return 0; +} + +static int +parallelsApplyChanges(virDomainObjPtr dom, virDomainDefPtr new) +{ + char buf[32]; + + virDomainDefPtr old = dom->def; + parallelsDomObjPtr pdom = dom->privateData; + + if (new->description && !STREQ_NULLABLE(old->description, new->description)) { + if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, + "--description", new->description, NULL)) + return -1; + } + + if (new->name && !STREQ_NULLABLE(old->name, new->name)) { + if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, + "--name", new->name, NULL)) + return -1; + } + + if (new->title && !STREQ_NULLABLE(old->title, new->title)) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("titles are not supported by parallels driver")); + return -1; + } + + if (new->blkio.ndevices > 0) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("blkio parameters are not supported " + "by parallels driver")); + return -1; + } + + if (old->mem.max_balloon != new->mem.max_balloon) { + if (new->mem.max_balloon != new->mem.cur_balloon) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing balloon parameters is not supported " + "by parallels driver")); + return -1; + } + + if (new->mem.max_balloon % (1 << 10) != 0) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Memory size should be multiple of 1Mb.")); + return -1; + } + + snprintf(buf, 31, "%llu", new->mem.max_balloon >> 10); + buf[31] = '\0'; + + if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, + "--memsize", buf, NULL)) + return -1; + } + + if (old->mem.hugepage_backed != new->mem.hugepage_backed || + old->mem.hard_limit != new->mem.hard_limit || + old->mem.soft_limit != new->mem.soft_limit || + old->mem.min_guarantee != new->mem.min_guarantee || + old->mem.swap_hard_limit != new->mem.swap_hard_limit) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Memory parameter is not supported " + "by parallels driver")); + return -1; + } + + if (old->vcpus != new->vcpus) { + if (new->vcpus != new->maxvcpus) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("current vcpus must be equal to maxvcpus")); + return -1; + } + + snprintf(buf, 31, "%d", new->vcpus); + buf[31] = '\0'; + + if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, + "--cpus", buf, NULL)) + return -1; + } + + if (old->placement_mode != new->placement_mode) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing cpu placement mode is not supported " + "by parallels driver")); + return -1; + } + + if (old->cpumasklen != new->cpumasklen || + (memcmp(old->cpumask, new->cpumask, old->cpumasklen))) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing cpu mask is not supported " + "by parallels driver")); + return -1; + } + + if (old->cputune.shares != new->cputune.shares || + old->cputune.period != new->cputune.period || + old->cputune.quota != new->cputune.quota || + old->cputune.nvcpupin != new->cputune.nvcpupin) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("cputune is not supported by parallels driver")); + return -1; + } + + if (old->numatune.memory.mode != new->numatune.memory.mode || + old->numatune.memory.placement_mode != new->numatune.memory.placement_mode || + !STREQ_NULLABLE(old->numatune.memory.nodemask, new->numatune.memory.nodemask)) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("numa parameters are not supported " + "by parallels driver")); + return -1; + } + + if (old->onReboot != new->onReboot || + old->onPoweroff != new->onPoweroff || + old->onCrash != new->onCrash) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("on_reboot, on_poweroff and on_crash parameters " + "are not supported by parallels driver")); + return -1; + } + + /* we fill only type and arch fields in parallelsLoadDomain, so + * we can check that all other paramenters are null */ + if (!STREQ_NULLABLE(old->os.type, new->os.type) || + !STREQ_NULLABLE(old->os.arch, new->os.arch) || + new->os.machine != NULL || new->os.nBootDevs != 1 || + new->os.bootDevs[0] != VIR_DOMAIN_BOOT_DISK || + new->os.bootmenu != 0 || new->os.init != NULL || + new->os.initargv != NULL || new->os.kernel != NULL || + new->os.initrd != NULL || new->os.cmdline != NULL || + new->os.root != NULL || new->os.loader != NULL || + new->os.bootloader != NULL || new->os.bootloaderArgs != NULL || + new->os.smbios_mode != 0 || new->os.bios.useserial != 0) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing OS parameters is not supported " + "by parallels driver")); + return -1; + } + + if (!STREQ_NULLABLE(old->emulator, new->emulator)) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing emulator is not supported " + "by parallels driver")); + return -1; + } + + if (old->features != new->features) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing features is not supported " + "by parallels driver")); + return -1; + } + + if (new->clock.offset != VIR_DOMAIN_CLOCK_OFFSET_UTC || + new->clock.ntimers != 0) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing clock parameters is not supported " + "by parallels driver")); + return -1; + } + + if (parallelsApplyGraphicsParams(old->graphics, old->ngraphics, + new->graphics, new->ngraphics) < 0) + return -1; + + if (new->ndisks != 0 || new->ncontrollers != 0 || + new->nfss != 0 || new->nnets != 0 || + new->nsounds != 0 || new->nhostdevs != 0 || + new->nredirdevs != 0 || new->nsmartcards != 0 || + new->nparallels || new->nchannels != 0 || + new->nleases != 0 || new->nhubs != 0) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing devices parameters is not supported " + "by parallels driver")); + return -1; + } + + /* there may be one auto-input */ + if (new->ninputs > 1 || + (new->ninputs > 1 && + (new->inputs[0]->type != VIR_DOMAIN_INPUT_TYPE_MOUSE || + new->inputs[0]->bus != VIR_DOMAIN_INPUT_BUS_PS2))) { + + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("changing input devices parameters is not supported " + "by parallels driver")); + } + + + if (parallelsApplySerialParams(old->serials, old->nserials, + new->serials, new->nserials) < 0) + return -1; + + if (parallelsApplySerialParams(old->consoles, old->nconsoles, + new->consoles, new->nconsoles) < 0) + return -1; + + if (parallelsApplyVideoParams(pdom, old->videos, old->nvideos, + new->videos, new->nvideos) < 0) + return -1; + return 0; +} + +static virDomainPtr +parallelsDomainDefineXML(virConnectPtr conn, const char *xml) +{ + parallelsConnPtr privconn = conn->privateData; + virDomainPtr ret = NULL; + virDomainDefPtr def; + virDomainObjPtr dom = NULL, olddom = NULL; + virDomainEventPtr event = NULL; + int dupVM; + + parallelsDriverLock(privconn); + if ((def = virDomainDefParseString(privconn->caps, xml, + 1 << VIR_DOMAIN_VIRT_PARALLELS, + VIR_DOMAIN_XML_INACTIVE)) == NULL) { + virReportError(VIR_ERR_INVALID_ARG, _("Can't parse XML desc")); + goto cleanup; + } + + if ((dupVM = virDomainObjIsDuplicate(&privconn->domains, def, 0)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, _("Already exists")); + goto cleanup; + } + + if (dupVM == 1) { + olddom = virDomainFindByUUID(&privconn->domains, def->uuid); + if (parallelsApplyChanges(olddom, def) < 0) { + virDomainObjUnlock(olddom); + goto cleanup; + } + virDomainObjUnlock(olddom); + + if (!(dom = virDomainAssignDef(privconn->caps, + &privconn->domains, def, false))) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Can't allocate domobj")); + goto cleanup; + } + + def = NULL; + } else { + virReportError(VIR_ERR_NO_SUPPORT, _("Not implemented yet")); + goto cleanup; + } + + event = virDomainEventNewFromObj(dom, + VIR_DOMAIN_EVENT_DEFINED, + !dupVM ? + VIR_DOMAIN_EVENT_DEFINED_ADDED : + VIR_DOMAIN_EVENT_DEFINED_UPDATED); + + ret = virGetDomain(conn, dom->def->name, dom->def->uuid); + if (ret) + ret->id = dom->def->id; + + cleanup: + virDomainDefFree(def); + if (dom) + virDomainObjUnlock(dom); + if (event) + parallelsDomainEventQueue(privconn, event); + parallelsDriverUnlock(privconn); + return ret; +} + static virDriver parallelsDriver = { .no = VIR_DRV_PARALLELS, .name = "Parallels", @@ -1147,6 +1624,7 @@ static virDriver parallelsDriver = { .domainDestroy = parallelsDestroyDomain, /* 0.10.0 */ .domainShutdown = parallelsShutdownDomain, /* 0.10.0 */ .domainCreate = parallelsDomainCreate, /* 0.10.0 */ + .domainDefineXML = parallelsDomainDefineXML, /* 0.10.0 */ }; /** -- 1.7.1

Parallels Cloud Server has one serious discrepancy with libvirt: libvirt stores domain configuration files in one place, and storage files in other places (with the API of storage pools and storage volumes). Parallels Cloud Server stores all domain data in a single directory, for example, you may have domain with name fedora-15, which will be located in '/var/parallels/fedora-15.pvm', and it's hard disk image will be in '/var/parallels/fedora-15.pvm/harddisk1.hdd'. I've decided to create storage driver, which produces pseudo-volumes (xml files with volume description), and they will be 'converted' to real disk images after attaching to a VM. So if someone creates VM with one hard disk using virt-manager, at first virt-manager creates a new volume, and then defines a domain. We can lookup a volume by path in XML domain definition and find out location of new domain and size of its hard disk. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/parallels/parallels_driver.c | 30 +- src/parallels/parallels_storage.c | 1387 +++++++++++++++++++++++++++++++++++++ src/parallels/parallels_utils.c | 22 + src/parallels/parallels_utils.h | 31 + 6 files changed, 1448 insertions(+), 26 deletions(-) create mode 100644 src/parallels/parallels_storage.c diff --git a/po/POTFILES.in b/po/POTFILES.in index e952f2d..18373b5 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -66,6 +66,7 @@ src/openvz/openvz_driver.c src/openvz/openvz_util.c src/parallels/parallels_driver.c src/parallels/parallels_utils.c +src/parallels/parallels_storage.c src/phyp/phyp_driver.c src/qemu/qemu_agent.c src/qemu/qemu_bridge_filter.c diff --git a/src/Makefile.am b/src/Makefile.am index eac8d99..0710bd1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -486,7 +486,8 @@ HYPERV_DRIVER_EXTRA_DIST = \ PARALLELS_DRIVER_SOURCES = \ parallels/parallels_driver.h \ parallels/parallels_driver.c \ - parallels/parallels_utils.c + parallels/parallels_utils.c \ + parallels/parallels_storage.c NETWORK_DRIVER_SOURCES = \ network/bridge_driver.h network/bridge_driver.c diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index ae4bcef..80ff709 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -50,10 +50,6 @@ #include "configmake.h" #include "storage_file.h" #include "nodeinfo.h" -#include "json.h" -#include "domain_conf.h" -#include "storage_conf.h" -#include "domain_event.h" #include "virdomainlist.h" #include "parallels_driver.h" @@ -76,34 +72,15 @@ virReportErrorHelper(VIR_FROM_TEST, VIR_ERR_OPERATION_FAILED, __FILE__, \ __FUNCTION__, __LINE__, _("Can't parse prlctl output")) -struct _parallelsConn { - virMutex lock; - virDomainObjList domains; - virStoragePoolObjList pools; - virCapsPtr caps; - virDomainEventStatePtr domainEventState; -}; - -typedef struct _parallelsConn parallelsConn; -typedef struct _parallelsConn *parallelsConnPtr; - -struct parallelsDomObj { - int id; - char *uuid; - char *os; -}; - -typedef struct parallelsDomObj *parallelsDomObjPtr; - static int parallelsClose(virConnectPtr conn); -static void +void parallelsDriverLock(parallelsConnPtr driver) { virMutexLock(&driver->lock); } -static void +void parallelsDriverUnlock(parallelsConnPtr driver) { virMutexUnlock(&driver->lock); @@ -662,6 +639,7 @@ parallelsOpen(virConnectPtr conn, { int ret; parallelsConnPtr privconn; + virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); if (!conn->uri) @@ -1648,6 +1626,8 @@ parallelsRegister(void) if (virRegisterDriver(¶llelsDriver) < 0) return -1; + if (parallelsStorageRegister()) + return -1; return 0; } diff --git a/src/parallels/parallels_storage.c b/src/parallels/parallels_storage.c new file mode 100644 index 0000000..a7fdaed --- /dev/null +++ b/src/parallels/parallels_storage.c @@ -0,0 +1,1387 @@ +/* + * parallels_storage.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 <stdlib.h> +#include <dirent.h> +#include <sys/statvfs.h> + +#include "datatypes.h" +#include "memory.h" +#include "configmake.h" +#include "storage_file.h" +#include "virterror_internal.h" + +#include "parallels_utils.h" + +#define VIR_FROM_THIS VIR_FROM_PARALLELS + +#define parallelsPoolNotFoundError(pool_name) \ + virReportError(VIR_ERR_INVALID_ARG, \ + _("pool '%s' not found"), pool_name); + +static virStorageVolDefPtr +parallelsStorageVolumeDefine(virStoragePoolObjPtr pool, const char *xmldesc, + const char *xmlfile, bool is_new); +static virStorageVolPtr +parallelsStorageVolumeLookupByPathLocked(virConnectPtr conn, const char *path); + +static virStorageVolPtr +parallelsStorageVolumeLookupByPath(virConnectPtr conn, const char *path); + +static int +parallelsStoragePoolGetAlloc(virStoragePoolDefPtr def); + +static void +parallelsStorageLock(virStorageDriverStatePtr driver) +{ + virMutexLock(&driver->lock); +} + +static void +parallelsStorageUnlock(virStorageDriverStatePtr driver) +{ + virMutexUnlock(&driver->lock); +} + +static int +parallelsStorageClose(virConnectPtr conn) +{ + parallelsConnPtr privconn = conn->privateData; + + virStorageDriverStatePtr storageState = conn->storagePrivateData; + conn->storagePrivateData = NULL; + + parallelsStorageLock(storageState); + virStoragePoolObjListFree(&privconn->pools); + VIR_FREE(storageState->configDir); + VIR_FREE(storageState->autostartDir); + parallelsStorageUnlock(storageState); + virMutexDestroy(&storageState->lock); + VIR_FREE(storageState); + + return 0; +} + +static int +parallelsFindVolumes(virStoragePoolObjPtr pool) +{ + DIR *dir; + struct dirent *ent; + char *path; + + if (!(dir = opendir(pool->def->target.path))) { + virReportSystemError(errno, + _("cannot open path '%s'"), + pool->def->target.path); + goto cleanup; + } + + while ((ent = readdir(dir)) != NULL) { + if (!virFileHasSuffix(ent->d_name, ".xml")) + continue; + + if (!(path = virFileBuildPath(pool->def->target.path, + ent->d_name, NULL))) + goto no_memory; + if (!parallelsStorageVolumeDefine(pool, NULL, path, false)) + goto cleanup; + VIR_FREE(path); + } + + return 0; +no_memory: + virReportOOMError(); +cleanup: + return -1; + +} + +static virDrvOpenStatus +parallelsStorageOpen(virConnectPtr conn, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + unsigned int flags) +{ + char *base = NULL; + virStorageDriverStatePtr storageState; + int privileged = (geteuid() == 0); + parallelsConnPtr privconn = conn->privateData; + virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); + + if (STRNEQ(conn->driver->name, "Parallels")) + return VIR_DRV_OPEN_DECLINED; + + if (VIR_ALLOC(storageState) < 0) { + virReportOOMError(); + return VIR_DRV_OPEN_ERROR; + } + + if (virMutexInit(&storageState->lock) < 0) { + VIR_FREE(storageState); + return VIR_DRV_OPEN_ERROR; + } + parallelsStorageLock(storageState); + + if (privileged) { + if ((base = strdup(SYSCONFDIR "/libvirt")) == NULL) + goto out_of_memory; + } else { + char *userdir = virGetUserDirectory(); + + if (!userdir) + goto error; + + if (virAsprintf(&base, "%s/.libvirt", userdir) == -1) { + VIR_FREE(userdir); + goto out_of_memory; + } + VIR_FREE(userdir); + } + + /* Configuration paths are either ~/.libvirt/storage/... (session) or + * /etc/libvirt/storage/... (system). + */ + if (virAsprintf(&storageState->configDir, + "%s/parallels-storage", base) == -1) + goto out_of_memory; + + if (virAsprintf(&storageState->autostartDir, + "%s/parallels-storage/autostart", base) == -1) + goto out_of_memory; + + VIR_FREE(base); + + if (virStoragePoolLoadAllConfigs(&privconn->pools, + storageState->configDir, + storageState->autostartDir) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Failed to load pool configs")); + goto error; + } + + for (int i = 0; i < privconn->pools.count; i++) { + virStoragePoolObjLock(privconn->pools.objs[i]); + virStoragePoolObjPtr pool; + + pool = privconn->pools.objs[i]; + pool->active = 1; + + if (parallelsStoragePoolGetAlloc(pool->def) < 0) + goto error; + + if (parallelsFindVolumes(pool) < 0) + goto error; + + virStoragePoolObjUnlock(privconn->pools.objs[i]); + } + + parallelsStorageUnlock(storageState); + + conn->storagePrivateData = storageState; + + return VIR_DRV_OPEN_SUCCESS; + +out_of_memory: + virReportOOMError(); +error: + VIR_FREE(base); + parallelsStorageUnlock(storageState); + parallelsStorageClose(conn); + return -1; +} + +static int +parallelsStorageNumPools(virConnectPtr conn) +{ + parallelsConnPtr privconn = conn->privateData; + int numActive = 0, i; + + parallelsDriverLock(privconn); + for (i = 0; i < privconn->pools.count; i++) + if (virStoragePoolObjIsActive(privconn->pools.objs[i])) + numActive++; + parallelsDriverUnlock(privconn); + + return numActive; +} + +static int +parallelsStorageListPools(virConnectPtr conn, char **const names, int nnames) +{ + parallelsConnPtr privconn = conn->privateData; + int n = 0, i; + + parallelsDriverLock(privconn); + memset(names, 0, sizeof(*names) * nnames); + for (i = 0; i < privconn->pools.count && n < nnames; i++) { + virStoragePoolObjLock(privconn->pools.objs[i]); + if (virStoragePoolObjIsActive(privconn->pools.objs[i]) && + !(names[n++] = strdup(privconn->pools.objs[i]->def->name))) { + virStoragePoolObjUnlock(privconn->pools.objs[i]); + goto no_memory; + } + virStoragePoolObjUnlock(privconn->pools.objs[i]); + } + parallelsDriverUnlock(privconn); + + return n; + +no_memory: + virReportOOMError(); + for (n = 0; n < nnames; n++) + VIR_FREE(names[n]); + parallelsDriverUnlock(privconn); + return -1; +} + +static int +parallelsStorageNumDefinedPools(virConnectPtr conn) +{ + parallelsConnPtr privconn = conn->privateData; + int numInactive = 0, i; + + parallelsDriverLock(privconn); + for (i = 0; i < privconn->pools.count; i++) { + virStoragePoolObjLock(privconn->pools.objs[i]); + if (!virStoragePoolObjIsActive(privconn->pools.objs[i])) + numInactive++; + virStoragePoolObjUnlock(privconn->pools.objs[i]); + } + parallelsDriverUnlock(privconn); + + return numInactive; +} + +static int +parallelsStorageListDefinedPools(virConnectPtr conn, + char **const names, int nnames) +{ + parallelsConnPtr privconn = conn->privateData; + int n = 0, i; + + parallelsDriverLock(privconn); + memset(names, 0, sizeof(*names) * nnames); + for (i = 0; i < privconn->pools.count && n < nnames; i++) { + virStoragePoolObjLock(privconn->pools.objs[i]); + if (!virStoragePoolObjIsActive(privconn->pools.objs[i]) && + !(names[n++] = strdup(privconn->pools.objs[i]->def->name))) { + virStoragePoolObjUnlock(privconn->pools.objs[i]); + goto no_memory; + } + virStoragePoolObjUnlock(privconn->pools.objs[i]); + } + parallelsDriverUnlock(privconn); + + return n; + +no_memory: + virReportOOMError(); + for (n = 0; n < nnames; n++) + VIR_FREE(names[n]); + parallelsDriverUnlock(privconn); + return -1; +} + + +static int +parallelsStoragePoolIsActive(virStoragePoolPtr pool) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr obj; + int ret = -1; + + parallelsDriverLock(privconn); + obj = virStoragePoolObjFindByUUID(&privconn->pools, pool->uuid); + parallelsDriverUnlock(privconn); + if (!obj) { + virReportError(VIR_ERR_NO_STORAGE_POOL, NULL); + goto cleanup; + } + ret = virStoragePoolObjIsActive(obj); + +cleanup: + if (obj) + virStoragePoolObjUnlock(obj); + return ret; +} + +static int +parallelsStoragePoolIsPersistent(virStoragePoolPtr pool ATTRIBUTE_UNUSED) +{ + return 1; +} + +static virStoragePoolPtr +parallelsStoragePoolLookupByUUID(virConnectPtr conn, const unsigned char *uuid) +{ + parallelsConnPtr privconn = conn->privateData; + virStoragePoolObjPtr pool; + virStoragePoolPtr ret = NULL; + + parallelsDriverLock(privconn); + pool = virStoragePoolObjFindByUUID(&privconn->pools, uuid); + parallelsDriverUnlock(privconn); + + if (pool == NULL) { + virReportError(VIR_ERR_NO_STORAGE_POOL, NULL); + goto cleanup; + } + + ret = virGetStoragePool(conn, pool->def->name, pool->def->uuid); + +cleanup: + if (pool) + virStoragePoolObjUnlock(pool); + return ret; +} + +static virStoragePoolPtr +parallelsStoragePoolLookupByName(virConnectPtr conn, const char *name) +{ + parallelsConnPtr privconn = conn->privateData; + virStoragePoolObjPtr pool; + virStoragePoolPtr ret = NULL; + + parallelsDriverLock(privconn); + pool = virStoragePoolObjFindByName(&privconn->pools, name); + parallelsDriverUnlock(privconn); + + if (pool == NULL) { + virReportError(VIR_ERR_NO_STORAGE_POOL, NULL); + goto cleanup; + } + + ret = virGetStoragePool(conn, pool->def->name, pool->def->uuid); + +cleanup: + if (pool) + virStoragePoolObjUnlock(pool); + return ret; +} + +static virStoragePoolPtr +parallelsStoragePoolLookupByVolume(virStorageVolPtr vol) +{ + return parallelsStoragePoolLookupByName(vol->conn, vol->pool); +} + +/* + * Fill capacity, available and allocation + * fields in pool definition. + */ +static int +parallelsStoragePoolGetAlloc(virStoragePoolDefPtr def) +{ + struct statvfs sb; + + if (statvfs(def->target.path, &sb) < 0) { + virReportSystemError(errno, + _("cannot statvfs path '%s'"), + def->target.path); + return -1; + } + + def->capacity = ((unsigned long long)sb.f_frsize * + (unsigned long long)sb.f_blocks); + def->available = ((unsigned long long)sb.f_bfree * + (unsigned long long)sb.f_bsize); + def->allocation = def->capacity - def->available; + + return 0; +} + +static virStoragePoolPtr +parallelsStoragePoolDefine(virConnectPtr conn, + const char *xml, unsigned int flags) +{ + parallelsConnPtr privconn = conn->privateData; + virStoragePoolDefPtr def; + virStoragePoolObjPtr pool = NULL; + virStoragePoolPtr ret = NULL; + + virCheckFlags(0, NULL); + + parallelsDriverLock(privconn); + if (!(def = virStoragePoolDefParseString(xml))) + goto cleanup; + + if (def->type != VIR_STORAGE_POOL_DIR) { + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("Only local directories are supported")); + goto cleanup; + } + + if (virStoragePoolObjIsDuplicate(&privconn->pools, def, 0) < 0) + goto cleanup; + + if (virStoragePoolSourceFindDuplicate(&privconn->pools, def) < 0) + goto cleanup; + + if (parallelsStoragePoolGetAlloc(def)) + goto cleanup; + + if (!(pool = virStoragePoolObjAssignDef(&privconn->pools, def))) + goto cleanup; + + if (virStoragePoolObjSaveDef(conn->storagePrivateData, pool, def) < 0) { + virStoragePoolObjRemove(&privconn->pools, pool); + def = NULL; + goto cleanup; + } + def = NULL; + + pool->configFile = strdup("\0"); + if (!pool->configFile) { + virReportOOMError(); + goto cleanup; + } + + ret = virGetStoragePool(conn, pool->def->name, pool->def->uuid); + +cleanup: + virStoragePoolDefFree(def); + if (pool) + virStoragePoolObjUnlock(pool); + parallelsDriverUnlock(privconn); + return ret; +} + +static int +parallelsStoragePoolUndefine(virStoragePoolPtr pool) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is still active"), pool->name); + goto cleanup; + } + + if (virStoragePoolObjDeleteDef(privpool) < 0) + goto cleanup; + + VIR_FREE(privpool->configFile); + + virStoragePoolObjRemove(&privconn->pools, privpool); + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + parallelsDriverUnlock(privconn); + return ret; +} + +static int +parallelsStoragePoolStart(virStoragePoolPtr pool, unsigned int flags) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + virCheckFlags(0, -1); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is already active"), pool->name); + goto cleanup; + } + + privpool->active = 1; + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static int +parallelsStoragePoolDestroy(virStoragePoolPtr pool) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), pool->name); + goto cleanup; + } + + if (privpool->configFile == NULL) { + virStoragePoolObjRemove(&privconn->pools, privpool); + privpool = NULL; + } + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + parallelsDriverUnlock(privconn); + return ret; +} + +static int +parallelsStoragePoolRefresh(virStoragePoolPtr pool, unsigned int flags) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + virCheckFlags(0, -1); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), pool->name); + goto cleanup; + } + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + + +static int +parallelsStoragePoolGetInfo(virStoragePoolPtr pool, virStoragePoolInfoPtr info) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + memset(info, 0, sizeof(virStoragePoolInfo)); + if (privpool->active) + info->state = VIR_STORAGE_POOL_RUNNING; + else + info->state = VIR_STORAGE_POOL_INACTIVE; + info->capacity = privpool->def->capacity; + info->allocation = privpool->def->allocation; + info->available = privpool->def->available; + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static char * +parallelsStoragePoolGetXMLDesc(virStoragePoolPtr pool, unsigned int flags) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + char *ret = NULL; + + virCheckFlags(0, NULL); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + ret = virStoragePoolDefFormat(privpool->def); + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static int +parallelsStoragePoolGetAutostart(virStoragePoolPtr pool, int *autostart) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (!privpool->configFile) { + *autostart = 0; + } else { + *autostart = privpool->autostart; + } + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static int +parallelsStoragePoolSetAutostart(virStoragePoolPtr pool, int autostart) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (!privpool->configFile) { + virReportError(VIR_ERR_INVALID_ARG, "%s", _("pool has no config file")); + goto cleanup; + } + + privpool->autostart = (autostart != 0); + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static int +parallelsStoragePoolNumVolumes(virStoragePoolPtr pool) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int ret = -1; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), pool->name); + goto cleanup; + } + + ret = privpool->volumes.count; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static int +parallelsStoragePoolListVolumes(virStoragePoolPtr pool, + char **const names, int maxnames) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + int i = 0, n = 0; + + memset(names, 0, maxnames * sizeof(*names)); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto error; + } + + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), pool->name); + goto error; + } + + for (i = 0; i < privpool->volumes.count && n < maxnames; i++) { + if ((names[n++] = strdup(privpool->volumes.objs[i]->name)) == NULL) { + virReportOOMError(); + goto error; + } + } + + virStoragePoolObjUnlock(privpool); + return n; + +error: + for (n = 0; n < maxnames; n++) + VIR_FREE(names[i]); + + if (privpool) + virStoragePoolObjUnlock(privpool); + return -1; +} + +static virStorageVolPtr +parallelsStorageVolumeLookupByName(virStoragePoolPtr pool, + const char *name) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + virStorageVolDefPtr privvol; + virStorageVolPtr ret = NULL; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), pool->name); + goto cleanup; + } + + privvol = virStorageVolDefFindByName(privpool, name); + + if (!privvol) { + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), name); + goto cleanup; + } + + ret = virGetStorageVol(pool->conn, privpool->def->name, + privvol->name, privvol->key); + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + + +static virStorageVolPtr +parallelsStorageVolumeLookupByKey(virConnectPtr conn, const char *key) +{ + parallelsConnPtr privconn = conn->privateData; + unsigned int i; + virStorageVolPtr ret = NULL; + + parallelsDriverLock(privconn); + for (i = 0; i < privconn->pools.count; i++) { + virStoragePoolObjLock(privconn->pools.objs[i]); + if (virStoragePoolObjIsActive(privconn->pools.objs[i])) { + virStorageVolDefPtr privvol = + virStorageVolDefFindByKey(privconn->pools.objs[i], key); + + if (privvol) { + ret = virGetStorageVol(conn, + privconn->pools.objs[i]->def->name, + privvol->name, privvol->key); + virStoragePoolObjUnlock(privconn->pools.objs[i]); + break; + } + } + virStoragePoolObjUnlock(privconn->pools.objs[i]); + } + parallelsDriverUnlock(privconn); + + if (!ret) + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching key '%s'"), key); + + return ret; +} + +static virStorageVolPtr +parallelsStorageVolumeLookupByPathLocked(virConnectPtr conn, const char *path) +{ + parallelsConnPtr privconn = conn->privateData; + unsigned int i; + virStorageVolPtr ret = NULL; + + for (i = 0; i < privconn->pools.count; i++) { + virStoragePoolObjLock(privconn->pools.objs[i]); + if (virStoragePoolObjIsActive(privconn->pools.objs[i])) { + virStorageVolDefPtr privvol = + virStorageVolDefFindByPath(privconn->pools.objs[i], path); + + if (privvol) { + ret = virGetStorageVol(conn, + privconn->pools.objs[i]->def->name, + privvol->name, privvol->key); + virStoragePoolObjUnlock(privconn->pools.objs[i]); + break; + } + } + virStoragePoolObjUnlock(privconn->pools.objs[i]); + } + + if (!ret) + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching path '%s'"), path); + + return ret; +} + +static virStorageVolPtr +parallelsStorageVolumeLookupByPath(virConnectPtr conn, const char *path) +{ + parallelsConnPtr privconn = conn->privateData; + virStorageVolPtr ret = NULL; + + parallelsDriverLock(privconn); + ret = parallelsStorageVolumeLookupByPathLocked(conn, path); + parallelsDriverUnlock(privconn); + + return ret; +} + +static virStorageVolDefPtr +parallelsStorageVolumeDefine(virStoragePoolObjPtr pool, + const char *xmldesc, + const char *xmlfile, bool is_new) +{ + virStorageVolDefPtr privvol = NULL; + virStorageVolDefPtr ret = NULL; + char *xml_path = NULL; + + if (xmlfile) + privvol = virStorageVolDefParseFile(pool->def, xmlfile); + else + privvol = virStorageVolDefParseString(pool->def, xmldesc); + + if (privvol == NULL) + goto cleanup; + + if (virStorageVolDefFindByName(pool, privvol->name)) { + virReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("storage vol already exists")); + goto cleanup; + } + + if (is_new) { + /* Make sure enough space */ + if ((pool->def->allocation + privvol->allocation) > + pool->def->capacity) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Not enough free space in pool for volume '%s'"), + privvol->name); + goto cleanup; + } + } + + if (VIR_REALLOC_N(pool->volumes.objs, pool->volumes.count + 1) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (virAsprintf(&privvol->target.path, "%s/%s", + pool->def->target.path, privvol->name) < 0) { + virReportOOMError(); + goto cleanup; + } + + privvol->key = strdup(privvol->target.path); + if (privvol->key == NULL) { + virReportOOMError(); + goto cleanup; + } + + if (is_new) { + xml_path = parallelsAddFileExt(privvol->target.path, ".xml"); + if (!xml_path) + goto cleanup; + + if (virXMLSaveFile(xml_path, privvol->name, + "volume-create", xmldesc)) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Can't create file with volume description")); + goto cleanup; + } + + pool->def->allocation += privvol->allocation; + pool->def->available = (pool->def->capacity - + pool->def->allocation); + } + + pool->volumes.objs[pool->volumes.count++] = privvol; + + ret = privvol; + privvol = NULL; + +cleanup: + virStorageVolDefFree(privvol); + VIR_FREE(xml_path); + return ret; +} + +static virStorageVolPtr +parallelsStorageVolumeCreateXML(virStoragePoolPtr pool, + const char *xmldesc, unsigned int flags) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + virStorageVolPtr ret = NULL; + virStorageVolDefPtr privvol = NULL; + + virCheckFlags(0, NULL); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), pool->name); + goto cleanup; + } + + privvol = parallelsStorageVolumeDefine(privpool, xmldesc, NULL, true); + if (!privvol) + goto cleanup; + + ret = virGetStorageVol(pool->conn, privpool->def->name, + privvol->name, privvol->key); +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static virStorageVolPtr +parallelsStorageVolumeCreateXMLFrom(virStoragePoolPtr pool, + const char *xmldesc, + virStorageVolPtr clonevol, + unsigned int flags) +{ + parallelsConnPtr privconn = pool->conn->privateData; + virStoragePoolObjPtr privpool; + virStorageVolDefPtr privvol = NULL, origvol = NULL; + virStorageVolPtr ret = NULL; + + virCheckFlags(0, NULL); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, pool->name); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(pool->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), pool->name); + goto cleanup; + } + + privvol = virStorageVolDefParseString(privpool->def, xmldesc); + if (privvol == NULL) + goto cleanup; + + if (virStorageVolDefFindByName(privpool, privvol->name)) { + virReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("storage vol already exists")); + goto cleanup; + } + + origvol = virStorageVolDefFindByName(privpool, clonevol->name); + if (!origvol) { + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), + clonevol->name); + goto cleanup; + } + + /* Make sure enough space */ + if ((privpool->def->allocation + privvol->allocation) > + privpool->def->capacity) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Not enough free space in pool for volume '%s'"), + privvol->name); + goto cleanup; + } + privpool->def->available = (privpool->def->capacity - + privpool->def->allocation); + + if (VIR_REALLOC_N(privpool->volumes.objs, + privpool->volumes.count + 1) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (virAsprintf(&privvol->target.path, "%s/%s", + privpool->def->target.path, privvol->name) == -1) { + virReportOOMError(); + goto cleanup; + } + + privvol->key = strdup(privvol->target.path); + if (privvol->key == NULL) { + virReportOOMError(); + goto cleanup; + } + + privpool->def->allocation += privvol->allocation; + privpool->def->available = (privpool->def->capacity - + privpool->def->allocation); + + privpool->volumes.objs[privpool->volumes.count++] = privvol; + + ret = virGetStorageVol(pool->conn, privpool->def->name, + privvol->name, privvol->key); + privvol = NULL; + +cleanup: + virStorageVolDefFree(privvol); + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static int +parallelsStorageVolumeDelete(virStorageVolPtr vol, unsigned int flags) +{ + parallelsConnPtr privconn = vol->conn->privateData; + virStoragePoolObjPtr privpool; + virStorageVolDefPtr privvol; + int i; + int ret = -1; + char *xml_path = NULL; + + virCheckFlags(0, -1); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, vol->pool); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(vol->pool); + goto cleanup; + } + + + privvol = virStorageVolDefFindByName(privpool, vol->name); + + if (privvol == NULL) { + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), vol->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), vol->pool); + goto cleanup; + } + + + privpool->def->allocation -= privvol->allocation; + privpool->def->available = (privpool->def->capacity - + privpool->def->allocation); + + for (i = 0; i < privpool->volumes.count; i++) { + if (privpool->volumes.objs[i] == privvol) { + xml_path = parallelsAddFileExt(privvol->target.path, ".xml"); + if (!xml_path) + goto cleanup; + + if (unlink(xml_path)) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Can't remove file '%s'"), xml_path); + goto cleanup; + } + + virStorageVolDefFree(privvol); + + if (i < (privpool->volumes.count - 1)) + memmove(privpool->volumes.objs + i, + privpool->volumes.objs + i + 1, + sizeof(*(privpool->volumes.objs)) * + (privpool->volumes.count - (i + 1))); + + if (VIR_REALLOC_N(privpool->volumes.objs, + privpool->volumes.count - 1) < 0) { + ; /* Failure to reduce memory allocation isn't fatal */ + } + privpool->volumes.count--; + + break; + } + } + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + VIR_FREE(xml_path); + return ret; +} + + +static int +parallelsStorageVolumeTypeForPool(int pooltype) +{ + + switch (pooltype) { + case VIR_STORAGE_POOL_DIR: + case VIR_STORAGE_POOL_FS: + case VIR_STORAGE_POOL_NETFS: + return VIR_STORAGE_VOL_FILE; +default: + return VIR_STORAGE_VOL_BLOCK; + } +} + +static int +parallelsStorageVolumeGetInfo(virStorageVolPtr vol, virStorageVolInfoPtr info) +{ + parallelsConnPtr privconn = vol->conn->privateData; + virStoragePoolObjPtr privpool; + virStorageVolDefPtr privvol; + int ret = -1; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, vol->pool); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(vol->pool); + goto cleanup; + } + + privvol = virStorageVolDefFindByName(privpool, vol->name); + + if (privvol == NULL) { + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), vol->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), vol->pool); + goto cleanup; + } + + memset(info, 0, sizeof(*info)); + info->type = parallelsStorageVolumeTypeForPool(privpool->def->type); + info->capacity = privvol->capacity; + info->allocation = privvol->allocation; + ret = 0; + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static char * +parallelsStorageVolumeGetXMLDesc(virStorageVolPtr vol, unsigned int flags) +{ + parallelsConnPtr privconn = vol->conn->privateData; + virStoragePoolObjPtr privpool; + virStorageVolDefPtr privvol; + char *ret = NULL; + + virCheckFlags(0, NULL); + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, vol->pool); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(vol->pool); + goto cleanup; + } + + privvol = virStorageVolDefFindByName(privpool, vol->name); + + if (privvol == NULL) { + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), vol->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), vol->pool); + goto cleanup; + } + + ret = virStorageVolDefFormat(privpool->def, privvol); + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static char * +parallelsStorageVolumeGetPath(virStorageVolPtr vol) +{ + parallelsConnPtr privconn = vol->conn->privateData; + virStoragePoolObjPtr privpool; + virStorageVolDefPtr privvol; + char *ret = NULL; + + parallelsDriverLock(privconn); + privpool = virStoragePoolObjFindByName(&privconn->pools, vol->pool); + parallelsDriverUnlock(privconn); + + if (privpool == NULL) { + parallelsPoolNotFoundError(vol->pool); + goto cleanup; + } + + privvol = virStorageVolDefFindByName(privpool, vol->name); + + if (privvol == NULL) { + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), vol->name); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(privpool)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("storage pool '%s' is not active"), vol->pool); + goto cleanup; + } + + ret = strdup(privvol->target.path); + if (ret == NULL) + virReportOOMError(); + +cleanup: + if (privpool) + virStoragePoolObjUnlock(privpool); + return ret; +} + +static virStorageDriver parallelsStorageDriver = { + .name = "Parallels", + .open = parallelsStorageOpen, /* 0.10.0 */ + .close = parallelsStorageClose, /* 0.10.0 */ + + .numOfPools = parallelsStorageNumPools, /* 0.10.0 */ + .listPools = parallelsStorageListPools, /* 0.10.0 */ + .numOfDefinedPools = parallelsStorageNumDefinedPools, /* 0.10.0 */ + .listDefinedPools = parallelsStorageListDefinedPools, /* 0.10.0 */ + .poolLookupByName = parallelsStoragePoolLookupByName, /* 0.10.0 */ + .poolLookupByUUID = parallelsStoragePoolLookupByUUID, /* 0.10.0 */ + .poolLookupByVolume = parallelsStoragePoolLookupByVolume, /* 0.10.0 */ + .poolDefineXML = parallelsStoragePoolDefine, /* 0.10.0 */ + .poolUndefine = parallelsStoragePoolUndefine, /* 0.10.0 */ + .poolCreate = parallelsStoragePoolStart, /* 0.10.0 */ + .poolDestroy = parallelsStoragePoolDestroy, /* 0.10.0 */ + .poolRefresh = parallelsStoragePoolRefresh, /* 0.10.0 */ + .poolGetInfo = parallelsStoragePoolGetInfo, /* 0.10.0 */ + .poolGetXMLDesc = parallelsStoragePoolGetXMLDesc, /* 0.10.0 */ + .poolGetAutostart = parallelsStoragePoolGetAutostart, /* 0.10.0 */ + .poolSetAutostart = parallelsStoragePoolSetAutostart, /* 0.10.0 */ + .poolNumOfVolumes = parallelsStoragePoolNumVolumes, /* 0.10.0 */ + .poolListVolumes = parallelsStoragePoolListVolumes, /* 0.10.0 */ + + .volLookupByName = parallelsStorageVolumeLookupByName, /* 0.10.0 */ + .volLookupByKey = parallelsStorageVolumeLookupByKey, /* 0.10.0 */ + .volLookupByPath = parallelsStorageVolumeLookupByPath, /* 0.10.0 */ + .volCreateXML = parallelsStorageVolumeCreateXML, /* 0.10.0 */ + .volCreateXMLFrom = parallelsStorageVolumeCreateXMLFrom, /* 0.10.0 */ + .volDelete = parallelsStorageVolumeDelete, /* 0.10.0 */ + .volGetInfo = parallelsStorageVolumeGetInfo, /* 0.10.0 */ + .volGetXMLDesc = parallelsStorageVolumeGetXMLDesc, /* 0.10.0 */ + .volGetPath = parallelsStorageVolumeGetPath, /* 0.10.0 */ + .poolIsActive = parallelsStoragePoolIsActive, /* 0.10.0 */ + .poolIsPersistent = parallelsStoragePoolIsPersistent, /* 0.10.0 */ +}; + +int +parallelsStorageRegister(void) +{ + if (virRegisterStorageDriver(¶llelsStorageDriver) < 0) + return -1; + + return 0; +} diff --git a/src/parallels/parallels_utils.c b/src/parallels/parallels_utils.c index 78d45e0..0f701d1 100644 --- a/src/parallels/parallels_utils.c +++ b/src/parallels/parallels_utils.c @@ -108,3 +108,25 @@ parallelsCmdRun(const char *binary, ...) return ret; } + +/* + * Return new file path in malloced string created by + * concatenating first and second function arguments. + */ +char * +parallelsAddFileExt(const char *path, const char *ext) +{ + char *new_path = NULL; + size_t len = strlen(path) + strlen(ext) + 1; + + if (VIR_ALLOC_N(new_path, len) < 0) { + virReportOOMError(); + return NULL; + } + + if (!virStrcpy(new_path, path, len)) + return NULL; + strcat(new_path, ext); + + return new_path; +} diff --git a/src/parallels/parallels_utils.h b/src/parallels/parallels_utils.h index d997ad3..f6948c4 100644 --- a/src/parallels/parallels_utils.h +++ b/src/parallels/parallels_utils.h @@ -23,9 +23,40 @@ #ifndef PARALLELS_UTILS_H # define PARALLELS_UTILS_H +# include "driver.h" +# include "util/threads.h" +# include "conf/domain_conf.h" +# include "conf/storage_conf.h" +# include "conf/domain_event.h" +# include "json.h" + +struct _parallelsConn { + virMutex lock; + virDomainObjList domains; + virStoragePoolObjList pools; + virCapsPtr caps; + virDomainEventStatePtr domainEventState; +}; + +typedef struct _parallelsConn parallelsConn; +typedef struct _parallelsConn *parallelsConnPtr; + +struct parallelsDomObj { + int id; + char *uuid; + char *os; +}; + +typedef struct parallelsDomObj *parallelsDomObjPtr; + +int parallelsStorageRegister(void); + virJSONValuePtr parallelsParseOutput(const char *binary, ...) ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL; int parallelsCmdRun(const char *binary, ...) ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL; +char * parallelsAddFileExt(const char *path, const char *ext); +void parallelsDriverLock(parallelsConnPtr driver); +void parallelsDriverUnlock(parallelsConnPtr driver); #endif -- 1.7.1

To create a new VM in Parallels Clud Server we should issue "prlctl create" command, and give path to the directory, where VM should be created. VM's storage will be in that directory later. So in this first version find out location of first VM's hard disk and create VM there. Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 81 ++++++++++++++++++++++++++++++++++++- src/parallels/parallels_storage.c | 5 +-- src/parallels/parallels_utils.h | 2 + 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index 80ff709..c12470b 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -1511,6 +1511,77 @@ parallelsApplyChanges(virDomainObjPtr dom, virDomainDefPtr new) return 0; } +static int +parallelsCreateVm(virConnectPtr conn, virDomainDefPtr def) +{ + parallelsConnPtr privconn = conn->privateData; + int i; + virStorageVolDefPtr privvol = NULL; + virStoragePoolObjPtr pool = NULL; + virStorageVolPtr vol = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + for (i = 0; i < def->ndisks; i++) { + if (def->disks[i]->device != VIR_DOMAIN_DISK_DEVICE_DISK) + continue; + + vol = parallelsStorageVolumeLookupByPathLocked(conn, def->disks[i]->src); + if (!vol) { + virReportError(VIR_ERR_INVALID_ARG, + _("Can't find volume with path '%s'"), + def->disks[i]->src); + return -1; + } + break; + } + + if (!vol) { + /* We determine path to VM directory from volume, so + * let's report error if no disk until better solution + * will be found */ + virReportError(VIR_ERR_INVALID_ARG, + _("Can't create VM without hard disks")); + return -1; + } + + pool = virStoragePoolObjFindByName(&privconn->pools, vol->pool); + if (!pool) { + virReportError(VIR_ERR_INVALID_ARG, + _("Can't find storage pool with name '%s'"), + vol->pool); + goto error; + } + + privvol = virStorageVolDefFindByPath(pool, def->disks[i]->src); + if (!privvol) { + virReportError(VIR_ERR_INVALID_ARG, + _("Can't find storage volume definition for path '%s'"), + def->disks[i]->src); + goto error2; + } + + virUUIDFormat(def->uuid, uuidstr); + + if (parallelsCmdRun(PRLCTL, "create", def->name, "--dst", + pool->def->target.path, "--no-hdd", + "--uuid", uuidstr, NULL) < 0) + goto error2; + + if (parallelsCmdRun(PRLCTL, "set", def->name, "--vnc-mode", "auto", NULL) < 0) + goto error2; + + virStoragePoolObjUnlock(pool); + virUnrefStorageVol(vol); + + return 0; + + error2: + virStoragePoolObjUnlock(pool); + error: + virUnrefStorageVol(vol); + return -1; +} + static virDomainPtr parallelsDomainDefineXML(virConnectPtr conn, const char *xml) { @@ -1550,8 +1621,16 @@ parallelsDomainDefineXML(virConnectPtr conn, const char *xml) def = NULL; } else { - virReportError(VIR_ERR_NO_SUPPORT, _("Not implemented yet")); + if (parallelsCreateVm(conn, def)) goto cleanup; + if (parallelsLoadDomains(privconn, def->name)) + goto cleanup; + dom = virDomainFindByName(&privconn->domains, def->name); + if (!dom) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Domain is not defined after creation")); + goto cleanup; + } } event = virDomainEventNewFromObj(dom, diff --git a/src/parallels/parallels_storage.c b/src/parallels/parallels_storage.c index a7fdaed..4b56588 100644 --- a/src/parallels/parallels_storage.c +++ b/src/parallels/parallels_storage.c @@ -44,9 +44,6 @@ static virStorageVolDefPtr parallelsStorageVolumeDefine(virStoragePoolObjPtr pool, const char *xmldesc, const char *xmlfile, bool is_new); static virStorageVolPtr -parallelsStorageVolumeLookupByPathLocked(virConnectPtr conn, const char *path); - -static virStorageVolPtr parallelsStorageVolumeLookupByPath(virConnectPtr conn, const char *path); static int @@ -868,7 +865,7 @@ parallelsStorageVolumeLookupByKey(virConnectPtr conn, const char *key) return ret; } -static virStorageVolPtr +virStorageVolPtr parallelsStorageVolumeLookupByPathLocked(virConnectPtr conn, const char *path) { parallelsConnPtr privconn = conn->privateData; diff --git a/src/parallels/parallels_utils.h b/src/parallels/parallels_utils.h index f6948c4..93bfb8e 100644 --- a/src/parallels/parallels_utils.h +++ b/src/parallels/parallels_utils.h @@ -58,5 +58,7 @@ int parallelsCmdRun(const char *binary, ...) char * parallelsAddFileExt(const char *path, const char *ext); void parallelsDriverLock(parallelsConnPtr driver); void parallelsDriverUnlock(parallelsConnPtr driver); +virStorageVolPtr parallelsStorageVolumeLookupByPathLocked(virConnectPtr conn, + const char *path); #endif -- 1.7.1

On Thu, Jul 26, 2012 at 10:34:31PM +0400, Dmitry Guryanov wrote:
Add support of collecting information about serial ports. This change is needed mostly as an example, support of other devices will be added later.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- src/parallels/parallels_driver.c | 116 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 116 insertions(+), 0 deletions(-)
diff --git a/src/parallels/parallels_driver.c b/src/parallels/parallels_driver.c index 8c20d27..ee31e6d 100644 --- a/src/parallels/parallels_driver.c +++ b/src/parallels/parallels_driver.c @@ -182,6 +182,119 @@ parallelsGetCapabilities(virConnectPtr conn) return xml; }
+static int +parallelsAddDomainHardware(virDomainDefPtr def, virJSONValuePtr jobj) +{ + int n, i; + virJSONValuePtr value; + const char *key;
Minor nit-pick, its slightly preferable to declare loop iterators like 'i' as a size_t
+ + n = virJSONValueObjectKeysNumber(jobj); + if (n < 1) + goto cleanup; + + for (i = 0; i < n; i++) { + key = virJSONValueObjectGetKey(jobj, i); + value = virJSONValueObjectGetValue(jobj, i); + + if (STRPREFIX(key, "serial")) { + if (parallelsAddSerialInfo(def, key, value)) + goto cleanup; + } + } + + return 0; + + cleanup: + return -1; +} + /* * Must be called with privconn->lock held */
ACK regardless of the above comment. 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 :|

On Thu, Jul 26, 2012 at 10:32:00PM +0400, Dmitry Guryanov wrote:
Parallels Cloud Server is a cloud-ready virtualization solution that allows users to simultaneously run multiple virtual machines and containers on the same physical server.
More information can be found here: http://www.parallels.com/products/pcs/ Also beta version of Parallels Cloud Server can be downloaded there.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- configure.ac | 61 ++++++--- docs/drvparallels.html.in | 28 ++++ include/libvirt/virterror.h | 1 + libvirt.spec.in | 9 +- mingw-libvirt.spec.in | 6 + po/POTFILES.in | 1 + src/Makefile.am | 13 ++ src/conf/domain_conf.c | 3 +- src/conf/domain_conf.h | 1 + src/driver.h | 1 + src/libvirt.c | 9 ++ src/parallels/parallels_driver.c | 287 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_driver.h | 28 ++++ src/util/virterror.c | 3 +- 14 files changed, 429 insertions(+), 22 deletions(-) create mode 100644 docs/drvparallels.html.in create mode 100644 src/parallels/parallels_driver.c create mode 100644 src/parallels/parallels_driver.h
diff --git a/docs/drvparallels.html.in b/docs/drvparallels.html.in new file mode 100644 index 0000000..40a0fe5 --- /dev/null +++ b/docs/drvparallels.html.in @@ -0,0 +1,28 @@ +<html><body> + <h1>Parallels Cloud Server driver</h1> + <ul id="toc"></ul> + <p> + The libvirt Parallels driver can manage Parallels Cloud Server starting from version 6.0. + </p> + + + <h2><a name="project">Project Links</a></h2> + <ul> + <li> + The <a href="http://www.parallels.com/products/server/baremetal/sp/">Parallels Cloud Server</a> Virtualization Solution. + </li> + </ul> + + + <h2><a name="uri">Connections to the Parallels Cloud Server driver</a></h2> + <p> + The libvirt Parallels driver is a single-instance privileged driver, with a driver name of 'parallels'. Some example connection URIs for the libvirt driver are: + </p> +<pre> +parallels:///default (local access) +parallels+unix:///default (local access) +parallels://example.com/default (remote access, TLS/x509) +parallels+tcp://example.com/default (remote access, SASl/Kerberos) +parallels+ssh://root@example.com/default (remote access, SSH tunnelled) +</pre> +</body></html>
It would be nice to have some examples of the suitable XML for running a parallels container shown here too. That can be done as a later follow up patch though. No need to change this patch though - I'm happy to see it as a later addon patch.
+static virCapsPtr +parallelsBuildCapabilities(void) +{ + virCapsPtr caps; + virCapsGuestPtr guest; + struct utsname utsname; + uname(&utsname); + + if ((caps = virCapabilitiesNew(utsname.machine, 0, 0)) == NULL) + goto no_memory; + + if (nodeCapsInitNUMA(caps) < 0) + goto no_memory; + + virCapabilitiesSetMacPrefix(caps, (unsigned char[]) { + 0x42, 0x1C, 0x00}); + + if ((guest = virCapabilitiesAddGuest(caps, "hvm", PARALLELS_DEFAULT_ARCH, + 64, "parallels", + NULL, 0, NULL)) == NULL) + goto no_memory;
Hmm, perhaps I'm misunderstanding, but isn't Parallels a container based technology like OpenVZ, rather than full machine virtualization ? For containers we use 'exe' as the os type rather than 'hvm', and I'd expect the arch + bit count to match the host OS.
+static virDrvOpenStatus +parallelsOpen(virConnectPtr conn, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + unsigned int flags) +{ + int ret; + parallelsConnPtr privconn; + virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); + + if (!conn->uri) + return VIR_DRV_OPEN_DECLINED; + + if (!conn->uri->scheme || STRNEQ(conn->uri->scheme, "parallels")) + return VIR_DRV_OPEN_DECLINED; + + /* Remote driver should handle these. */ + if (conn->uri->server) + return VIR_DRV_OPEN_DECLINED; + + /* From this point on, the connection is for us. */ + if (!conn->uri->path || + conn->uri->path[0] == '\0' || + (conn->uri->path[0] == '/' && conn->uri->path[1] == '\0')) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("parallelsOpen: supply a path or use " + "parallels:///default")); + return VIR_DRV_OPEN_ERROR; + } + + if (STREQ(conn->uri->path, "/default"))
How do connections from parallels work from an end user POV ? Is there is a single parallels service for the whole machine which all users connect to, or does each user have their own private service they work with. If the former, I think it'd be preferable for the URI to be either simple parallels:/// Or parallels:///system to have a bit closer alignment with the naming we use with LXC / QEMU / OpenBVZ. If the latter, then I'd suggest parallels:///session to indicate its tied to a per-user account/session.
+static int +parallelsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer) +{ + /* TODO */ + *hvVer = 6; + return 0; +}
For this I reckon you basically want to return the version of the parallels software.
+int +parallelsRegister(void) +{ + char *prlctl_path; + + prlctl_path = virFindFileInPath(PRLCTL); + if (!prlctl_path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Can't find prlctl command in the PATH env")); + return VIR_DRV_OPEN_ERROR;
You can just return '0' here, and rather than reporting an error, you want to just log a VIR_DEBUG statement I reckon, since you don't want to cause initialization of libvirt as a whole to fail just because parallels isn't installed.
+ } + + VIR_FREE(prlctl_path); + + if (virRegisterDriver(¶llelsDriver) < 0) + return -1; + + return 0; +}
If you can either answer my questions or address them, I'd ACK this now. 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 :|

On 07/27/2012 01:16 PM, Daniel P. Berrange wrote:
On Thu, Jul 26, 2012 at 10:32:00PM +0400, Dmitry Guryanov wrote:
Parallels Cloud Server is a cloud-ready virtualization solution that allows users to simultaneously run multiple virtual machines and containers on the same physical server.
More information can be found here: http://www.parallels.com/products/pcs/ Also beta version of Parallels Cloud Server can be downloaded there.
Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com> --- configure.ac | 61 ++++++--- docs/drvparallels.html.in | 28 ++++ include/libvirt/virterror.h | 1 + libvirt.spec.in | 9 +- mingw-libvirt.spec.in | 6 + po/POTFILES.in | 1 + src/Makefile.am | 13 ++ src/conf/domain_conf.c | 3 +- src/conf/domain_conf.h | 1 + src/driver.h | 1 + src/libvirt.c | 9 ++ src/parallels/parallels_driver.c | 287 ++++++++++++++++++++++++++++++++++++++ src/parallels/parallels_driver.h | 28 ++++ src/util/virterror.c | 3 +- 14 files changed, 429 insertions(+), 22 deletions(-) create mode 100644 docs/drvparallels.html.in create mode 100644 src/parallels/parallels_driver.c create mode 100644 src/parallels/parallels_driver.h
diff --git a/docs/drvparallels.html.in b/docs/drvparallels.html.in new file mode 100644 index 0000000..40a0fe5 --- /dev/null +++ b/docs/drvparallels.html.in @@ -0,0 +1,28 @@ +<html><body> + <h1>Parallels Cloud Server driver</h1> + <ul id="toc"></ul> + <p> + The libvirt Parallels driver can manage Parallels Cloud Server starting from version 6.0. + </p> + + + <h2><a name="project">Project Links</a></h2> + <ul> + <li> + The <a href="http://www.parallels.com/products/server/baremetal/sp/">Parallels Cloud Server</a> Virtualization Solution. + </li> + </ul> + + + <h2><a name="uri">Connections to the Parallels Cloud Server driver</a></h2> + <p> + The libvirt Parallels driver is a single-instance privileged driver, with a driver name of 'parallels'. Some example connection URIs for the libvirt driver are: + </p> +<pre> +parallels:///default (local access) +parallels+unix:///default (local access) +parallels://example.com/default (remote access, TLS/x509) +parallels+tcp://example.com/default (remote access, SASl/Kerberos) +parallels+ssh://root@example.com/default (remote access, SSH tunnelled) +</pre> +</body></html> It would be nice to have some examples of the suitable XML for running a parallels container shown here too. That can be done as a later follow up patch though. No need to change this patch though - I'm happy to see it as a later addon patch.
+static virCapsPtr +parallelsBuildCapabilities(void) +{ + virCapsPtr caps; + virCapsGuestPtr guest; + struct utsname utsname; + uname(&utsname); + + if ((caps = virCapabilitiesNew(utsname.machine, 0, 0)) == NULL) + goto no_memory; + + if (nodeCapsInitNUMA(caps) < 0) + goto no_memory; + + virCapabilitiesSetMacPrefix(caps, (unsigned char[]) { + 0x42, 0x1C, 0x00}); + + if ((guest = virCapabilitiesAddGuest(caps, "hvm", PARALLELS_DEFAULT_ARCH, + 64, "parallels", + NULL, 0, NULL)) == NULL) + goto no_memory; Hmm, perhaps I'm misunderstanding, but isn't Parallels a container based technology like OpenVZ, rather than full machine virtualization ?
For containers we use 'exe' as the os type rather than 'hvm', and I'd expect the arch + bit count to match the host OS.
Parallels Cloud Server uses the same interface for both containers and virtual machines management, where it's possible, I've decided to start writing support of VMs. I hope then it's possible to make driver which supports both types of virtualization and going to do it later.
+static virDrvOpenStatus +parallelsOpen(virConnectPtr conn, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + unsigned int flags) +{ + int ret; + parallelsConnPtr privconn; + virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); + + if (!conn->uri) + return VIR_DRV_OPEN_DECLINED; + + if (!conn->uri->scheme || STRNEQ(conn->uri->scheme, "parallels")) + return VIR_DRV_OPEN_DECLINED; + + /* Remote driver should handle these. */ + if (conn->uri->server) + return VIR_DRV_OPEN_DECLINED; + + /* From this point on, the connection is for us. */ + if (!conn->uri->path || + conn->uri->path[0] == '\0' || + (conn->uri->path[0] == '/' && conn->uri->path[1] == '\0')) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("parallelsOpen: supply a path or use " + "parallels:///default")); + return VIR_DRV_OPEN_ERROR; + } + + if (STREQ(conn->uri->path, "/default")) How do connections from parallels work from an end user POV ?
Is there is a single parallels service for the whole machine which all users connect to, or does each user have their own private service they work with.
If the former, I think it'd be preferable for the URI to be either simple
parallels:///
Or
parallels:///system
to have a bit closer alignment with the naming we use with LXC / QEMU / OpenBVZ.
If the latter, then I'd suggest parallels:///session to indicate its tied to a per-user account/session.
Each user in system has it's own set of virtual machines, I agree to change URI to parallels://system
+static int +parallelsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer) +{ + /* TODO */ + *hvVer = 6; + return 0; +} For this I reckon you basically want to return the version of the parallels software.
Yes, there are no problems with fixing it now.
+int +parallelsRegister(void) +{ + char *prlctl_path; + + prlctl_path = virFindFileInPath(PRLCTL); + if (!prlctl_path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Can't find prlctl command in the PATH env")); + return VIR_DRV_OPEN_ERROR; You can just return '0' here, and rather than reporting an error, you want to just log a VIR_DEBUG statement I reckon, since you don't want to cause initialization of libvirt as a whole to fail just because parallels isn't installed.
OK, I can go it in separate patch.
+ } + + VIR_FREE(prlctl_path); + + if (virRegisterDriver(¶llelsDriver) < 0) + return -1; + + return 0; +}
If you can either answer my questions or address them, I'd ACK this now.
Daniel
-- Dmitry Guryanov

On Fri, Jul 27, 2012 at 01:48:49PM +0400, Dmitry Guryanov wrote:
On 07/27/2012 01:16 PM, Daniel P. Berrange wrote:
+static virCapsPtr +parallelsBuildCapabilities(void) +{ + virCapsPtr caps; + virCapsGuestPtr guest; + struct utsname utsname; + uname(&utsname); + + if ((caps = virCapabilitiesNew(utsname.machine, 0, 0)) == NULL) + goto no_memory; + + if (nodeCapsInitNUMA(caps) < 0) + goto no_memory; + + virCapabilitiesSetMacPrefix(caps, (unsigned char[]) { + 0x42, 0x1C, 0x00}); + + if ((guest = virCapabilitiesAddGuest(caps, "hvm", PARALLELS_DEFAULT_ARCH, + 64, "parallels", + NULL, 0, NULL)) == NULL) + goto no_memory; Hmm, perhaps I'm misunderstanding, but isn't Parallels a container
On Thu, Jul 26, 2012 at 10:32:00PM +0400, Dmitry Guryanov wrote: based technology like OpenVZ, rather than full machine virtualization ?
For containers we use 'exe' as the os type rather than 'hvm', and I'd expect the arch + bit count to match the host OS.
Parallels Cloud Server uses the same interface for both containers and virtual machines management, where it's possible, I've decided to start writing support of VMs. I hope then it's possible to make driver which supports both types of virtualization and going to do it later.
Ah that makes sense now. Thanks for explaining. So what you have here is fine to start with.
+ /* From this point on, the connection is for us. */ + if (!conn->uri->path || + conn->uri->path[0] == '\0' || + (conn->uri->path[0] == '/' && conn->uri->path[1] == '\0')) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("parallelsOpen: supply a path or use " + "parallels:///default")); + return VIR_DRV_OPEN_ERROR; + } + + if (STREQ(conn->uri->path, "/default")) How do connections from parallels work from an end user POV ?
Is there is a single parallels service for the whole machine which all users connect to, or does each user have their own private service they work with.
If the former, I think it'd be preferable for the URI to be either simple
parallels:///
Or
parallels:///system
to have a bit closer alignment with the naming we use with LXC / QEMU / OpenBVZ.
If the latter, then I'd suggest parallels:///session to indicate its tied to a per-user account/session.
Each user in system has it's own set of virtual machines, I agree to change URI to parallels://system
If each user has their own set of VMs, then you want parallels:///session actually 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 (2)
-
Daniel P. Berrange
-
Dmitry Guryanov