From 6b2333022503d9abed152a7c3e96884de564acb9 Mon Sep 17 00:00:00 2001
From: Xie Tongchen <xietongchen1@huawei.com>
Date: Wed, 19 Nov 2025 19:29:16 +0800
Subject: [PATCH 1/7] ham migration: add ham migration feature
Offering: NA
CVE:
Reference: NA
DTS/AR: xxx
Contribute to Upstream(Y/N): N
Description:
Add ham migration feature
Signed-off-by: Xie Tongchen <xietongchen1@huawei.com>
include/libvirt/libvirt-domain.h | 8 +
include/libvirt/libvirt-qemu.h | 3 +
src/conf/domain_conf.c | 7 +
src/conf/domain_conf.h | 1 +
src/driver-hypervisor.h | 7 +
src/libvirt-qemu.c | 64 ++++
src/libvirt_private.syms | 6 +
src/libvirt_qemu.syms | 5 +
src/qemu/meson.build | 1 +
src/qemu/qemu_driver.c | 64 +++-
src/qemu/qemu_ham.c | 576 ++++++++++++++++++++++++++++
src/qemu/qemu_ham.h | 38 ++
src/qemu/qemu_migration.c | 29 ++
src/qemu/qemu_migration.h | 1 +
src/qemu/qemu_migration_params.c | 6 +
src/qemu/qemu_migration_params.h | 1 +
src/qemu_protocol-structs | 9 +
src/remote/qemu_protocol.x | 19 +-
src/remote/remote_daemon.c | 4 +
src/remote/remote_daemon_dispatch.c | 32 ++
src/remote/remote_driver.c | 25 ++
src/util/meson.build | 1 +
src/util/virham.c | 223 +++++++++++
src/util/virham.h | 21 +
tools/virsh-domain.c | 12 +
25 files changed, 1161 insertions(+), 2 deletions(-)
create mode 100644 src/qemu/qemu_ham.c
create mode 100644 src/qemu/qemu_ham.h
create mode 100644 src/util/virham.c
create mode 100644 src/util/virham.h
@@ -1115,6 +1115,12 @@ typedef enum {
* Since: 9.10.0
*/
VIR_MIGRATE_ONECOPY = (1 << 22),
+
+ /* Support ham migration by LDST.
+ *
+ * Since: 9.10.0
+ */
+ VIR_MIGRATE_LDST = (1 << 27),
} virDomainMigrateFlags;
@@ -4203,6 +4209,8 @@ typedef enum {
* suspended and it can later be resumed using virDomainMigrate* APIs with
* VIR_MIGRATE_POSTCOPY_RESUME flag. (Since: 8.5.0) */
VIR_DOMAIN_ABORT_JOB_POSTCOPY = 1 << 0,
+ /* Interrupt ham migration. (Since: 9.10.0) */
+ VIR_DOMAIN_ABORT_JOB_HAM = 1 << 1,
} virDomainAbortJobFlagsValues;
int virDomainAbortJobFlags(virDomainPtr dom,
@@ -51,6 +51,9 @@ int virDomainQemuMonitorCommandWithFiles(virDomainPtr domain,
char **result,
unsigned int flags);
+int virDomainQemuMonitorCommandAsync(virDomainPtr domain, const char *cmd,
+ char **result, int asyncJob);
+
virDomainPtr virDomainQemuAttach(virConnectPtr domain,
unsigned int pid_value,
unsigned int flags);
@@ -4106,6 +4106,7 @@ static void virDomainObjDispose(void *obj)
VIR_DEBUG("obj=%p", dom);
virCondDestroy(&dom->cond);
+ virCondDestroy(&dom->hamCond);
virDomainDefFree(dom->def);
virDomainDefFree(dom->newDef);
@@ -4136,6 +4137,12 @@ virDomainObjNew(virDomainXMLOption *xmlopt)
goto error;
}
+ if (virCondInit(&domain->hamCond) < 0) {
+ virReportSystemError(errno, "%s",
+ _("failed to initialize domain ham condition"));
+ goto error;
+ }
+
if (xmlopt->privateData.alloc) {
domain->privateData = (xmlopt->privateData.alloc)(xmlopt->config.priv);
if (!domain->privateData)
@@ -3250,6 +3250,7 @@ struct _virDomainStateReason {
struct _virDomainObj {
virObjectLockable parent;
virCond cond;
+ virCond hamCond;
virDomainJobObj *job;
@@ -900,6 +900,12 @@ typedef int
char **result,
unsigned int flags);
+typedef int
+(*virDrvDomainQemuMonitorCommandAsync)(virDomainPtr domain,
+ const char *cmd,
+ char **result,
+ int asyncJob);
+
typedef char *
(*virDrvDomainQemuAgentCommand)(virDomainPtr domain,
const char *cmd,
@@ -1644,6 +1650,7 @@ struct _virHypervisorDriver {
virDrvDomainSnapshotDelete domainSnapshotDelete;
virDrvDomainQemuMonitorCommand domainQemuMonitorCommand;
virDrvDomainQemuMonitorCommandWithFiles domainQemuMonitorCommandWithFiles;
+ virDrvDomainQemuMonitorCommandAsync domainQemuMonitorCommandAsync;
virDrvDomainQemuAttach domainQemuAttach;
virDrvDomainQemuAgentCommand domainQemuAgentCommand;
virDrvConnectDomainQemuMonitorEventRegister connectDomainQemuMonitorEventRegister;
@@ -190,6 +190,70 @@ virDomainQemuMonitorCommandWithFiles(virDomainPtr domain,
return -1;
}
+/**
+ * virDomainQemuMonitorCommandAsync:
+ * @domain: a domain object
+ * @cmd: the QEMU monitor command string
+ * @result: a string returned by @cmd
+ * @asyncJob: enum of type virDomainAsyncJob
+ *
+ * This API is QEMU specific, so it will only work with hypervisor
+ * connections to the QEMU driver.
+ *
+ * Send an arbitrary monitor command @cmd to @domain through the
+ * QEMU monitor. There are several requirements to safely and
+ * successfully use this API:
+ *
+ * - A @cmd that queries state without making any modifications is safe
+ * - A @cmd that alters state that is also tracked by libvirt is unsafe,
+ * and may cause libvirtd to crash
+ * - A @cmd that alters state not tracked by the current version of
+ * libvirt is possible as a means to test new QEMU features before
+ * they have support in libvirt, but no guarantees are made to safety
+ *
+ * When @domain is running in async job, @asyncJob is considered to be set
+ * as same as the domain's async job.
+ *
+ * If successful, @result will be filled with the string output of the
+ * @cmd, and the caller must free this string.
+ *
+ * Returns 0 in case of success, -1 in case of failure
+ *
+ * Since: 9.10.0
+ */
+int
+virDomainQemuMonitorCommandAsync(virDomainPtr domain, const char *cmd,
+ char **result, int asyncJob)
+{
+ virConnectPtr conn;
+
+ VIR_DOMAIN_DEBUG(domain, "cmd=%s, result=%p, asyncJob=%d",
+ cmd, result, asyncJob);
+
+ virResetLastError();
+
+ virCheckDomainReturn(domain, -1);
+ conn = domain->conn;
+
+ virCheckNonNullArgGoto(result, error);
+ virCheckReadOnlyGoto(conn->flags, error);
+
+ if (conn->driver->domainQemuMonitorCommandAsync) {
+ int ret;
+ ret = conn->driver->domainQemuMonitorCommandAsync(domain, cmd, result,
+ asyncJob);
+ if (ret < 0)
+ goto error;
+ return ret;
+ }
+
+ virReportUnsupportedError();
+
+ error:
+ virDispatchError(conn);
+ return -1;
+}
+
/**
* virDomainQemuAttach:
* @conn: pointer to a hypervisor connection
@@ -2459,6 +2459,12 @@ virGICVersionTypeToString;
virGSListStringFree;
+# util/virham.h
+virHamClearAll;
+virHamGetClearReqInit;
+virHamRackIpcSyncSendAndRecv;
+
+
# util/virhash.h
virHashAddEntry;
virHashAtomicNew;
@@ -35,3 +35,8 @@ LIBVIRT_QEMU_8.2.0 {
global:
virDomainQemuMonitorCommandWithFiles;
} LIBVIRT_QEMU_1.2.3;
+
+LIBVIRT_QEMU_9.10.0 {
+ global:
+ virDomainQemuMonitorCommandAsync;
+} LIBVIRT_QEMU_8.2.0;
@@ -43,6 +43,7 @@ qemu_driver_sources = [
'qemu_vhost_user_gpu.c',
'qemu_virtiofs.c',
'qemu_hotpatch.c',
+ 'qemu_ham.c',
]
driver_source_files += files(qemu_driver_sources)
@@ -52,6 +52,7 @@
#include "qemu_saveimage.h"
#include "qemu_snapshot.h"
#include "qemu_validate.h"
+#include "qemu_ham.h"
#include "virerror.h"
#include "virlog.h"
@@ -12180,6 +12181,29 @@ qemuDomainAbortJobPostcopy(virDomainObj *vm,
}
+#define HAM_CANCELLED_TIMEOUT (1000ull * 300)
+
+static int
+qemuHamWaitForCancelled(virDomainObj *vm)
+{
+ unsigned long long now = 0;
+ unsigned long long then = 0;
+
+ if (virTimeMillisNow(&now) < 0)
+ return -1;
+
+ then = now + HAM_CANCELLED_TIMEOUT;
+
+ if (virCondWaitUntil(&vm->hamCond, &vm->parent.lock, then) < 0) {
+ virReportSystemError(errno, "%s",
+ _("failed to wait for domain ham condition"));
+ return -1;
+ }
+
+ return 0;
+}
+
+
static int
qemuDomainAbortJobFlags(virDomainPtr dom,
unsigned int flags)
@@ -12187,10 +12211,12 @@ qemuDomainAbortJobFlags(virDomainPtr dom,
virDomainObj *vm;
int ret = -1;
qemuDomainObjPrivate *priv;
+ bool isHam = false;
VIR_DEBUG("flags=0x%x", flags);
- virCheckFlags(VIR_DOMAIN_ABORT_JOB_POSTCOPY, -1);
+ virCheckFlags(VIR_DOMAIN_ABORT_JOB_POSTCOPY |
+ VIR_DOMAIN_ABORT_JOB_HAM, -1);
if (!(vm = qemuDomainObjFromDomain(dom)))
goto cleanup;
@@ -12206,6 +12232,9 @@ qemuDomainAbortJobFlags(virDomainPtr dom,
priv = vm->privateData;
+ isHam = flags & VIR_DOMAIN_ABORT_JOB_HAM &&
+ vm->job->asyncJob == VIR_ASYNC_JOB_MIGRATION_OUT;
+
if (flags & VIR_DOMAIN_ABORT_JOB_POSTCOPY &&
(vm->job->asyncJob != VIR_ASYNC_JOB_MIGRATION_OUT ||
!virDomainObjIsPostcopy(vm, vm->job))) {
@@ -12271,6 +12300,9 @@ qemuDomainAbortJobFlags(virDomainPtr dom,
endjob:
virDomainObjEndJob(vm);
+ if (isHam && ret == 0)
+ ret = qemuHamWaitForCancelled(vm);
+
cleanup:
virDomainObjEndAPI(&vm);
return ret;
@@ -13438,6 +13470,35 @@ qemuDomainQemuMonitorCommand(virDomainPtr domain,
}
+static int
+qemuDomainQemuMonitorCommandAsync(virDomainPtr domain,
+ const char *cmd,
+ char **result,
+ int asyncJob)
+{
+ virQEMUDriver *driver = domain->conn->privateData;
+ virDomainObj *vm = NULL;
+ int ret = -1;
+
+ if (!(vm = qemuDomainObjFromDomain(domain)))
+ goto cleanup;
+
+ if (virDomainQemuMonitorCommandAsyncEnsureACL(domain->conn, vm->def) < 0)
+ goto cleanup;
+
+ if (virDomainObjCheckActive(vm) < 0)
+ goto cleanup;
+
+ qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_CUSTOM_MONITOR, NULL);
+
+ ret = qemuDomainSendQemuMonitorCommandAsync(vm, cmd, result, asyncJob);
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
+
static int
qemuDomainOpenConsole(virDomainPtr dom,
const char *dev_name,
@@ -20307,6 +20368,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
.domainSnapshotDelete = qemuDomainSnapshotDelete, /* 0.8.0 */
.domainQemuMonitorCommand = qemuDomainQemuMonitorCommand, /* 0.8.3 */
.domainQemuMonitorCommandWithFiles = qemuDomainQemuMonitorCommandWithFiles, /* 8.2.0 */
+ .domainQemuMonitorCommandAsync = qemuDomainQemuMonitorCommandAsync, /* 9.10.0 */
.domainQemuAttach = NULL, /* 0.9.4 - 5.5.0 */
.domainQemuAgentCommand = qemuDomainQemuAgentCommand, /* 0.10.0 */
.connectDomainQemuMonitorEventRegister = qemuConnectDomainQemuMonitorEventRegister, /* 1.2.3 */
new file mode 100644
@@ -0,0 +1,576 @@
+/*
+ * qemu_ham.c: QEMU ham migration handling
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#include "qemu_domain.h"
+#include "qemu_ham.h"
+
+#include "virlog.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_ham");
+
+typedef struct _qemuHamRamBlock qemuHamRamBlock;
+struct _qemuHamRamBlock {
+ unsigned int numaId;
+ unsigned long long hva;
+ unsigned long long size;
+};
+
+typedef struct _qemuHamRamInfo qemuHamRamInfo;
+struct _qemuHamRamInfo {
+ int pid;
+ unsigned int scna;
+ unsigned int num;
+ qemuHamRamBlock *blocks;
+};
+
+static void
+qemuHamRamInfoFree(qemuHamRamInfo *ramInfo)
+{
+ if (!ramInfo)
+ return;
+
+ VIR_FREE(ramInfo->blocks);
+ VIR_FREE(ramInfo);
+}
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuHamRamInfo, qemuHamRamInfoFree);
+
+void
+qemuHamMigrationInfoFree(qemuHamMigrationInfo *hamInfo)
+{
+ if (!hamInfo)
+ return;
+
+ VIR_FREE(hamInfo->name);
+ VIR_FREE(hamInfo->srcHostname);
+ VIR_FREE(hamInfo->dstHostname);
+ VIR_FREE(hamInfo);
+}
+
+int
+qemuDomainSendQemuMonitorCommandAsync(virDomainObj *vm,
+ const char *cmd,
+ char **result,
+ virDomainAsyncJob asyncJob)
+{
+ qemuDomainObjPrivate *priv = vm->privateData;
+ int ret;
+
+ if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
+ return -1;
+
+ ret = qemuMonitorArbitraryCommand(priv->mon, cmd, -1, result, false);
+ qemuDomainObjExitMonitor(vm);
+
+ return ret;
+}
+
+static virJSONValue *
+qemuHamSendQemuMonitorCommand(virDomainObj *vm,
+ virDomainPtr domain,
+ const char *commandName,
+ virJSONValue *arguments,
+ virDomainAsyncJob asyncJob)
+{
+ g_autoptr(virJSONValue) command = NULL;
+ g_autoptr(virJSONValue) args = virJSONValueCopy(arguments);
+ g_autofree char *monitorCmd = NULL;
+ g_autofree char *result = NULL;
+ int ret = -1;
+
+ if (virJSONValueObjectAdd(&command,
+ "s:execute", commandName,
+ "A:arguments", &args,
+ NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to wrap execute command '%1$s' and arguments into a QMP command wrapper"),
+ commandName);
+ return NULL;
+ }
+
+ if (!(monitorCmd = virJSONValueToString(command, false))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to convert QMP command json object to string"));
+ return NULL;
+ }
+
+ if (vm)
+ ret = qemuDomainSendQemuMonitorCommandAsync(vm, monitorCmd, &result, asyncJob);
+
+ if (domain)
+ ret = domain->conn->driver->domainQemuMonitorCommandAsync(domain, monitorCmd, &result, asyncJob);
+
+ if (ret < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to get result of QMP command: %1$s"), monitorCmd);
+ return NULL;
+ }
+
+ return virJSONValueFromString(result);
+}
+
+static qemuHamRamInfo *
+qemuHamGetRamInfo(virDomainPtr domain)
+{
+ g_autoptr(qemuHamRamInfo) ramInfo = g_new0(qemuHamRamInfo, 1);
+ g_autoptr(virJSONValue) result = NULL;
+ virJSONValue *data = NULL;
+ virJSONValue *blocksArray = NULL;
+ int i;
+
+ if (!(result = qemuHamSendQemuMonitorCommand(NULL, domain, "query-ramblock",
+ NULL, VIR_ASYNC_JOB_MIGRATION_IN))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("unable to execute QMP command 'query-ramblock'"));
+ return NULL;
+ }
+
+ if (!(data = virJSONValueObjectGetObject(result, "return"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("'return' member missing"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectGetNumberInt(data, "pid", &ramInfo->pid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("'pid' missing in return value"));
+ return NULL;
+ }
+
+ if (!(blocksArray = virJSONValueObjectGetArray(data, "block"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'block' was missing or not an array"));
+ return NULL;
+ }
+
+ if ((ramInfo->num = virJSONValueArraySize(blocksArray)) <= 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'block' array is empty"));
+ return NULL;
+ }
+
+ ramInfo->blocks = g_new0(qemuHamRamBlock, ramInfo->num);
+
+ for (i = 0; i < ramInfo->num; i++) {
+ virJSONValue *entry = virJSONValueArrayGet(blocksArray, i);
+ qemuHamRamBlock *block = ramInfo->blocks + i;
+
+ if (!entry) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("array element missing"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectGetNumberUlong(entry, "hva", &block->hva) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("found block wihtout hva"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectGetNumberUlong(entry, "size", &block->size) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("found block wihtout size"));
+ return NULL;
+ }
+ }
+
+ return g_steal_pointer(&ramInfo);
+}
+
+static char *
+qemuHamGetBorrowReq(qemuHamMigrationInfo *hamInfo, qemuHamRamInfo *ramInfo)
+{
+ g_autoptr(virJSONValue) borrowReq = virJSONValueNewObject();
+ g_autoptr(virJSONValue) valist = virJSONValueNewArray();
+ int i;
+
+ if (virJSONValueObjectAppendString(borrowReq, "action", "borrow") < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add action to borrow request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendString(borrowReq, "srcHostname", hamInfo->srcHostname) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add srcHostname to borrow request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendNumberInt(borrowReq, "srcPid", hamInfo->srcPid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add srcPid to borrow request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendString(borrowReq, "dstHostname", hamInfo->dstHostname) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add dstHostname to borrow request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendNumberInt(borrowReq, "dstPid", hamInfo->dstPid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add dstPid to borrow request json object"));
+ return NULL;
+ }
+
+ for (i = 0; i < ramInfo->num; i++) {
+ g_autoptr(virJSONValue) va = virJSONValueNewObject();
+ qemuHamRamBlock *block = ramInfo->blocks + i;
+
+ if (virJSONValueObjectAppendNumberUlong(va, "start", block->hva) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add start to va json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendNumberUlong(va, "length", block->size) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add length to va json object"));
+ return NULL;
+ }
+
+ if (virJSONValueArrayAppend(valist, &va) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add va to valist json array"));
+ return NULL;
+ }
+ }
+
+ if (virJSONValueObjectAppend(borrowReq, "valist", &valist) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add valist to borrow request json object"));
+ return NULL;
+ }
+
+ return virJSONValueToString(borrowReq, false);
+}
+
+static int
+qemuHamGetBorrowInfo(qemuHamMigrationInfo *hamInfo,
+ qemuHamRamInfo *ramInfo,
+ const char *respStr)
+{
+ g_autoptr(virJSONValue) resp = NULL;
+ virJSONValue *message = NULL;
+ virJSONValue *numaIdsArray = NULL;
+ const char *name;
+ int code;
+ int i;
+
+ if (!(resp = virJSONValueFromString(respStr))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to parse borrow info response to json object"));
+ return -1;
+ }
+
+ if (virJSONValueObjectGetNumberInt(resp, "code", &code) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'code' missing in borrow info response"));
+ return -1;
+ }
+
+ if (code != 200) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("borrow info request failed with code %1$d"), code);
+ return -1;
+ }
+
+ if (!(message = virJSONValueObjectGet(resp, "message"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'message' missing in borrow info response"));
+ return -1;
+ }
+
+ if (!(name = virJSONValueObjectGetString(message, "name"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'name' missing in message json object"));
+ return -1;
+ }
+
+ hamInfo->name = g_strdup(name);
+
+ if (virJSONValueObjectGetNumberUint(message, "scna", &ramInfo->scna) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'scna' missing in message json object"));
+ return -1;
+ }
+
+ if (!(numaIdsArray = virJSONValueObjectGetArray(message, "numaIds"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'numaIds' was missing or not an array"));
+ return -1;
+ }
+
+ if (ramInfo->num != virJSONValueArraySize(numaIdsArray)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("number of numa mismatch with number of block"));
+ return -1;
+ }
+
+ for (i = 0; i < ramInfo->num; i++) {
+ virJSONValue *entry = virJSONValueArrayGet(numaIdsArray, i);
+ qemuHamRamBlock *block = ramInfo->blocks + i;
+
+ if (!entry) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("array element missing"));
+ return -1;
+ }
+
+ if (virJSONValueGetNumberUint(entry, &block->numaId) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("missing or invalid element of numaIds array"));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+qemuHamSendRamInfo(qemuHamRamInfo *ramInfo, virDomainObj *vm)
+{
+ g_autoptr(virJSONValue) arguments = virJSONValueNewObject();
+ g_autoptr(virJSONValue) blocksArray = virJSONValueNewArray();
+ g_autoptr(virJSONValue) result = NULL;
+ int i;
+
+ if (virJSONValueObjectAppendNumberInt(arguments, "pid", ramInfo->pid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add pid to arguments json object"));
+ return -1;
+ }
+
+ if (virJSONValueObjectAppendNumberUint(arguments, "scna", ramInfo->scna) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add scna to arguments json object"));
+ return -1;
+ }
+
+ for (i = 0; i < ramInfo->num; i++) {
+ g_autoptr(virJSONValue) entry = virJSONValueNewObject();
+ qemuHamRamBlock *block = ramInfo->blocks + i;
+
+ if (virJSONValueObjectAppendNumberUint(entry, "numa-id", block->numaId) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add numa id to json object"));
+ return -1;
+ }
+
+ if (virJSONValueObjectAppendNumberUlong(entry, "size", block->size) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add size to json object"));
+ return -1;
+ }
+
+ if (virJSONValueArrayAppend(blocksArray, &entry) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add entry to block json array"));
+ return -1;
+ }
+ }
+
+ if (virJSONValueObjectAppend(arguments, "block", &blocksArray) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add block to arguments json object"));
+ return -1;
+ }
+
+ if (!(result = qemuHamSendQemuMonitorCommand(vm, NULL, "recv-rmtnuma", arguments,
+ VIR_ASYNC_JOB_MIGRATION_OUT))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("unable to execute QMP command 'recv-rmtnuma'"));
+ return -1;
+ }
+
+ if (virJSONValueObjectHasKey(result, "error")) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("execute QMP command 'recv-rmtnuma' failed"));
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+qemuHamRollbackPages(virDomainObj *vm)
+{
+ g_autoptr(virJSONValue) result = NULL;
+
+ if (!(result = qemuHamSendQemuMonitorCommand(vm, NULL, "rollback-pages", NULL,
+ VIR_ASYNC_JOB_MIGRATION_OUT))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("unable to execute QMP command 'rollback-pages'"));
+ return -1;
+ }
+
+ if (virJSONValueObjectHasKey(result, "error")) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("execute QMP command 'rollback-pages' failed"));
+ return -1;
+ }
+
+ return 0;
+}
+
+static char *
+qemuHamGetClearReq(qemuHamMigrationInfo *hamInfo, virHamClearType type)
+{
+ g_autofree char *clearReqInit = NULL;
+ g_autoptr(virJSONValue) clearReq = NULL;
+
+ if (!(clearReqInit = virHamGetClearReqInit(type, hamInfo->srcHostname))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add action, type and srcHostname to clear request"));
+ return NULL;
+ }
+
+ if (!(clearReq = virJSONValueFromString(clearReqInit))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create clear request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendString(clearReq, "name", hamInfo->name) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add name to clear request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendNumberInt(clearReq, "srcPid", hamInfo->srcPid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add srcPid to clear request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendString(clearReq, "dstHostname", hamInfo->dstHostname) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add dstHostname to clear request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendNumberInt(clearReq, "dstPid", hamInfo->dstPid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add dstPid to clear request json object"));
+ return NULL;
+ }
+
+ return virJSONValueToString(clearReq, false);
+}
+
+void
+qemuHamSendClearReq(qemuHamMigrationInfo *hamInfo, virHamClearType type)
+{
+ g_autofree char *req = NULL;
+
+ if (!(req = qemuHamGetClearReq(hamInfo, type))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create clear request"));
+ return;
+ }
+ VIR_INFO("Ham migration clear borrowed numa request: %s", NULLSTR(req));
+
+ if (!virHamRackIpcSyncSendAndRecv(req))
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to clear borrowed numa"));
+}
+
+int
+qemuHamMigrationPrepare(qemuHamMigrationInfo *hamInfo,
+ virConnectPtr dconn,
+ virDomainObj *vm,
+ char *cookieout,
+ int cookieoutlen)
+{
+ g_autoptr(xmlXPathContext) ctxt = NULL;
+ virDomainPtr ddomain = NULL;
+ g_autofree char *dname = NULL;
+ g_autofree char *duuidstr = NULL;
+ unsigned char duuid[VIR_UUID_BUFLEN];
+ g_autoptr(qemuHamRamInfo) ramInfo = NULL;
+ g_autofree char *req = NULL;
+ g_autofree char *resp = NULL;
+ int ret = -1;
+
+ if (cookieout && cookieoutlen &&
+ cookieout[cookieoutlen-1] != '\0') {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("migration cookie was not NULL terminated"));
+ return -1;
+ }
+
+ if (!virXMLParseStringCtxt(cookieout, _("(qemu_migration_cookie)"), &ctxt)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to parse xml context"));
+ return -1;
+ }
+
+ if (!(dname = virXPathString("string(./name[1])", ctxt))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("missing name element in migration data"));
+ return -1;
+ }
+
+ if (!(duuidstr = virXPathString("string(./uuid[1])", ctxt))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("missing uuid element in migration data"));
+ return -1;
+ }
+
+ if (virUUIDParse(duuidstr, duuid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed uuid element"));
+ return -1;
+ }
+
+ if (!(hamInfo->dstHostname = virXPathString("string(./hostname[1])", ctxt))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("missing hostname element in migration data"));
+ return -1;
+ }
+
+ ddomain = virGetDomain(dconn, dname, duuid, -1);
+
+ VIR_DEBUG("Begin to get ram info of domain on destination for ham migration");
+ if (!(ramInfo = qemuHamGetRamInfo(ddomain))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to get ram info of domain on destination"));
+ goto cleanup;
+ }
+
+ hamInfo->dstPid = ramInfo->pid;
+
+ VIR_DEBUG("Begin to get borrowed numa info for ham migration");
+ if (!(req = qemuHamGetBorrowReq(hamInfo, ramInfo))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create borrow numa request"));
+ goto cleanup;
+ }
+ VIR_INFO("Ham migration borrow numa request: %s", NULLSTR(req));
+
+ if (!(resp = virHamRackIpcSyncSendAndRecv(req))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to get borrow response from RackAgentIpcServer"));
+ goto cleanup;
+ }
+ VIR_INFO("Ham migration borrow numa response: %s", NULLSTR(resp));
+
+ if (qemuHamGetBorrowInfo(hamInfo, ramInfo, resp) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to get borrow info"));
+ goto cleanup;
+ }
+
+ VIR_DEBUG("Send ram info to domain on source for ham migration");
+ if (qemuHamSendRamInfo(ramInfo, vm) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to send ram info to domain on source"));
+ goto cleanup;
+ }
+
+ ret = 0;
+
+ cleanup:
+ if (ddomain)
+ virObjectUnref(ddomain);
+
+ return ret;
+}
new file mode 100644
@@ -0,0 +1,38 @@
+/*
+ * qemu_ham.h: QEMU ham migration handling
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#pragma once
+
+#include "virconftypes.h"
+#include "virdomainjob.h"
+#include "virham.h"
+
+typedef struct _qemuHamMigrationInfo qemuHamMigrationInfo;
+struct _qemuHamMigrationInfo {
+ char *name;
+ char *srcHostname;
+ char *dstHostname;
+ int srcPid;
+ int dstPid;
+};
+
+void qemuHamMigrationInfoFree(qemuHamMigrationInfo *hamInfo);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuHamMigrationInfo, qemuHamMigrationInfoFree);
+
+int qemuDomainSendQemuMonitorCommandAsync(virDomainObj *vm,
+ const char *cmd,
+ char **result,
+ virDomainAsyncJob asyncJob);
+
+int qemuHamMigrationPrepare(qemuHamMigrationInfo *hamInfo,
+ virConnectPtr dconn,
+ virDomainObj *vm,
+ char *cookieout,
+ int cookieoutlen);
+
+int qemuHamRollbackPages(virDomainObj *vm);
+
+void qemuHamSendClearReq(qemuHamMigrationInfo *hamInfo, virHamClearType type);
@@ -39,6 +39,7 @@
#include "qemu_slirp.h"
#include "qemu_block.h"
#include "qemu_tpm.h"
+#include "qemu_ham.h"
#include "domain_audit.h"
#include "virlog.h"
@@ -58,6 +59,7 @@
#include "virprocess.h"
#include "virdomainsnapshotobjlist.h"
#include "virutil.h"
+#include "virham.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
@@ -5533,6 +5535,7 @@ qemuMigrationSrcPerformPeer2Peer3(virQEMUDriver *driver,
int maxparams = 0;
size_t i;
bool offline = !!(flags & VIR_MIGRATE_OFFLINE);
+ g_autoptr(qemuHamMigrationInfo) hamInfo = NULL;
VIR_DEBUG("driver=%p, sconn=%p, dconn=%p, dconnuri=%s, vm=%p, xmlin=%s, "
"dname=%s, uri=%s, graphicsuri=%s, listenAddress=%s, "
@@ -5679,6 +5682,19 @@ qemuMigrationSrcPerformPeer2Peer3(virQEMUDriver *driver,
goto finish;
}
+ if (flags & VIR_MIGRATE_LDST) {
+ VIR_INFO("Prepare for ham migration");
+ hamInfo = g_new0(qemuHamMigrationInfo, 1);
+ hamInfo->srcHostname = virGetHostname();
+ hamInfo->srcPid = vm->pid;
+ if (qemuHamMigrationPrepare(hamInfo, dconn, vm, cookieout, cookieoutlen) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to prepare for ham migration"));
+ virErrorPreserveLast(&orig_err);
+ goto finish;
+ }
+ }
+
/* Perform the migration. The driver isn't supposed to return
* until the migration is complete. The src VM should remain
* running, but in paused state until the destination can
@@ -5836,6 +5852,19 @@ qemuMigrationSrcPerformPeer2Peer3(virQEMUDriver *driver,
ret = -1;
}
+ if ((flags & VIR_MIGRATE_LDST) && hamInfo && hamInfo->name) {
+ VIR_INFO("Cleanup for ham migration");
+ if (ret < 0) {
+ if (qemuHamRollbackPages(vm) < 0)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to rollback memory pages for ham migration"));
+ qemuHamSendClearReq(hamInfo, VIR_HAM_CLEAR_MIGRATE_FAILURE);
+ virCondBroadcast(&vm->hamCond);
+ } else {
+ qemuHamSendClearReq(hamInfo, VIR_HAM_CLEAR_MIGRATE_SUCCESS);
+ }
+ }
+
virObjectUnref(st);
virErrorRestore(&orig_err);
@@ -64,6 +64,7 @@
VIR_MIGRATE_ZEROCOPY | \
VIR_MIGRATE_RETURNPATH | \
VIR_MIGRATE_ONECOPY | \
+ VIR_MIGRATE_LDST | \
0)
/* All supported migration parameters and their types. */
@@ -100,6 +100,7 @@ VIR_ENUM_IMPL(qemuMigrationCapability,
"return-path",
"zero-copy-send",
"onecopy",
+ "ldst",
);
@@ -201,6 +202,11 @@ static const qemuMigrationParamsFlagMapItem qemuMigrationParamsFlagMap[] = {
VIR_MIGRATE_ONECOPY,
QEMU_MIGRATION_CAP_ONECOPY,
QEMU_MIGRATION_SOURCE},
+
+ {QEMU_MIGRATION_FLAG_REQUIRED,
+ VIR_MIGRATE_LDST,
+ QEMU_MIGRATION_CAP_LDST,
+ QEMU_MIGRATION_SOURCE},
};
/* Translation from VIR_MIGRATE_PARAM_* typed parameters to
@@ -41,6 +41,7 @@ typedef enum {
QEMU_MIGRATION_CAP_RETURN_PATH,
QEMU_MIGRATION_CAP_ZERO_COPY_SEND,
QEMU_MIGRATION_CAP_ONECOPY,
+ QEMU_MIGRATION_CAP_LDST,
QEMU_MIGRATION_CAP_LAST
} qemuMigrationCapability;
@@ -55,6 +55,14 @@ struct qemu_domain_monitor_command_with_files_args {
struct qemu_domain_monitor_command_with_files_ret {
remote_nonnull_string result;
};
+struct qemu_domain_monitor_command_async_args {
+ remote_nonnull_domain dom;
+ remote_nonnull_string cmd;
+ int asyncJob;
+};
+struct qemu_domain_monitor_command_async_ret {
+ remote_nonnull_string result;
+};
enum qemu_procedure {
QEMU_PROC_DOMAIN_MONITOR_COMMAND = 1,
QEMU_PROC_DOMAIN_ATTACH = 2,
@@ -63,4 +71,5 @@ enum qemu_procedure {
QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER = 5,
QEMU_PROC_DOMAIN_MONITOR_EVENT = 6,
QEMU_PROC_DOMAIN_MONITOR_COMMAND_WITH_FILES = 7,
+ QEMU_PROC_DOMAIN_MONITOR_COMMAND_ASYNC = 8,
};
@@ -89,6 +89,16 @@ struct qemu_domain_monitor_command_with_files_ret {
remote_nonnull_string result;
};
+struct qemu_domain_monitor_command_async_args {
+ remote_nonnull_domain dom;
+ remote_nonnull_string cmd;
+ int asyncJob;
+};
+
+struct qemu_domain_monitor_command_async_ret {
+ remote_nonnull_string result;
+};
+
/* Define the program number, protocol version and procedure numbers here. */
const QEMU_PROGRAM = 0x20008087;
@@ -169,5 +179,12 @@ enum qemu_procedure {
* @priority: low
* @acl: domain:write
*/
- QEMU_PROC_DOMAIN_MONITOR_COMMAND_WITH_FILES = 7
+ QEMU_PROC_DOMAIN_MONITOR_COMMAND_WITH_FILES = 7,
+
+ /**
+ * @generate: none
+ * @priority: low
+ * @acl: domain:write
+ */
+ QEMU_PROC_DOMAIN_MONITOR_COMMAND_ASYNC = 8
};
@@ -55,6 +55,7 @@
#include "virsystemd.h"
#include "virhostuptime.h"
#include "virdaemon.h"
+#include "virham.h"
#include "driver.h"
@@ -1193,6 +1194,9 @@ int main(int argc, char **argv) {
goto cleanup;
}
+ VIR_DEBUG("Clear all borrowed numa nodes of ham migration");
+ virHamClearAll(virGetHostname());
+
#if defined(__linux__) && defined(NETLINK_ROUTE)
/* Register the netlink event service for NETLINK_ROUTE */
if (virNetlinkEventServiceStart(NETLINK_ROUTE, 0) < 0) {
@@ -4739,6 +4739,38 @@ qemuDispatchDomainMonitorCommandWithFiles(virNetServer *server G_GNUC_UNUSED,
}
+static int
+qemuDispatchDomainMonitorCommandAsync(virNetServer *server G_GNUC_UNUSED,
+ virNetServerClient *client,
+ virNetMessage *msg G_GNUC_UNUSED,
+ struct virNetMessageError *rerr,
+ qemu_domain_monitor_command_async_args *args,
+ qemu_domain_monitor_command_async_ret *ret)
+{
+ virDomainPtr dom = NULL;
+ int rv = -1;
+ virConnectPtr conn = remoteGetHypervisorConn(client);
+
+ if (!conn)
+ goto cleanup;
+
+ if (!(dom = get_nonnull_domain(conn, args->dom)))
+ goto cleanup;
+
+ if (virDomainQemuMonitorCommandAsync(dom, args->cmd, &ret->result,
+ args->asyncJob) < 0)
+ goto cleanup;
+
+ rv = 0;
+
+ cleanup:
+ if (rv < 0)
+ virNetMessageSaveError(rerr);
+ virObjectUnref(dom);
+ return rv;
+}
+
+
static int
remoteDispatchDomainMigrateBegin3(virNetServer *server G_GNUC_UNUSED,
virNetServerClient *client,
@@ -5553,6 +5553,30 @@ remoteDomainQemuMonitorCommandWithFiles(virDomainPtr domain,
}
+static int
+remoteDomainQemuMonitorCommandAsync(virDomainPtr domain, const char *cmd,
+ char **result, int asyncJob)
+{
+ qemu_domain_monitor_command_async_args args;
+ g_auto(qemu_domain_monitor_command_async_ret) ret = {0};
+ struct private_data *priv = domain->conn->privateData;
+ VIR_LOCK_GUARD lock = remoteDriverLock(priv);
+
+ make_nonnull_domain(&args.dom, domain);
+ args.cmd = (char *)cmd;
+ args.asyncJob = asyncJob;
+
+ if (call(domain->conn, priv, REMOTE_CALL_QEMU, QEMU_PROC_DOMAIN_MONITOR_COMMAND_ASYNC,
+ (xdrproc_t) xdr_qemu_domain_monitor_command_async_args, (char *) &args,
+ (xdrproc_t) xdr_qemu_domain_monitor_command_async_ret, (char *) &ret) == -1)
+ return -1;
+
+ *result = g_steal_pointer(&ret.result);
+
+ return 0;
+}
+
+
static char *
remoteDomainMigrateBegin3(virDomainPtr domain,
const char *xmlin,
@@ -7786,6 +7810,7 @@ static virHypervisorDriver hypervisor_driver = {
.domainSnapshotDelete = remoteDomainSnapshotDelete, /* 0.8.0 */
.domainQemuMonitorCommand = remoteDomainQemuMonitorCommand, /* 0.8.3 */
.domainQemuMonitorCommandWithFiles = remoteDomainQemuMonitorCommandWithFiles, /* 8.2.0 */
+ .domainQemuMonitorCommandAsync = remoteDomainQemuMonitorCommandAsync, /* 9.10.0 */
.domainQemuAttach = remoteDomainQemuAttach, /* 0.9.4 */
.domainQemuAgentCommand = remoteDomainQemuAgentCommand, /* 0.10.0 */
.connectDomainQemuMonitorEventRegister = remoteConnectDomainQemuMonitorEventRegister, /* 1.2.3 */
@@ -111,6 +111,7 @@ util_sources = [
'virvsock.c',
'virxml.c',
'virub.c',
+ 'virham.c',
]
util_public_sources = files(
new file mode 100644
@@ -0,0 +1,223 @@
+/*
+ * virham.c: rack ipc functions for ham migration
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#include <dlfcn.h>
+
+#include "viralloc.h"
+#include "virerror.h"
+#include "virfile.h"
+#include "virham.h"
+#include "virjson.h"
+#include "virlog.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+VIR_LOG_INIT("util.ham");
+
+/* rack ipc path for ham migration */
+#define VIR_HAM_RACK_IPC_PATH "/usr/local/softbus/ctrlbus/lib/librack_com.so"
+/* rack ipc timeout(seconds) for ham migration */
+#define VIR_HAM_RACK_IPC_TIMEOUT (60 * 30)
+
+typedef int (*virHamRackIpcClientStart)(uint16_t timeout);
+
+typedef struct _virHamRackIpcData virHamRackIpcData;
+struct _virHamRackIpcData {
+ uint8_t *buffer;
+ uint32_t length;
+};
+
+typedef int (*virHamRackIpcSyncSend)(virHamRackIpcData *sendData, virHamRackIpcData *recvData);
+
+typedef void (*virHamRackIpcCallback)(void *ctx,
+ void *recv,
+ uint32_t len,
+ int32_t result);
+
+typedef struct _virHamRackIpcCallbackDef virHamRackIpcCallbackDef;
+struct _virHamRackIpcCallbackDef {
+ virHamRackIpcCallback cb;
+ void *cbCtx;
+};
+
+typedef int (*virHamRackIpcAsyncSend)(virHamRackIpcData *sendData, virHamRackIpcCallbackDef *callback);
+
+typedef struct _virHamRackIpcClient virHamRackIpcClient;
+struct _virHamRackIpcClient {
+ virHamRackIpcClientStart start;
+ virHamRackIpcSyncSend syncSend;
+ virHamRackIpcAsyncSend asyncSend;
+};
+
+static virHamRackIpcClient *rackIpcClient;
+
+static void
+virHamRackIpcDataFree(virHamRackIpcData *data)
+{
+ if (!data)
+ return;
+
+ VIR_FREE(data->buffer);
+ VIR_FREE(data);
+}
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virHamRackIpcData, virHamRackIpcDataFree);
+
+static int
+virHamRackIpcInitialize(void)
+{
+ void *handle = NULL;
+ int code;
+
+ if (!virFileExists(VIR_HAM_RACK_IPC_PATH)) {
+ VIR_WARN("Ham rack ipc file doesn't exist");
+ return -1;
+ }
+
+ if (!(handle = dlopen(VIR_HAM_RACK_IPC_PATH, RTLD_NOW | RTLD_LOCAL))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to load rack ipc file: %1$s"), dlerror());
+ return -1;
+ }
+
+ rackIpcClient = g_new0(virHamRackIpcClient, 1);
+
+ if (!(rackIpcClient->start = dlsym(handle, "RackStartIpcClientWithTimeout"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to find symbol 'RackStartIpcClientWithTimeout': %1$s"), dlerror());
+ goto error;
+ }
+
+ if (!(rackIpcClient->syncSend = dlsym(handle, "RackSyncSendForHam"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to find symbol 'RackSyncSendForHam': %1$s"), dlerror());
+ goto error;
+ }
+
+ if (!(rackIpcClient->asyncSend = dlsym(handle, "RackAsyncSendForHam"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to find symbol 'RackAsyncSendForHam': %1$s"), dlerror());
+ goto error;
+ }
+
+ if ((code = rackIpcClient->start(VIR_HAM_RACK_IPC_TIMEOUT)) != 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to start RackIpcClient with code %1$d"), code);
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ dlclose(handle);
+ VIR_FREE(rackIpcClient);
+ return -1;
+}
+
+char *
+virHamRackIpcSyncSendAndRecv(const char *req)
+{
+ g_autoptr(virHamRackIpcData) sendData = g_new0(virHamRackIpcData, 1);
+ g_autoptr(virHamRackIpcData) recvData = g_new0(virHamRackIpcData, 1);
+ int code;
+ g_autofree char *resp = NULL;
+
+ if (!rackIpcClient && virHamRackIpcInitialize() < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to initialize RackIpcClient"));
+ return NULL;
+ }
+
+ sendData->length = strlen(req);
+ sendData->buffer = g_malloc0(sendData->length);
+ memcpy(sendData->buffer, req, sendData->length);
+
+ if ((code = rackIpcClient->syncSend(sendData, recvData)) != 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to communicate with RackAgentIpcServer with code %1$d"), code);
+ return NULL;
+ }
+
+ if (!(resp = g_new0(char, recvData->length + 1))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to allocate memory of the length %1$u"), recvData->length);
+ return NULL;
+ }
+
+ memcpy(resp, recvData->buffer, recvData->length);
+ /* MatrixVirt returned length does not include the string terminator */
+ resp[recvData->length] = '\0';
+
+ return g_steal_pointer(&resp);
+}
+
+char *
+virHamGetClearReqInit(virHamClearType type, const char *hostname)
+{
+ g_autoptr(virJSONValue) clearReq = virJSONValueNewObject();
+
+ if (virJSONValueObjectAppendString(clearReq, "action", "clear") < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add action to clear request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendNumberInt(clearReq, "type", type) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add type to clear request json object"));
+ return NULL;
+ }
+
+ if (virJSONValueObjectAppendString(clearReq, "srcHostname", hostname) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to add srcHostname to clear request json object"));
+ return NULL;
+ }
+
+ return virJSONValueToString(clearReq, false);
+}
+
+static int
+virHamRackIpcAsyncSendAndRecv(const char *req, virHamRackIpcCallbackDef *callback)
+{
+ g_autoptr(virHamRackIpcData) sendData = g_new0(virHamRackIpcData, 1);
+ int code;
+
+ if (!rackIpcClient && virHamRackIpcInitialize() < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to initialize RackIpcClient"));
+ return -1;
+ }
+
+ sendData->length = strlen(req);
+ sendData->buffer = g_malloc0(sendData->length);
+ memcpy(sendData->buffer, req, sendData->length);
+
+ if ((code = rackIpcClient->asyncSend(sendData, callback)) != 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to send request to RackAgentIpcServer with code %1$d"), code);
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+virHamClearAll(const char *hostname)
+{
+ g_autofree char *req = NULL;
+ virHamRackIpcCallbackDef *callback = g_new0(virHamRackIpcCallbackDef, 1);
+
+ if (!(req = virHamGetClearReqInit(VIR_HAM_CLEAR_ALL, hostname))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create clear request"));
+ return;
+ }
+ VIR_INFO("Ham migration clear all borrowed numa nodes request: %s", NULLSTR(req));
+
+ if (virHamRackIpcAsyncSendAndRecv(req, callback) < 0)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to clear all borrowed numa nodes"));
+}
new file mode 100644
@@ -0,0 +1,21 @@
+/*
+ * virham.h: rack ipc functions for ham migration
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#pragma once
+
+typedef enum {
+ VIR_HAM_CLEAR_ALL = 0, /* Clear all resource */
+ VIR_HAM_CLEAR_MIGRATE_SUCCESS, /* Clear specific resource when ham migration succeed */
+ VIR_HAM_CLEAR_MIGRATE_FAILURE, /* Clear specific resource when ham migration failed */
+
+ VIR_HAM_CLEAR_LAST
+} virHamClearType;
+
+char *virHamRackIpcSyncSendAndRecv(const char *req);
+
+char *virHamGetClearReqInit(virHamClearType type, const char *hostname);
+
+void virHamClearAll(const char *hostname);
@@ -6527,6 +6527,10 @@ static const vshCmdOptDef opts_domjobabort[] = {
.type = VSH_OT_BOOL,
.help = N_("interrupt post-copy migration")
},
+ {.name = "ham",
+ .type = VSH_OT_BOOL,
+ .help = N_("interrupt ham migration")
+ },
{.name = NULL}
};
@@ -6543,6 +6547,9 @@ cmdDomjobabort(vshControl *ctl, const vshCmd *cmd)
if (vshCommandOptBool(cmd, "postcopy"))
flags |= VIR_DOMAIN_ABORT_JOB_POSTCOPY;
+ if (vshCommandOptBool(cmd, "ham"))
+ flags |= VIR_DOMAIN_ABORT_JOB_HAM;
+
if (flags == 0)
rc = virDomainAbortJob(dom);
else
@@ -11137,6 +11144,10 @@ static const vshCmdOptDef opts_migrate[] = {
.type = VSH_OT_BOOL,
.help = N_("enable one-copy migration")
},
+ {.name = "ldst",
+ .type = VSH_OT_BOOL,
+ .help = N_("enable ham migration")
+ },
{.name = NULL}
};
@@ -11188,6 +11199,7 @@ doMigrate(void *opaque)
{ "suspend", VIR_MIGRATE_PAUSED },
{ "return-path", VIR_MIGRATE_RETURNPATH },
{ "onecopy", VIR_MIGRATE_ONECOPY },
+ { "ldst", VIR_MIGRATE_LDST },
};
#ifndef WIN32
--
2.51.0.windows.1