From: Purna Pavan Chandra Aekkaladevi <paekkaladevi@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@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
_______________________________________________
Devel mailing list -- devel@lists.libvirt.org
To unsubscribe send an email to devel-leave@lists.libvirt.org
On 2/6/24 10:34, Purna Pavan Chandra Aekkaladevi wrote: > From: Purna Pavan Chandra Aekkaladevi <paekkaladevi@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@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(-) There are multiple APIs implemented here which makes it unnecessarily hard to do a proper review. Can you please split this huge patch into smaller ones? > > 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; > + } I don't think we should do this. Users can start a transient domain with the same name and UUID as a defined domain, but a different XML. If we did this then previously saved data is lost (and domain disks are possibly left in an inconsistent state as the domain's memory would be removed here). > + > 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 Ah, so it stores multiple files inside of the directory? Well, this calls for update of virDomainSave() documentation then. I don't think it's worth going through the hassle of packing everything under that dir into a single file (e.g. via 'tar'). This is where I stop my review. Sorry for letting this slip. Michal _______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
© 2016 - 2024 Red Hat, Inc.