From: Purna Pavan Chandra Aekkaladevi <paekkaladevi(a)microsoft.com>
save, managedsave and restore is supported for domains without any
network, hostdev config defined. The `path` input to the save command
should be a directory path since cloud-hypervisor expects directory path.
Signed-off-by: Purna Pavan Chandra Aekkaladevi <paekkaladevi(a)linux.microsoft.com>
---
src/ch/ch_conf.c | 6 +
src/ch/ch_conf.h | 12 ++
src/ch/ch_driver.c | 511 +++++++++++++++++++++++++++++++++++++++++++-
src/ch/ch_monitor.c | 97 ++++++++-
src/ch/ch_monitor.h | 6 +-
src/ch/ch_process.c | 101 +++++++--
src/ch/ch_process.h | 4 +
7 files changed, 715 insertions(+), 22 deletions(-)
diff --git a/src/ch/ch_conf.c b/src/ch/ch_conf.c
index f421af5121..c109721a83 100644
--- a/src/ch/ch_conf.c
+++ b/src/ch/ch_conf.c
@@ -139,10 +139,12 @@ virCHDriverConfigNew(bool privileged)
if (privileged) {
cfg->logDir = g_strdup_printf("%s/log/libvirt/ch", LOCALSTATEDIR);
cfg->stateDir = g_strdup_printf("%s/libvirt/ch", RUNSTATEDIR);
+ cfg->saveDir = g_strdup_printf("%s/lib/libvirt/ch/save",
LOCALSTATEDIR);
} else {
g_autofree char *rundir = NULL;
g_autofree char *cachedir = NULL;
+ g_autofree char *configbasedir = NULL;
cachedir = virGetUserCacheDirectory();
@@ -150,6 +152,9 @@ virCHDriverConfigNew(bool privileged)
rundir = virGetUserRuntimeDirectory();
cfg->stateDir = g_strdup_printf("%s/ch/run", rundir);
+
+ configbasedir = virGetUserConfigDirectory();
+ cfg->saveDir = g_strdup_printf("%s/ch/save", configbasedir);
}
return cfg;
@@ -166,6 +171,7 @@ virCHDriverConfigDispose(void *obj)
{
virCHDriverConfig *cfg = obj;
+ g_free(cfg->saveDir);
g_free(cfg->stateDir);
g_free(cfg->logDir);
}
diff --git a/src/ch/ch_conf.h b/src/ch/ch_conf.h
index 579eca894e..a77cad7a2a 100644
--- a/src/ch/ch_conf.h
+++ b/src/ch/ch_conf.h
@@ -37,6 +37,7 @@ struct _virCHDriverConfig {
char *stateDir;
char *logDir;
+ char *saveDir;
int cgroupControllers;
@@ -81,6 +82,17 @@ struct _virCHDriver
ebtablesContext *ebtables;
};
+#define CH_SAVE_MAGIC "libvirt-xml\n \0 \r"
+#define CH_SAVE_XML "libvirt-save.xml"
+
+typedef struct _CHSaveXMLHeader CHSaveXMLHeader;
+struct _CHSaveXMLHeader {
+ char magic[sizeof(CH_SAVE_MAGIC)-1];
+ uint32_t xmlLen;
+ /* 20 bytes used, pad up to 64 bytes */
+ uint32_t unused[11];
+};
+
virCaps *virCHDriverCapsInit(void);
virCaps *virCHDriverGetCapabilities(virCHDriver *driver,
bool refresh);
diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c
index 96de5044ac..4413abfa79 100644
--- a/src/ch/ch_driver.c
+++ b/src/ch/ch_driver.c
@@ -20,6 +20,8 @@
#include <config.h>
+#include <fcntl.h>
+
#include "ch_capabilities.h"
#include "ch_conf.h"
#include "ch_domain.h"
@@ -32,6 +34,7 @@
#include "viraccessapicheck.h"
#include "virchrdev.h"
#include "virerror.h"
+#include "virfile.h"
#include "virlog.h"
#include "virobject.h"
#include "virtypedparam.h"
@@ -176,6 +179,13 @@ static char *chConnectGetCapabilities(virConnectPtr conn)
return xml;
}
+static char *
+chDomainManagedSavePath(virCHDriver *driver, virDomainObj *vm)
+{
+ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver);
+ return g_strdup_printf("%s/%s.save", cfg->saveDir,
vm->def->name);
+}
+
/**
* chDomainCreateXML:
* @conn: pointer to connection
@@ -196,6 +206,7 @@ chDomainCreateXML(virConnectPtr conn,
virDomainObj *vm = NULL;
virDomainPtr dom = NULL;
unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE;
+ g_autofree char *managed_save_path = NULL;
virCheckFlags(VIR_DOMAIN_START_VALIDATE, NULL);
@@ -218,6 +229,15 @@ chDomainCreateXML(virConnectPtr conn,
NULL)))
goto cleanup;
+ /* cleanup if there's any stale managedsave dir */
+ managed_save_path = chDomainManagedSavePath(driver, vm);
+ if (virFileDeleteTree(managed_save_path) < 0) {
+ virReportSystemError(errno,
+ _("Failed to cleanup stale managed save dir
'%1$s'"),
+ managed_save_path);
+ goto cleanup;
+ }
+
if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
goto cleanup;
@@ -242,6 +262,8 @@ chDomainCreateWithFlags(virDomainPtr dom, unsigned int flags)
{
virCHDriver *driver = dom->conn->privateData;
virDomainObj *vm;
+ virCHDomainObjPrivate *priv;
+ g_autofree char *managed_save_path = NULL;
int ret = -1;
virCheckFlags(0, -1);
@@ -255,8 +277,33 @@ chDomainCreateWithFlags(virDomainPtr dom, unsigned int flags)
if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
goto cleanup;
- ret = virCHProcessStart(driver, vm, VIR_DOMAIN_RUNNING_BOOTED);
+ if (vm->hasManagedSave) {
+ priv = vm->privateData;
+ managed_save_path = chDomainManagedSavePath(driver, vm);
+ if (virCHProcessStartRestore(driver, vm, managed_save_path) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to restore domain from managed save"));
+ goto endjob;
+ }
+ if (virCHMonitorResumeVM(priv->monitor) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to resume domain after restore from managed
save"));
+ goto endjob;
+ }
+ virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_RESTORED);
+ if (virFileDeleteTree(managed_save_path) < 0) {
+ virReportSystemError(errno,
+ _("Failed to remove managed save path
'%1$s'"),
+ managed_save_path);
+ goto endjob;
+ }
+ vm->hasManagedSave = false;
+ ret = 0;
+ } else {
+ ret = virCHProcessStart(driver, vm, VIR_DOMAIN_RUNNING_BOOTED);
+ }
+ endjob:
virDomainObjEndJob(vm);
cleanup:
@@ -277,6 +324,7 @@ chDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned
int flags)
g_autoptr(virDomainDef) vmdef = NULL;
virDomainObj *vm = NULL;
virDomainPtr dom = NULL;
+ g_autofree char *managed_save_path = NULL;
unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE;
virCheckFlags(VIR_DOMAIN_DEFINE_VALIDATE, NULL);
@@ -299,6 +347,15 @@ chDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned
int flags)
0, NULL)))
goto cleanup;
+ /* cleanup if there's any stale managedsave dir */
+ managed_save_path = chDomainManagedSavePath(driver, vm);
+ if (virFileDeleteTree(managed_save_path) < 0) {
+ virReportSystemError(errno,
+ _("Failed to cleanup stale managed save dir
'%1$s'"),
+ managed_save_path);
+ goto cleanup;
+ }
+
vm->persistent = 1;
dom = virGetDomain(conn, vm->def->name, vm->def->uuid,
vm->def->id);
@@ -621,6 +678,449 @@ chDomainDestroy(virDomainPtr dom)
return chDomainDestroyFlags(dom, 0);
}
+static int
+chDomainSaveAdditionalValidation(virDomainDef *vmdef)
+{
+ /*
+ SAVE and RESTORE are functional only without any networking and
+ device passthrough configuration
+ */
+ if (vmdef->nnets > 0) {
+ virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+ _("cannot save domain with network interfaces"));
+ return -1;
+ }
+ if (vmdef->nhostdevs > 0) {
+ virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+ _("cannot save domain with host devices"));
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * chDoDomainSave:
+ * @driver: pointer to driver structure
+ * @vm: pointer to virtual machine structure. Must be locked before invocation.
+ * @to_dir: directory path (CH needs directory input) to save the domain
+ * @managed: whether the VM is managed or not
+ *
+ * Checks if the domain is running or paused, then suspends it and saves it
+ *
+ * Returns 0 on success or -1 in case of error
+ */
+static int
+chDoDomainSave(virCHDriver *driver,
+ virDomainObj *vm,
+ const char *to_dir,
+ bool managed)
+{
+ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver);
+ virCHDomainObjPrivate *priv = vm->privateData;
+ CHSaveXMLHeader hdr;
+ g_autofree char *to = NULL;
+ g_autofree char *xml = NULL;
+ uint32_t xml_len;
+ VIR_AUTOCLOSE fd = -1;
+ int ret = -1;
+
+ virDomainState domainState = virDomainObjGetState(vm, NULL);
+ if (domainState == VIR_DOMAIN_RUNNING) {
+ if (virCHMonitorSuspendVM(priv->monitor) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to suspend domain before saving"));
+ goto end;
+ }
+ virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_SAVE);
+ } else if (domainState != VIR_DOMAIN_PAUSED) {
+ virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+ _("only can save running/paused domain"));
+ goto end;
+ }
+
+ if (chDomainSaveAdditionalValidation(vm->def) < 0)
+ goto end;
+
+ if (virDirCreate(to_dir, 0770, cfg->user, cfg->group,
+ VIR_DIR_CREATE_ALLOW_EXIST) < 0) {
+ virReportSystemError(errno, _("Failed to create SAVE dir %1$s"),
to_dir);
+ goto end;
+ }
+
+ to = g_strdup_printf("%s/%s", to_dir, CH_SAVE_XML);
+ if ((fd = virFileOpenAs(to, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR,
+ cfg->user, cfg->group, 0)) < 0) {
+ virReportSystemError(-fd,
+ _("Failed to create/open domain save xml file
'%1$s'"),
+ to);
+ goto end;
+ }
+
+ if ((xml = virDomainDefFormat(vm->def, driver->xmlopt, 0)) == NULL)
+ goto end;
+ xml_len = strlen(xml) + 1;
+
+ memset(&hdr, 0, sizeof(hdr));
+ memcpy(hdr.magic, CH_SAVE_MAGIC, sizeof(hdr.magic));
+ hdr.xmlLen = xml_len;
+
+ if (safewrite(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ virReportSystemError(errno, "%s", _("Failed to write file
header"));
+ goto end;
+ }
+
+ if (safewrite(fd, xml, xml_len) != xml_len) {
+ virReportSystemError(errno, "%s", _("Failed to write xml
definition"));
+ goto end;
+ }
+
+ if (virCHMonitorSaveVM(priv->monitor, to_dir) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to save
domain"));
+ goto end;
+ }
+
+ if (virCHProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SAVED) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Failed to shutoff after domain save"));
+ goto end;
+ }
+
+ vm->hasManagedSave = managed;
+ ret = 0;
+
+ end:
+ return ret;
+}
+
+static int
+chDomainSaveFlags(virDomainPtr dom, const char *to, const char *dxml, unsigned int
flags)
+{
+ virCHDriver *driver = dom->conn->privateData;
+ virDomainObj *vm = NULL;
+ int ret = -1;
+
+ virCheckFlags(0, -1);
+ if (dxml) {
+ virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+ _("xml modification unsupported"));
+ return -1;
+ }
+
+ if (!(vm = virCHDomainObjFromDomain(dom)))
+ goto cleanup;
+
+ if (virDomainSaveFlagsEnsureACL(dom->conn, vm->def) < 0)
+ goto cleanup;
+
+ if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
+ goto cleanup;
+
+ if (virDomainObjCheckActive(vm) < 0)
+ goto endjob;
+
+ if (chDoDomainSave(driver, vm, to, false) < 0)
+ goto endjob;
+
+ /* Remove if VM is not persistent */
+ virCHDomainRemoveInactive(driver, vm);
+ ret = 0;
+
+ endjob:
+ virDomainObjEndJob(vm);
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
+static int
+chDomainSave(virDomainPtr dom, const char *to)
+{
+ return chDomainSaveFlags(dom, to, NULL, 0);
+}
+
+static char *
+chDomainSaveXMLRead(int fd)
+{
+ g_autofree char *xml = NULL;
+ CHSaveXMLHeader hdr;
+
+ if (saferead(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("failed to read CHSaveXMLHeader header"));
+ return NULL;
+ }
+
+ if (memcmp(hdr.magic, CH_SAVE_MAGIC, sizeof(hdr.magic))) {
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
+ _("save image magic is incorrect"));
+ return NULL;
+ }
+
+ if (hdr.xmlLen <= 0) {
+ virReportError(VIR_ERR_OPERATION_FAILED,
+ _("invalid XML length: %1$d"), hdr.xmlLen);
+ return NULL;
+ }
+
+ xml = g_new0(char, hdr.xmlLen);
+
+ if (saferead(fd, xml, hdr.xmlLen) != hdr.xmlLen) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("failed to read XML"));
+ return NULL;
+ }
+
+ return g_steal_pointer(&xml);
+}
+
+static int chDomainSaveImageRead(virCHDriver *driver,
+ const char *path,
+ virDomainDef **ret_def)
+{
+ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver);
+ g_autoptr(virDomainDef) def = NULL;
+ g_autofree char *from = NULL;
+ g_autofree char *xml = NULL;
+ VIR_AUTOCLOSE fd = -1;
+ int ret = -1;
+
+ from = g_strdup_printf("%s/%s", path, CH_SAVE_XML);
+ if ((fd = virFileOpenAs(from, O_RDONLY, 0, cfg->user, cfg->group, 0)) < 0)
{
+ virReportSystemError(errno,
+ _("Failed to open domain save file
'%1$s'"),
+ from);
+ goto end;
+ }
+
+ if (!(xml = chDomainSaveXMLRead(fd)))
+ goto end;
+
+ if (!(def = virDomainDefParseString(xml, driver->xmlopt, NULL,
+ VIR_DOMAIN_DEF_PARSE_INACTIVE |
+ VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
+ goto end;
+
+ *ret_def = g_steal_pointer(&def);
+ ret = 0;
+
+ end:
+ return ret;
+}
+
+static char *
+chDomainSaveImageGetXMLDesc(virConnectPtr conn,
+ const char *path,
+ unsigned int flags)
+{
+ virCHDriver *driver = conn->privateData;
+ g_autoptr(virDomainDef) def = NULL;
+ char *ret = NULL;
+
+ virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL);
+
+ if (chDomainSaveImageRead(driver, path, &def) < 0)
+ goto cleanup;
+
+ if (virDomainSaveImageGetXMLDescEnsureACL(conn, def) < 0)
+ goto cleanup;
+
+ ret = virDomainDefFormat(def, driver->xmlopt,
+ virDomainDefFormatConvertXMLFlags(flags));
+
+ cleanup:
+ return ret;
+}
+
+static int
+chDomainRestoreFlags(virConnectPtr conn,
+ const char *from,
+ const char *dxml,
+ unsigned int flags)
+{
+ virCHDriver *driver = conn->privateData;
+ virDomainObj *vm = NULL;
+ virCHDomainObjPrivate *priv;
+ g_autoptr(virDomainDef) def = NULL;
+ int ret = -1;
+
+ virCheckFlags(0, -1);
+
+ if (dxml) {
+ virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+ _("xml modification unsupported"));
+ return -1;
+ }
+
+ if (chDomainSaveImageRead(driver, from, &def) < 0)
+ goto cleanup;
+
+ if (virDomainRestoreFlagsEnsureACL(conn, def) < 0)
+ goto cleanup;
+
+ if (!(vm = virDomainObjListAdd(driver->domains, &def,
+ driver->xmlopt,
+ VIR_DOMAIN_OBJ_LIST_ADD_LIVE |
+ VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE,
+ NULL)))
+ goto cleanup;
+
+ if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
+ goto cleanup;
+
+ if (virCHProcessStartRestore(driver, vm, from) < 0)
+ goto endjob;
+
+ priv = vm->privateData;
+ if (virCHMonitorResumeVM(priv->monitor) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to resume domain after restore"));
+ goto endjob;
+ }
+ virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_RESTORED);
+ ret = 0;
+
+ endjob:
+ virDomainObjEndJob(vm);
+
+ cleanup:
+ if (vm && ret < 0)
+ virCHDomainRemoveInactive(driver, vm);
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
+static int
+chDomainRestore(virConnectPtr conn, const char *from)
+{
+ return chDomainRestoreFlags(conn, from, NULL, 0);
+}
+
+static int
+chDomainManagedSave(virDomainPtr dom, unsigned int flags)
+{
+ virCHDriver *driver = dom->conn->privateData;
+ virDomainObj *vm = NULL;
+ g_autofree char *to = NULL;
+ int ret = -1;
+
+ virCheckFlags(0, -1);
+
+ if (!(vm = virCHDomainObjFromDomain(dom)))
+ goto cleanup;
+
+ if (virDomainManagedSaveEnsureACL(dom->conn, vm->def) < 0)
+ goto cleanup;
+
+ if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
+ goto cleanup;
+
+ if (virDomainObjCheckActive(vm) < 0)
+ goto endjob;
+
+ if (!vm->persistent) {
+ virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+ _("cannot do managed save for transient domain"));
+ goto endjob;
+ }
+
+ to = chDomainManagedSavePath(driver, vm);
+ if (chDoDomainSave(driver, vm, to, true) < 0)
+ goto endjob;
+
+ ret = 0;
+
+ endjob:
+ virDomainObjEndJob(vm);
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
+static int
+chDomainManagedSaveRemove(virDomainPtr dom, unsigned int flags)
+{
+ virCHDriver *driver = dom->conn->privateData;
+ virDomainObj *vm;
+ int ret = -1;
+ g_autofree char *path = NULL;
+
+ virCheckFlags(0, -1);
+
+ if (!(vm = virCHDomainObjFromDomain(dom)))
+ return -1;
+
+ if (virDomainManagedSaveRemoveEnsureACL(dom->conn, vm->def) < 0)
+ goto cleanup;
+
+ path = chDomainManagedSavePath(driver, vm);
+
+ if (virFileDeleteTree(path) < 0) {
+ virReportSystemError(errno,
+ _("Failed to remove managed save path
'%1$s'"),
+ path);
+ goto cleanup;
+ }
+
+ vm->hasManagedSave = false;
+ ret = 0;
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
+static char *
+chDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags)
+{
+ virCHDriver *driver = dom->conn->privateData;
+ virDomainObj *vm = NULL;
+ g_autoptr(virDomainDef) def = NULL;
+ char *ret = NULL;
+ g_autofree char *path = NULL;
+
+ virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL);
+
+ if (!(vm = virCHDomainObjFromDomain(dom)))
+ goto cleanup;
+
+ path = chDomainManagedSavePath(driver, vm);
+ if (chDomainSaveImageRead(driver, path, &def) < 0)
+ goto cleanup;
+
+ if (virDomainManagedSaveGetXMLDescEnsureACL(dom->conn, def, flags) < 0)
+ goto cleanup;
+
+ ret = virDomainDefFormat(def, driver->xmlopt,
+ virDomainDefFormatConvertXMLFlags(flags));
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
+static int
+chDomainHasManagedSaveImage(virDomainPtr dom, unsigned int flags)
+{
+ virDomainObj *vm = NULL;
+ int ret = -1;
+
+ virCheckFlags(0, -1);
+
+ if (!(vm = virCHDomainObjFromDomain(dom)))
+ return -1;
+
+ if (virDomainHasManagedSaveImageEnsureACL(dom->conn, vm->def) < 0)
+ goto cleanup;
+
+ ret = vm->hasManagedSave;
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
static virDomainPtr chDomainLookupByID(virConnectPtr conn,
int id)
{
@@ -1734,6 +2234,15 @@ static virHypervisorDriver chHypervisorDriver = {
.nodeGetCPUMap = chNodeGetCPUMap, /* 8.0.0 */
.domainSetNumaParameters = chDomainSetNumaParameters, /* 8.1.0 */
.domainGetNumaParameters = chDomainGetNumaParameters, /* 8.1.0 */
+ .domainSave = chDomainSave, /* 10.1.0 */
+ .domainSaveFlags = chDomainSaveFlags, /* 10.1.0 */
+ .domainSaveImageGetXMLDesc = chDomainSaveImageGetXMLDesc, /* 10.1.0 */
+ .domainRestore = chDomainRestore, /* 10.1.0 */
+ .domainRestoreFlags = chDomainRestoreFlags, /* 10.1.0 */
+ .domainManagedSave = chDomainManagedSave, /* 10.1.0 */
+ .domainManagedSaveRemove = chDomainManagedSaveRemove, /* 10.1.0 */
+ .domainManagedSaveGetXMLDesc = chDomainManagedSaveGetXMLDesc, /* 10.1.0 */
+ .domainHasManagedSaveImage = chDomainHasManagedSaveImage, /* 10.1.0 */
};
static virConnectDriver chConnectDriver = {
diff --git a/src/ch/ch_monitor.c b/src/ch/ch_monitor.c
index 62ba72bb82..bb55548516 100644
--- a/src/ch/ch_monitor.c
+++ b/src/ch/ch_monitor.c
@@ -444,6 +444,22 @@ virCHMonitorBuildVMJson(virCHDriver *driver, virDomainDef *vmdef,
return 0;
}
+static int
+virCHMonitorBuildKeyValueStringJson(char **jsonstr,
+ const char *key,
+ const char *value)
+{
+ g_autoptr(virJSONValue) content = virJSONValueNewObject();
+
+ if (virJSONValueObjectAppendString(content, key, value) < 0)
+ return -1;
+
+ if (!(*jsonstr = virJSONValueToString(content, false)))
+ return -1;
+
+ return 0;
+}
+
static int
chMonitorCreateSocket(const char *socket_path)
{
@@ -500,10 +516,11 @@ chMonitorCreateSocket(const char *socket_path)
}
virCHMonitor *
-virCHMonitorNew(virDomainObj *vm, const char *socketdir)
+virCHMonitorNew(virDomainObj *vm, virCHDriverConfig *cfg)
{
g_autoptr(virCHMonitor) mon = NULL;
g_autoptr(virCommand) cmd = NULL;
+ char *socketdir = cfg->stateDir;
int socket_fd = 0;
if (virCHMonitorInitialize() < 0)
@@ -527,6 +544,13 @@ virCHMonitorNew(virDomainObj *vm, const char *socketdir)
return NULL;
}
+ if (g_mkdir_with_parents(cfg->saveDir, 0777) < 0) {
+ virReportSystemError(errno,
+ _("Cannot create save directory '%1$s'"),
+ cfg->saveDir);
+ return NULL;
+ }
+
cmd = virCommandNew(vm->def->emulator);
virCommandSetUmask(cmd, 0x002);
socket_fd = chMonitorCreateSocket(mon->socketpath);
@@ -883,6 +907,77 @@ virCHMonitorResumeVM(virCHMonitor *mon)
return virCHMonitorPutNoContent(mon, URL_VM_RESUME);
}
+static int
+virCHMonitorSaveRestoreVM(virCHMonitor *mon, const char *path, bool save)
+{
+ g_autofree char *url = NULL;
+ int responseCode = 0;
+ int ret = -1;
+ g_autofree char *payload = NULL;
+ g_autofree char *path_url = NULL;
+ struct curl_slist *headers = NULL;
+ struct curl_data data = {0};
+
+ if (save)
+ url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_SAVE);
+ else
+ url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_RESTORE);
+
+ headers = curl_slist_append(headers, "Accept: application/json");
+ headers = curl_slist_append(headers, "Content-Type: application/json");
+
+ path_url = g_strdup_printf("file://%s", path);
+ if (save) {
+ if (virCHMonitorBuildKeyValueStringJson(&payload,
"destination_url", path_url) != 0)
+ return -1;
+ } else {
+ if (virCHMonitorBuildKeyValueStringJson(&payload, "source_url",
path_url) != 0)
+ return -1;
+ }
+
+ VIR_WITH_OBJECT_LOCK_GUARD(mon) {
+ /* reset all options of a libcurl session handle at first */
+ curl_easy_reset(mon->handle);
+
+ curl_easy_setopt(mon->handle, CURLOPT_UNIX_SOCKET_PATH, mon->socketpath);
+ curl_easy_setopt(mon->handle, CURLOPT_URL, url);
+ curl_easy_setopt(mon->handle, CURLOPT_CUSTOMREQUEST, "PUT");
+ curl_easy_setopt(mon->handle, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(mon->handle, CURLOPT_POSTFIELDS, payload);
+ curl_easy_setopt(mon->handle, CURLOPT_WRITEFUNCTION, curl_callback);
+ curl_easy_setopt(mon->handle, CURLOPT_WRITEDATA, (void *)&data);
+
+ responseCode = virCHMonitorCurlPerform(mon->handle);
+ }
+
+ if (responseCode == 200 || responseCode == 204) {
+ ret = 0;
+ } else {
+ data.content = g_realloc(data.content, data.size + 1);
+ data.content[data.size] = 0;
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ data.content);
+ g_free(data.content);
+ }
+
+ /* reset the libcurl handle to avoid leaking a stack pointer to data */
+ curl_easy_reset(mon->handle);
+ curl_slist_free_all(headers);
+ return ret;
+}
+
+int
+virCHMonitorSaveVM(virCHMonitor *mon, const char *to)
+{
+ return virCHMonitorSaveRestoreVM(mon, to, true);
+}
+
+int
+virCHMonitorRestoreVM(virCHMonitor *mon, const char *from)
+{
+ return virCHMonitorSaveRestoreVM(mon, from, false);
+}
+
/**
* virCHMonitorGetInfo:
* @mon: Pointer to the monitor
diff --git a/src/ch/ch_monitor.h b/src/ch/ch_monitor.h
index 47b4e7abbd..ea6b2a771b 100644
--- a/src/ch/ch_monitor.h
+++ b/src/ch/ch_monitor.h
@@ -37,6 +37,8 @@
#define URL_VM_Suspend "vm.pause"
#define URL_VM_RESUME "vm.resume"
#define URL_VM_INFO "vm.info"
+#define URL_VM_SAVE "vm.snapshot"
+#define URL_VM_RESTORE "vm.restore"
#define VIRCH_THREAD_NAME_LEN 16
@@ -99,7 +101,7 @@ struct _virCHMonitor {
virCHMonitorThreadInfo *threads;
};
-virCHMonitor *virCHMonitorNew(virDomainObj *vm, const char *socketdir);
+virCHMonitor *virCHMonitorNew(virDomainObj *vm, virCHDriverConfig *cfg);
void virCHMonitorClose(virCHMonitor *mon);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virCHMonitor, virCHMonitorClose);
@@ -110,6 +112,8 @@ int virCHMonitorShutdownVM(virCHMonitor *mon);
int virCHMonitorRebootVM(virCHMonitor *mon);
int virCHMonitorSuspendVM(virCHMonitor *mon);
int virCHMonitorResumeVM(virCHMonitor *mon);
+int virCHMonitorSaveVM(virCHMonitor *mon, const char *to);
+int virCHMonitorRestoreVM(virCHMonitor *mon, const char *from);
int virCHMonitorGetInfo(virCHMonitor *mon, virJSONValue **info);
void virCHMonitorCPUInfoFree(virCHMonitorCPUInfo *cpus);
diff --git a/src/ch/ch_process.c b/src/ch/ch_process.c
index 86d3190324..3f0816a513 100644
--- a/src/ch/ch_process.c
+++ b/src/ch/ch_process.c
@@ -51,7 +51,7 @@ virCHProcessConnectMonitor(virCHDriver *driver,
virCHMonitor *monitor = NULL;
virCHDriverConfig *cfg = virCHDriverGetConfig(driver);
- monitor = virCHMonitorNew(vm, cfg->stateDir);
+ monitor = virCHMonitorNew(vm, cfg);
virObjectUnref(cfg);
return monitor;
@@ -453,6 +453,34 @@ virCHProcessSetupVcpus(virDomainObj *vm)
return 0;
}
+static int
+virCHProcessSetup(virDomainObj *vm)
+{
+ virCHDomainObjPrivate *priv = vm->privateData;
+
+ virCHDomainRefreshThreadInfo(vm);
+
+ VIR_DEBUG("Setting emulator tuning/settings");
+ if (virCHProcessSetupEmulatorThreads(vm) < 0)
+ return -1;
+
+ VIR_DEBUG("Setting iothread tuning/settings");
+ if (virCHProcessSetupIOThreads(vm) < 0)
+ return -1;
+
+ VIR_DEBUG("Setting global CPU cgroup (if required)");
+ if (virDomainCgroupSetupGlobalCpuCgroup(vm,
+ priv->cgroup) < 0)
+ return -1;
+
+ VIR_DEBUG("Setting vCPU tuning/settings");
+ if (virCHProcessSetupVcpus(vm) < 0)
+ return -1;
+
+ virCHProcessUpdateInfo(vm);
+ return 0;
+}
+
#define PKT_TIMEOUT_MS 500 /* ms */
@@ -707,26 +735,9 @@ virCHProcessStart(virCHDriver *driver,
goto cleanup;
}
- virCHDomainRefreshThreadInfo(vm);
-
- VIR_DEBUG("Setting emulator tuning/settings");
- if (virCHProcessSetupEmulatorThreads(vm) < 0)
- goto cleanup;
-
- VIR_DEBUG("Setting iothread tuning/settings");
- if (virCHProcessSetupIOThreads(vm) < 0)
- goto cleanup;
-
- VIR_DEBUG("Setting global CPU cgroup (if required)");
- if (virDomainCgroupSetupGlobalCpuCgroup(vm,
- priv->cgroup) < 0)
- goto cleanup;
-
- VIR_DEBUG("Setting vCPU tuning/settings");
- if (virCHProcessSetupVcpus(vm) < 0)
+ if (virCHProcessSetup(vm) < 0)
goto cleanup;
- virCHProcessUpdateInfo(vm);
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, reason);
return 0;
@@ -785,3 +796,55 @@ virCHProcessStop(virCHDriver *driver G_GNUC_UNUSED,
return 0;
}
+
+/**
+ * virCHProcessStartRestore:
+ * @driver: pointer to driver structure
+ * @vm: pointer to virtual machine structure
+ * @from: directory path to restore the VM from
+ *
+ * Starts Cloud-Hypervisor process with the restored VM
+ *
+ * Returns 0 on success or -1 in case of error
+ */
+int
+virCHProcessStartRestore(virCHDriver *driver, virDomainObj *vm, const char *from)
+{
+ virCHDomainObjPrivate *priv = vm->privateData;
+ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(priv->driver);
+
+ if (!priv->monitor) {
+ /* Get the first monitor connection if not already */
+ if (!(priv->monitor = virCHProcessConnectMonitor(driver, vm))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create connection to CH socket"));
+ return -1;
+ }
+ }
+
+ vm->pid = priv->monitor->pid;
+ vm->def->id = vm->pid;
+ priv->machineName = virCHDomainGetMachineName(vm);
+
+ if (virCHMonitorRestoreVM(priv->monitor, from) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to restore domain"));
+ return -1;
+ }
+
+ if (virDomainCgroupSetupCgroup("ch", vm,
+ 0, NULL, /* nnicindexes, nicindexes */
+ &priv->cgroup,
+ cfg->cgroupControllers,
+ 0, /*maxThreadsPerProc*/
+ priv->driver->privileged,
+ priv->machineName) < 0)
+ return -1;
+
+ if (virCHProcessSetup(vm) < 0)
+ return -1;
+
+ virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_FROM_SNAPSHOT);
+
+ return 0;
+}
diff --git a/src/ch/ch_process.h b/src/ch/ch_process.h
index 800e3f4e23..38bfce3b7f 100644
--- a/src/ch/ch_process.h
+++ b/src/ch/ch_process.h
@@ -32,3 +32,7 @@ int virCHProcessStop(virCHDriver *driver,
int virCHProcessSetupVcpu(virDomainObj *vm,
unsigned int vcpuid);
+
+int virCHProcessStartRestore(virCHDriver *driver,
+ virDomainObj *vm,
+ const char *from);
--
2.34.1