[PATCH 0/1] [RFC] Live migration support for ch driver

This change serves as a proof of concept that adds live migration support to the Cloud Hypervisor driver. It is meant to show feasibility and to receive early feedback. I tested the live migration by invoking: virsh -c ch:///session migrate --domain vmName --desturi ch+ssh://dstHost/session --live Opens: * What is required for a minimal viable live migration to be merged? * Job state tracking? (virDomainObjBeginJob, ...) * What should 'virsh domjobinfo' show? * Testing? * Anything else? Stefan Kober (1): Initial CH migrate API src/ch/ch_conf.h | 4 + src/ch/ch_domain.h | 2 + src/ch/ch_driver.c | 362 +++++++++++++++++++++++++++++- src/ch/ch_monitor.c | 156 +++++++++++++ src/ch/ch_monitor.h | 8 + src/ch/ch_process.c | 136 ++++++++++- src/ch/ch_process.h | 6 + src/hypervisor/domain_interface.c | 1 + src/libvirt-domain.c | 15 +- 9 files changed, 680 insertions(+), 10 deletions(-) -- 2.49.0

--- src/ch/ch_conf.h | 4 + src/ch/ch_domain.h | 2 + src/ch/ch_driver.c | 362 +++++++++++++++++++++++++++++- src/ch/ch_monitor.c | 156 +++++++++++++ src/ch/ch_monitor.h | 8 + src/ch/ch_process.c | 136 ++++++++++- src/ch/ch_process.h | 6 + src/hypervisor/domain_interface.c | 1 + src/libvirt-domain.c | 15 +- 9 files changed, 680 insertions(+), 10 deletions(-) diff --git a/src/ch/ch_conf.h b/src/ch/ch_conf.h index b08573476e..40d639eb2a 100644 --- a/src/ch/ch_conf.h +++ b/src/ch/ch_conf.h @@ -26,6 +26,7 @@ #include "ch_capabilities.h" #include "virebtables.h" #include "object_event.h" +#include "virportallocator.h" #define CH_DRIVER_NAME "CH" #define CH_CMD "cloud-hypervisor" @@ -90,6 +91,9 @@ struct _virCHDriver /* Immutable pointer, self-locking APIs */ virObjectEventState *domainEventState; + + /* Immutable pointer, immutable object */ + virPortAllocatorRange *migrationPorts; }; #define CH_SAVE_MAGIC "libvirt-xml\n \0 \r" diff --git a/src/ch/ch_domain.h b/src/ch/ch_domain.h index 69a657f6af..f9ad18b518 100644 --- a/src/ch/ch_domain.h +++ b/src/ch/ch_domain.h @@ -25,6 +25,7 @@ #include "virchrdev.h" #include "vircgroup.h" #include "virdomainjob.h" +#include "virthread.h" typedef struct _virCHDomainObjPrivate virCHDomainObjPrivate; @@ -32,6 +33,7 @@ struct _virCHDomainObjPrivate { virChrdevs *chrdevs; virCHDriver *driver; virCHMonitor *monitor; + virThread *migrationDstReceiveThr; char *machineName; virBitmap *autoCpuset; virBitmap *autoNodeset; diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c index 3bdcf66ebd..c53607251b 100644 --- a/src/ch/ch_driver.c +++ b/src/ch/ch_driver.c @@ -29,20 +29,25 @@ #include "ch_process.h" #include "domain_cgroup.h" #include "domain_event.h" +#include "domain_interface.h" #include "datatypes.h" #include "driver.h" +#include "viralloc.h" #include "viraccessapicheck.h" #include "virchrdev.h" #include "virerror.h" #include "virlog.h" #include "virobject.h" #include "virfile.h" +#include "virtime.h" #include "virtypedparam.h" #include "virutil.h" #include "viruuid.h" #include "virnuma.h" #include "virhostmem.h" +#include "util/virportallocator.h" + #define VIR_FROM_THIS VIR_FROM_CH VIR_LOG_INIT("ch.ch_driver"); @@ -1453,6 +1458,13 @@ chStateInitialize(bool privileged, if (!(ch_driver->domainEventState = virObjectEventStateNew())) goto cleanup; + /* Allocate bitmap for migration port reservation */ + if (!(ch_driver->migrationPorts = + virPortAllocatorRangeNew(_("migration"), + 49152, + 49216))) + goto cleanup; + if ((rv = chExtractVersion(ch_driver)) < 0) { if (rv == -2) ret = VIR_DRV_STATE_INIT_SKIPPED; @@ -1495,8 +1507,9 @@ chConnectSupportsFeature(virConnectPtr conn, _("Global feature %1$d should have already been handled"), feature); return -1; - case VIR_DRV_FEATURE_MIGRATION_V2: case VIR_DRV_FEATURE_MIGRATION_V3: + return 1; + case VIR_DRV_FEATURE_MIGRATION_V2: case VIR_DRV_FEATURE_MIGRATION_P2P: case VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION: case VIR_DRV_FEATURE_XML_MIGRATABLE: @@ -2338,6 +2351,348 @@ chDomainInterfaceAddresses(virDomain *dom, return ret; } +/******************************************************************* + * Migration Protocol Version 3 + *******************************************************************/ + +static char * +chDomainMigrateBegin3(virDomainPtr domain, + const char *xmlin, + char **cookieout, + int *cookieoutlen, + unsigned long flags, + const char *dname, + unsigned long resource G_GNUC_UNUSED) +{ + virDomainObj *vm; + char *xml = NULL; + virCHDriver *driver = domain->conn->privateData; + + VIR_WARN("chDomainMigrateBegin3 %p %s %p %p %lu %s", + domain, xmlin, cookieout, cookieoutlen, flags, dname); + if (!(vm = virCHDomainObjFromDomain(domain))) + return NULL; + + if (virDomainMigrateBegin3EnsureACL(domain->conn, vm->def) < 0) { + virDomainObjEndAPI(&vm); + return NULL; + } + + // Copied from libxl_migration.c:386 + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + xml = virDomainDefFormat(vm->def, driver->xmlopt, VIR_DOMAIN_DEF_FORMAT_SECURE); + + if (xml) { + VIR_WARN("chDomainMigrateBegin3 success. xml: %s", xml); + goto cleanup; + } + + return NULL; + cleanup: + virDomainObjEndAPI(&vm); + return xml; +} + +static +virDomainDef * +chMigrationAnyPrepareDef(virCHDriver *driver, + const char *dom_xml, + const char *dname) +{ + virDomainDef *def; + char *name = NULL; + + if (!dom_xml) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("no domain XML passed")); + return NULL; + } + + if (!(def = virDomainDefParseString(dom_xml, driver->xmlopt, + NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE))) + goto cleanup; + + if (dname) { + VIR_FREE(name); + def->name = g_strdup(dname); + } + + cleanup: + return def; +} + +typedef struct _chMigrationDstArgs { + unsigned int port; + virCHDomainObjPrivate *priv; +} chMigrationDstArgs; + +static void +chDoMigrateDstReceive(void *opaque) +{ + chMigrationDstArgs *args = opaque; + virCHDomainObjPrivate *priv = args->priv; + g_autofree char* rcv_uri = NULL; + + VIR_WARN("In thread. %u %p", args->port, args->priv); + if (!priv->monitor) { + VIR_ERROR(_("VMs monitor not initialized")); + } + + rcv_uri = g_strdup_printf("tcp:0.0.0.0:%d", args->port); + + if (virCHMonitorMigrationReceive(priv->monitor, rcv_uri) < 0) { + VIR_WARN("Migration receive failed."); + } + + VIR_WARN("Migration thread finished its duty"); +} + +/** + * Runs on the destination and prepares the empty cloud hypervisor process to + * receive the migration. + * Allocates some tcp port number to use as a migration channel. + */ +static int +chDomainMigratePrepare3(virConnectPtr dconn, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + const char *uri_in, + char **uri_out, + unsigned long flags, + const char *dname, + unsigned long resource G_GNUC_UNUSED, + const char *dom_xml) +{ + virCHDriver *driver = dconn->privateData; + virDomainObj *vm = NULL; + virCHDomainObjPrivate *priv = NULL; + chMigrationDstArgs *args = g_new0(chMigrationDstArgs, 1); + unsigned short port = 0; + g_autofree char *hostname = NULL; + const char *threadname = "mig-ch"; + g_autoptr(virDomainDef) def = NULL; + int rc = 0; + const char *incFormat = "%s:%s:%d"; // seems to differ for AF_INET6 + + VIR_WARN("chDomainMigratePrepare3 %p %s %u %p %p %s %p %lu %s %s", + dconn, cookiein, cookieinlen, cookieout, cookieoutlen, uri_in, uri_out, flags, dname, dom_xml); + + if (virDomainMigratePrepare3EnsureACL(dconn, def) < 0) + return -1; + + if (!(def = chMigrationAnyPrepareDef(driver, dom_xml, dname))) + return -1; + + VIR_WARN("Got DomainDef prepared successfully"); + + if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) { + rc = -1; + goto cleanup; + } + VIR_WARN("Got port %i", port); + + if ((hostname = virGetHostname()) == NULL) { + rc = -1; + goto cleanup; + } + + *uri_out = g_strdup_printf(incFormat, "tcp", hostname, port); + VIR_WARN("uri out %s", *uri_out); + + if (!(vm = virDomainObjListAdd(driver->domains, &def, + driver->xmlopt, + VIR_DOMAIN_OBJ_LIST_ADD_LIVE | + VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, + NULL))) + { + rc = -1; + VIR_WARN("Could not add Domain Obj to List"); + goto cleanup; + } + + if (virCHProcessInit(driver, vm) < 0) { + rc = -1; + VIR_WARN("Could not init process"); + goto cleanup; + } + + VIR_WARN("Try creating migration thread"); + priv = vm->privateData; + args->port = port; + args->priv = priv; + + // VM receiving is blocking which we cannot do here, because it would block + // the Libvirt migration protocol. + // Prepare a thread to receive the migration data + // VIR_FREE(priv->migrationDstReceiveThr); + priv->migrationDstReceiveThr = g_new0(virThread, 1); + if (virThreadCreateFull(priv->migrationDstReceiveThr, true, + chDoMigrateDstReceive, + threadname, + false, + args) < 0) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to create thread for receiving migration data")); + goto cleanup; + } + + VIR_WARN("Finished creating migration thread"); + + + VIR_WARN("Fin migrationPrepare"); + + + cleanup: + virDomainObjEndAPI(&vm); + return rc; +} + +static int +chDomainMigratePerform3(virDomainPtr dom, + const char *xmlin, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + const char *dconnuri, + const char *uri, + unsigned long flags, + const char *dname, + unsigned long resource) +{ + size_t i; + virDomainObj *vm; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(dom->conn->privateData); + virCHDomainObjPrivate *priv = NULL; + g_autofree char *id = g_strdup_printf("bla"); + VIR_WARN("chDomainMigratePerform3 %p %s %s %u %p %p %s %s %lu %s %lu", + dom, xmlin, cookiein, cookieinlen, cookieout, cookieoutlen, dconnuri, uri, flags, dname, resource); + + if (!(vm = virCHDomainObjFromDomain(dom))) + return -1; + + priv = vm->privateData; + + if (!priv->monitor) { + VIR_ERROR(_("VMs monitor not initialized")); + goto cleanup; + } + + if (virDomainMigratePerform3EnsureACL(dom->conn, vm->def) < 0) { + goto cleanup; + } + + // Net device id hardcoded currently + // We need to remove network devices from the VM before live migration. + // Libvirt pre-allocates network devices and passes only the FD to CHV. CHV + // is not able to migrate those devices. + // See following CHV issue: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7054 + /* de-activate netdevs after stopping vm */ + ignore_value(virDomainInterfaceStopDevices(vm->def)); + for (i = 0; i < vm->def->nnets; i++) { + virDomainInterfaceDeleteDevice(vm->def, vm->def->nets[i], false, cfg->stateDir); + id = g_strdup_printf("net_%lu", i); + if (virCHMonitorRemoveDevice(priv->monitor, id) < 0) { + VIR_WARN("Could not remove net device. Continue to migrate regardless."); + } + } + + if (virCHMonitorMigrationSend(priv->monitor, uri) < 0) { + VIR_WARN("Migration send failed."); + goto cleanup; + } + + cleanup: + virDomainObjEndAPI(&vm); + return 0; +} + +static virDomainPtr +chDomainMigrateFinish3(virConnectPtr dconn, + const char *dname, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + const char *dconnuri G_GNUC_UNUSED, + const char *uri G_GNUC_UNUSED, + unsigned long flags, + int cancelled) +{ + virCHDriver *driver = dconn->privateData; + virDomainObj *vm = NULL; + virDomainPtr dom = NULL; + + VIR_WARN("chDomainMigrateFinish3 %p %s %s %d %p %p %lu %d", + dconn, dname, cookiein, cookieinlen, cookieout, cookieoutlen, flags, cancelled); + + vm = virDomainObjListFindByName(driver->domains, dname); + if (!vm) { + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching name '%1$s'"), dname); + return NULL; + } + + if (virDomainMigrateFinish3EnsureACL(dconn, vm->def) < 0) { + virDomainObjEndAPI(&vm); + return NULL; + } + if (!(dom = virGetDomain(dconn, vm->def->name, vm->def->uuid, vm->def->id))) { + virDomainObjEndAPI(&vm); + VIR_WARN("virGetDomain failed."); + return NULL; + + } + if (virCHProcessUpdateInfo(vm) < 0) { + VIR_WARN("Could not update console info. Consider that non-fatal."); + } + + if (virCHProcessInitNetwork(driver, vm) < 0) { + VIR_WARN("Could not updatenetwork info. Consider that non-fatal."); + } + + virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_MIGRATED); + + virDomainObjEndAPI(&vm); + return dom; +} + +static int +chDomainMigrateConfirm3(virDomainPtr domain, + const char *cookiein, + int cookieinlen, + unsigned long flags, + int cancelled) +{ + virCHDriver *driver = domain->conn->privateData; + virObjectEvent *event = NULL; + virDomainObj *vm; + + VIR_WARN("chDomainMigrateConfirm3 %p %s %d %lu %d", + domain, cookiein, cookieinlen, flags, cancelled); + + if (!(vm = virCHDomainObjFromDomain(domain))) + return -1; + + if (virDomainMigrateConfirm3EnsureACL(domain->conn, vm->def) < 0) { + virDomainObjEndAPI(&vm); + return -1; + } + + // Code from chDestroyFlags + virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_MIGRATED); + virCHProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_DESTROYED); + virCHDomainRemoveInactive(driver, vm); + virDomainObjEndAPI(&vm); + + virObjectEventStateQueue(driver->domainEventState, event); + return 0; +} /* Function Tables */ static virHypervisorDriver chHypervisorDriver = { @@ -2400,6 +2755,11 @@ static virHypervisorDriver chHypervisorDriver = { .connectDomainEventRegisterAny = chConnectDomainEventRegisterAny, /* 10.10.0 */ .connectDomainEventDeregisterAny = chConnectDomainEventDeregisterAny, /* 10.10.0 */ .domainInterfaceAddresses = chDomainInterfaceAddresses, /* 11.0.0 */ + .domainMigrateBegin3 = chDomainMigrateBegin3, /* 11.4.0 */ + .domainMigratePrepare3 = chDomainMigratePrepare3, /* 11.4.0 */ + .domainMigratePerform3 = chDomainMigratePerform3, /* 11.4.0 */ + .domainMigrateFinish3 = chDomainMigrateFinish3, /* 11.4.0 */ + .domainMigrateConfirm3 = chDomainMigrateConfirm3, /* 11.4.0 */ }; static virConnectDriver chConnectDriver = { diff --git a/src/ch/ch_monitor.c b/src/ch/ch_monitor.c index 3d3b4cb87d..595fa30be0 100644 --- a/src/ch/ch_monitor.c +++ b/src/ch/ch_monitor.c @@ -548,6 +548,7 @@ virCHMonitorBuildVMJson(virCHDriver *driver, virDomainDef *vmdef, if (!(*jsonstr = virJSONValueToString(content, false))) return -1; + VIR_WARN("Build VM JSON: \n %s \n", *jsonstr); return 0; } @@ -684,6 +685,8 @@ virCHMonitorNew(virDomainObj *vm, virCHDriverConfig *cfg, int logfile) return NULL; } + VIR_WARN("Start emulator with cmd: %s", vm->def->emulator); + cmd = virCommandNew(vm->def->emulator); virCommandSetOutputFD(cmd, &logfile); virCommandSetErrorFD(cmd, &logfile); @@ -1163,6 +1166,159 @@ virCHMonitorSaveVM(virCHMonitor *mon, return ret; } +int virCHMonitorRemoveDevice(virCHMonitor *mon, + const char* device_id) +{ + g_autofree char *url = NULL; + int responseCode = 0; + int ret = -1; + g_autofree char *payload = NULL; + struct curl_slist *headers = NULL; + struct curl_data data = {0}; + + url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_REMOVE_DEVICE); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + if (virCHMonitorBuildKeyValueStringJson(&payload, "id", device_id) != 0) + return -1; + + VIR_WARN("Remove device id %s json %s", device_id, payload); + + 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 virCHMonitorMigrationSend(virCHMonitor *mon, + const char *dst_uri) +{ + g_autofree char *url = NULL; + int responseCode = 0; + int ret = -1; + g_autofree char *payload = NULL; + struct curl_slist *headers = NULL; + struct curl_data data = {0}; + + url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_SEND_MIGRATION); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + if (virCHMonitorBuildKeyValueStringJson(&payload, "destination_url", dst_uri) != 0) + return -1; + + VIR_WARN("Send VM to url %s json %s", dst_uri, payload); + + 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, _("Error sending VM: '%1$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 virCHMonitorMigrationReceive(virCHMonitor *mon, + const char *rcv_uri) +{ + g_autofree char *url = NULL; + int responseCode = 0; + int ret = -1; + g_autofree char *payload = NULL; + struct curl_slist *headers = NULL; + struct curl_data data = {0}; + + url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_RECEIVE_MIGRATION); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + if (virCHMonitorBuildKeyValueStringJson(&payload, "receiver_url", rcv_uri) != 0) + return -1; + + VIR_WARN("Receive VM from url %s json: %s", rcv_uri, payload); + + 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, _("Error receiving VM: '%1$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 virCHMonitorBuildRestoreJson(virDomainDef *vmdef, const char *from, diff --git a/src/ch/ch_monitor.h b/src/ch/ch_monitor.h index ffac9e938e..256ab0d8a1 100644 --- a/src/ch/ch_monitor.h +++ b/src/ch/ch_monitor.h @@ -40,6 +40,9 @@ #define URL_VM_INFO "vm.info" #define URL_VM_SAVE "vm.snapshot" #define URL_VM_RESTORE "vm.restore" +#define URL_VM_RECEIVE_MIGRATION "vm.receive-migration" +#define URL_VM_SEND_MIGRATION "vm.send-migration" +#define URL_VM_REMOVE_DEVICE "vm.remove-device" #define VIRCH_THREAD_NAME_LEN 16 @@ -128,6 +131,11 @@ int virCHMonitorSuspendVM(virCHMonitor *mon); int virCHMonitorResumeVM(virCHMonitor *mon); int virCHMonitorSaveVM(virCHMonitor *mon, const char *to); +int virCHMonitorMigrationSend(virCHMonitor *mon, + const char *dst_uri); +int virCHMonitorMigrationReceive(virCHMonitor *mon, + const char *rcv_uri); +int virCHMonitorRemoveDevice(virCHMonitor *mon, const char* device_id); int virCHMonitorGetInfo(virCHMonitor *mon, virJSONValue **info); size_t virCHMonitorGetThreadInfo(virCHMonitor *mon, bool refresh, diff --git a/src/ch/ch_process.c b/src/ch/ch_process.c index 95c808cb41..4d2bbc2b3d 100644 --- a/src/ch/ch_process.c +++ b/src/ch/ch_process.c @@ -135,6 +135,7 @@ int virCHProcessUpdateInfo(virDomainObj *vm) { g_autoptr(virJSONValue) info = NULL; + virCHDomainObjPrivate *priv = vm->privateData; if (virCHMonitorGetInfo(priv->monitor, &info) < 0) return -1; @@ -643,7 +644,7 @@ chProcessAddNetworkDevices(virCHDriver *driver, int **nicindexes, size_t *nnicindexes) { - size_t i; + size_t i, j; VIR_AUTOCLOSE mon_sockfd = -1; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) http_headers = VIR_BUFFER_INITIALIZER; @@ -654,8 +655,10 @@ chProcessAddNetworkDevices(virCHDriver *driver, return -1; } - if ((mon_sockfd = chMonitorSocketConnect(mon)) < 0) + if ((mon_sockfd = chMonitorSocketConnect(mon)) < 0) { + VIR_WARN("chProcessAddNetworkDevices failed"); return -1; + } virBufferAddLit(&http_headers, "PUT /api/v1/vm.add-net HTTP/1.1\r\n"); virBufferAddLit(&http_headers, "Host: localhost\r\n"); @@ -681,24 +684,33 @@ chProcessAddNetworkDevices(virCHDriver *driver, if (virCHDomainValidateActualNetDef(vmdef->nets[i]) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("net definition failed validation")); + VIR_WARN("virCHDomainValidateActualNetDef failed."); return -1; } tapfds = g_new0(int, tapfd_len); memset(tapfds, -1, (tapfd_len) * sizeof(int)); + VIR_WARN("net type: %u", vmdef->nets[i]->type); /* Connect Guest interfaces */ if (virCHConnetNetworkInterfaces(driver, vmdef, vmdef->nets[i], tapfds, - nicindexes, nnicindexes) < 0) + nicindexes, nnicindexes) < 0) { + VIR_WARN("chProcessAddNetworkDevices failed."); return -1; + } if (virCHMonitorBuildNetJson(vmdef->nets[i], i, &payload) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to build net json")); + VIR_WARN("virCHMonitorBuildNetJson failed."); return -1; } - VIR_DEBUG("payload sent with net-add request to CH = %s", payload); + VIR_WARN("payload sent with net-add request to CH = %s", payload); + + for (j = 0; j < tapfd_len; j++) { + VIR_WARN("tapfd %lu : %d", j, tapfds[j]); + } virBufferAsprintf(&buf, "%s", virBufferCurrentContent(&http_headers)); virBufferAsprintf(&buf, "Content-Length: %zu\r\n\r\n", strlen(payload)); @@ -892,6 +904,122 @@ virCHProcessPrepareDomain(virDomainObj *vm) return 0; } +int virCHProcessInitNetwork(virCHDriver *driver, + virDomainObj *vm) +{ + int ret = -1; + virCHDomainObjPrivate *priv = vm->privateData; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(priv->driver); + g_autofree int *nicindexes = NULL; + size_t nnicindexes = 0; + + if (chProcessAddNetworkDevices(driver, priv->monitor, vm->def, + &nicindexes, &nnicindexes) < 0) { + VIR_WARN("Failed chProcessAddNetworkDevices"); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed while adding guest interfaces")); + goto cleanup; + } + + /* Bring up netdevs before starting CPUs */ + if (virDomainInterfaceStartDevices(vm->def) < 0) { + VIR_WARN("Failed virDomainInterfaceStartDevices"); + return -1; + } + + return 0; + + cleanup: + + return ret; +} + +/** + * A variant of virCHProcessStart that does not start the vCPU threads and the + * VM. Sets up the CH process along most configuration. + * Is used to setup CH in order to receive a live migration afterwards. + */ +int +virCHProcessInit(virCHDriver *driver, + virDomainObj *vm) +{ + int ret = -1; + virCHDomainObjPrivate *priv = vm->privateData; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(priv->driver); + g_autofree int *nicindexes = NULL; + size_t nnicindexes = 0; + g_autoptr(domainLogContext) logCtxt = NULL; + int logfile = -1; + + if (virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("VM is already active")); + return -1; + } + + if (virCHProcessStartValidate(driver, vm) < 0) { + return -1; + } + + VIR_WARN("Creating domain log file for %s domain", vm->def->name); + if (!(logCtxt = domainLogContextNew(cfg->stdioLogD, cfg->logDir, + CH_DRIVER_NAME, + vm, driver->privileged, + vm->def->name))) { + virLastErrorPrefixMessage("%s", _("can't connect to virtlogd")); + return -1; + } + logfile = domainLogContextGetWriteFD(logCtxt); + + if (virCHProcessPrepareDomain(vm) < 0) { + return -1; + } + + if (virCHProcessPrepareHost(driver, vm) < 0) + return -1; + + if (!priv->monitor) { + /* And we can get the first monitor connection now too */ + if (!(priv->monitor = virCHProcessConnectMonitor(driver, vm, logfile))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create connection to CH socket")); + goto cleanup; + } + + if (virCHMonitorCreateVM(driver, priv->monitor) < 0) { + VIR_WARN("Failed virCHMonitorCreateVM"); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create guest VM")); + goto cleanup; + } + } + + vm->def->id = vm->pid; + priv->machineName = virCHDomainGetMachineName(vm); + + if (virDomainCgroupSetupCgroup("ch", vm, + nnicindexes, nicindexes, + &priv->cgroup, + cfg->cgroupControllers, + 0, /*maxThreadsPerProc*/ + priv->driver->privileged, + priv->machineName) < 0) + { + VIR_WARN("Failed virDomainCgroupSetupCgroup"); + goto cleanup; + } + + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_MIGRATION); + + return 0; + + cleanup: + if (ret) + virCHProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED); + + return ret; +} + /** * virCHProcessStart: * @driver: pointer to driver structure diff --git a/src/ch/ch_process.h b/src/ch/ch_process.h index 7a6995b7cf..3a5a2d17d7 100644 --- a/src/ch/ch_process.h +++ b/src/ch/ch_process.h @@ -23,6 +23,9 @@ #include "ch_conf.h" #include "internal.h" +int virCHProcessInit(virCHDriver *driver, + virDomainObj *vm); + int virCHProcessStart(virCHDriver *driver, virDomainObj *vm, virDomainRunningReason reason); @@ -38,3 +41,6 @@ int virCHProcessStartRestore(virCHDriver *driver, const char *from); int virCHProcessUpdateInfo(virDomainObj *vm); + +int virCHProcessInitNetwork(virCHDriver *driver, + virDomainObj *vm); diff --git a/src/hypervisor/domain_interface.c b/src/hypervisor/domain_interface.c index 5bc698d272..a13fcfb7d2 100644 --- a/src/hypervisor/domain_interface.c +++ b/src/hypervisor/domain_interface.c @@ -82,6 +82,7 @@ virDomainInterfaceEthernetConnect(virDomainDef *def, bool template_ifname = false; const char *tunpath = "/dev/net/tun"; const char *auditdev = tunpath; + VIR_WARN("virDomainInterfaceEthernetConnect %s", net->ifname); if (net->backend.tap) { tunpath = net->backend.tap; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 93e8f5b853..867876b23f 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -3307,6 +3307,11 @@ virDomainMigrateVersion3Full(virDomainPtr domain, "params=%p, nparams=%d, useParams=%d, flags=0x%x", dconn, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), bandwidth, params, nparams, useParams, flags); + VIR_WARN( + "dconn=%p, xmlin=%s, dname=%s, uri=%s, bandwidth=%llu, " + "params=%p, nparams=%d, useParams=%d, flags=0x%x", + dconn, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), + bandwidth, params, nparams, useParams, flags); VIR_TYPED_PARAMS_DEBUG(params, nparams); virCheckNonEmptyOptStringArgReturn(dname, NULL); @@ -3337,7 +3342,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, if (ret) protection = VIR_MIGRATE_CHANGE_PROTECTION; - VIR_DEBUG("Begin3 %p", domain->conn); + VIR_WARN("Begin3 %p", domain->conn); if (useParams) { dom_xml = domain->conn->driver->domainMigrateBegin3Params (domain, params, nparams, &cookieout, &cookieoutlen, @@ -3366,7 +3371,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, destflags = flags & ~(VIR_MIGRATE_ABORT_ON_ERROR | VIR_MIGRATE_AUTO_CONVERGE); - VIR_DEBUG("Prepare3 %p flags=0x%x", dconn, destflags); + VIR_WARN("Prepare3 %p flags=0x%x", dconn, destflags); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; cookieoutlen = 0; @@ -3427,7 +3432,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, * running, but in paused state until the destination can * confirm migration completion. */ - VIR_DEBUG("Perform3 %p uri=%s", domain->conn, uri); + VIR_WARN("Perform3 %p uri=%s", domain->conn, uri); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; @@ -3465,7 +3470,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, * send all migration data. Returns NULL for ddomain if * the dest was unable to complete migration. */ - VIR_DEBUG("Finish3 %p ret=%d", dconn, ret); + VIR_WARN("Finish3 %p ret=%d", dconn, ret); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; @@ -3540,7 +3545,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, * cancelled there. */ if (notify_source) { - VIR_DEBUG("Confirm3 %p ret=%d domain=%p", domain->conn, ret, domain); + VIR_WARN("Confirm3 %p ret=%d domain=%p", domain->conn, ret, domain); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; -- 2.49.0

On 5/20/25 13:12, Stefan Kober wrote:
--- src/ch/ch_conf.h | 4 + src/ch/ch_domain.h | 2 + src/ch/ch_driver.c | 362 +++++++++++++++++++++++++++++- src/ch/ch_monitor.c | 156 +++++++++++++ src/ch/ch_monitor.h | 8 + src/ch/ch_process.c | 136 ++++++++++- src/ch/ch_process.h | 6 + src/hypervisor/domain_interface.c | 1 + src/libvirt-domain.c | 15 +- 9 files changed, 680 insertions(+), 10 deletions(-)
There's quite a lot happening in just this one patch. Nevertheless, couple of overall comments: 1) Do NOT change VIR_DEBUG() to VIR_WARN(). It cloggs logs for no additional value. If you want to see debug logs there is plenty of ways to do that: https://libvirt.org/kbase/debuglogs.html 2) In general we tend to use the following pattern for functions: int f(..) { int ret = -1; if (cond) goto cleanup; if (otherCond) goto cleanup; ret = 0; cleanup: cleanupCode(); return ret; } See comments below.
diff --git a/src/ch/ch_conf.h b/src/ch/ch_conf.h index b08573476e..40d639eb2a 100644 --- a/src/ch/ch_conf.h +++ b/src/ch/ch_conf.h @@ -26,6 +26,7 @@ #include "ch_capabilities.h" #include "virebtables.h" #include "object_event.h" +#include "virportallocator.h"
#define CH_DRIVER_NAME "CH" #define CH_CMD "cloud-hypervisor" @@ -90,6 +91,9 @@ struct _virCHDriver
/* Immutable pointer, self-locking APIs */ virObjectEventState *domainEventState; + + /* Immutable pointer, immutable object */ + virPortAllocatorRange *migrationPorts; };
#define CH_SAVE_MAGIC "libvirt-xml\n \0 \r" diff --git a/src/ch/ch_domain.h b/src/ch/ch_domain.h index 69a657f6af..f9ad18b518 100644 --- a/src/ch/ch_domain.h +++ b/src/ch/ch_domain.h @@ -25,6 +25,7 @@ #include "virchrdev.h" #include "vircgroup.h" #include "virdomainjob.h" +#include "virthread.h"
typedef struct _virCHDomainObjPrivate virCHDomainObjPrivate; @@ -32,6 +33,7 @@ struct _virCHDomainObjPrivate { virChrdevs *chrdevs; virCHDriver *driver; virCHMonitor *monitor; + virThread *migrationDstReceiveThr; char *machineName; virBitmap *autoCpuset; virBitmap *autoNodeset; diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c index 3bdcf66ebd..c53607251b 100644 --- a/src/ch/ch_driver.c +++ b/src/ch/ch_driver.c @@ -29,20 +29,25 @@ #include "ch_process.h" #include "domain_cgroup.h" #include "domain_event.h" +#include "domain_interface.h" #include "datatypes.h" #include "driver.h" +#include "viralloc.h" #include "viraccessapicheck.h" #include "virchrdev.h" #include "virerror.h" #include "virlog.h" #include "virobject.h" #include "virfile.h" +#include "virtime.h" #include "virtypedparam.h" #include "virutil.h" #include "viruuid.h" #include "virnuma.h" #include "virhostmem.h"
+#include "util/virportallocator.h" + #define VIR_FROM_THIS VIR_FROM_CH
VIR_LOG_INIT("ch.ch_driver"); @@ -1453,6 +1458,13 @@ chStateInitialize(bool privileged, if (!(ch_driver->domainEventState = virObjectEventStateNew())) goto cleanup;
+ /* Allocate bitmap for migration port reservation */ + if (!(ch_driver->migrationPorts = + virPortAllocatorRangeNew(_("migration"), + 49152, + 49216)))
These magic constants should be declared as macros somewhere in ch_conf.h.
+ goto cleanup; + if ((rv = chExtractVersion(ch_driver)) < 0) { if (rv == -2) ret = VIR_DRV_STATE_INIT_SKIPPED; @@ -1495,8 +1507,9 @@ chConnectSupportsFeature(virConnectPtr conn, _("Global feature %1$d should have already been handled"), feature); return -1; - case VIR_DRV_FEATURE_MIGRATION_V2: case VIR_DRV_FEATURE_MIGRATION_V3: + return 1; + case VIR_DRV_FEATURE_MIGRATION_V2: case VIR_DRV_FEATURE_MIGRATION_P2P: case VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION: case VIR_DRV_FEATURE_XML_MIGRATABLE: @@ -2338,6 +2351,348 @@ chDomainInterfaceAddresses(virDomain *dom, return ret; }
+/******************************************************************* + * Migration Protocol Version 3 + *******************************************************************/ + +static char * +chDomainMigrateBegin3(virDomainPtr domain, + const char *xmlin, + char **cookieout, + int *cookieoutlen, + unsigned long flags, + const char *dname, + unsigned long resource G_GNUC_UNUSED) +{ + virDomainObj *vm; + char *xml = NULL; + virCHDriver *driver = domain->conn->privateData; + + VIR_WARN("chDomainMigrateBegin3 %p %s %p %p %lu %s", + domain, xmlin, cookieout, cookieoutlen, flags, dname);
I don't see any plausible reason for VIR_WARN() here. We do not need to warn user about arguments. VIR_DEBUG() would be a better fit, but not really - the only caller of this function (virDomainMigrateBegin3()) already did that.
+ if (!(vm = virCHDomainObjFromDomain(domain))) + return NULL; + + if (virDomainMigrateBegin3EnsureACL(domain->conn, vm->def) < 0) { + virDomainObjEndAPI(&vm); + return NULL; + } + + // Copied from libxl_migration.c:386
We use C89 style of comments.
+ if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + xml = virDomainDefFormat(vm->def, driver->xmlopt, VIR_DOMAIN_DEF_FORMAT_SECURE); + + if (xml) { + VIR_WARN("chDomainMigrateBegin3 success. xml: %s", xml); + goto cleanup; + } + + return NULL; + cleanup: + virDomainObjEndAPI(&vm); + return xml; +} + +static +virDomainDef * +chMigrationAnyPrepareDef(virCHDriver *driver, + const char *dom_xml, + const char *dname) +{ + virDomainDef *def; + char *name = NULL; + + if (!dom_xml) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("no domain XML passed")); + return NULL; + } + + if (!(def = virDomainDefParseString(dom_xml, driver->xmlopt, + NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE))) + goto cleanup; + + if (dname) { + VIR_FREE(name);
I bet what you've meant to say here is: VIR_FREE(def->name); which renders 'name' variable unused.
+ def->name = g_strdup(dname); + } + + cleanup: + return def; +} + +typedef struct _chMigrationDstArgs { + unsigned int port; + virCHDomainObjPrivate *priv; +} chMigrationDstArgs; + +static void +chDoMigrateDstReceive(void *opaque) +{ + chMigrationDstArgs *args = opaque; + virCHDomainObjPrivate *priv = args->priv; + g_autofree char* rcv_uri = NULL; + + VIR_WARN("In thread. %u %p", args->port, args->priv); + if (!priv->monitor) { + VIR_ERROR(_("VMs monitor not initialized")); + } + + rcv_uri = g_strdup_printf("tcp:0.0.0.0:%d", args->port); + + if (virCHMonitorMigrationReceive(priv->monitor, rcv_uri) < 0) { + VIR_WARN("Migration receive failed."); + } + + VIR_WARN("Migration thread finished its duty"); +} + +/** + * Runs on the destination and prepares the empty cloud hypervisor process to + * receive the migration. + * Allocates some tcp port number to use as a migration channel. + */ +static int +chDomainMigratePrepare3(virConnectPtr dconn, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + const char *uri_in, + char **uri_out, + unsigned long flags, + const char *dname, + unsigned long resource G_GNUC_UNUSED, + const char *dom_xml) +{ + virCHDriver *driver = dconn->privateData; + virDomainObj *vm = NULL; + virCHDomainObjPrivate *priv = NULL; + chMigrationDstArgs *args = g_new0(chMigrationDstArgs, 1);
This is allocated but never freed.
+ unsigned short port = 0; + g_autofree char *hostname = NULL; + const char *threadname = "mig-ch"; + g_autoptr(virDomainDef) def = NULL; + int rc = 0; + const char *incFormat = "%s:%s:%d"; // seems to differ for AF_INET6 + + VIR_WARN("chDomainMigratePrepare3 %p %s %u %p %p %s %p %lu %s %s", + dconn, cookiein, cookieinlen, cookieout, cookieoutlen, uri_in, uri_out, flags, dname, dom_xml); + + if (virDomainMigratePrepare3EnsureACL(dconn, def) < 0) + return -1; + + if (!(def = chMigrationAnyPrepareDef(driver, dom_xml, dname))) + return -1; + + VIR_WARN("Got DomainDef prepared successfully"); + + if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) { + rc = -1; + goto cleanup; + } + VIR_WARN("Got port %i", port); + + if ((hostname = virGetHostname()) == NULL) { + rc = -1; + goto cleanup; + } + + *uri_out = g_strdup_printf(incFormat, "tcp", hostname, port); + VIR_WARN("uri out %s", *uri_out); + + if (!(vm = virDomainObjListAdd(driver->domains, &def, + driver->xmlopt, + VIR_DOMAIN_OBJ_LIST_ADD_LIVE | + VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, + NULL))) + { + rc = -1; + VIR_WARN("Could not add Domain Obj to List"); + goto cleanup; + } + + if (virCHProcessInit(driver, vm) < 0) { + rc = -1; + VIR_WARN("Could not init process"); + goto cleanup; + } + + VIR_WARN("Try creating migration thread"); + priv = vm->privateData; + args->port = port; + args->priv = priv; + + // VM receiving is blocking which we cannot do here, because it would block + // the Libvirt migration protocol. + // Prepare a thread to receive the migration data + // VIR_FREE(priv->migrationDstReceiveThr);
This VIR_FREE() must be called in virCHDomainObjPrivateFree() at least.
+ priv->migrationDstReceiveThr = g_new0(virThread, 1); + if (virThreadCreateFull(priv->migrationDstReceiveThr, true, + chDoMigrateDstReceive, + threadname, + false, + args) < 0) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to create thread for receiving migration data")); + goto cleanup; + } + + VIR_WARN("Finished creating migration thread"); + + + VIR_WARN("Fin migrationPrepare"); + + + cleanup: + virDomainObjEndAPI(&vm); + return rc; +}
This is the place where I acquired so much of changed lines that I stopped my review. Please see the diff at EOL which I think should be squashed in. It covers also other fixes that I haven't raised explicitly here.
+ +static int +chDomainMigratePerform3(virDomainPtr dom, + const char *xmlin, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + const char *dconnuri, + const char *uri, + unsigned long flags, + const char *dname, + unsigned long resource) +{ + size_t i; + virDomainObj *vm; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(dom->conn->privateData); + virCHDomainObjPrivate *priv = NULL; + g_autofree char *id = g_strdup_printf("bla"); + VIR_WARN("chDomainMigratePerform3 %p %s %s %u %p %p %s %s %lu %s %lu", + dom, xmlin, cookiein, cookieinlen, cookieout, cookieoutlen, dconnuri, uri, flags, dname, resource); + + if (!(vm = virCHDomainObjFromDomain(dom))) + return -1; + + priv = vm->privateData; + + if (!priv->monitor) { + VIR_ERROR(_("VMs monitor not initialized")); + goto cleanup; + } + + if (virDomainMigratePerform3EnsureACL(dom->conn, vm->def) < 0) { + goto cleanup; + } + + // Net device id hardcoded currently + // We need to remove network devices from the VM before live migration. + // Libvirt pre-allocates network devices and passes only the FD to CHV. CHV + // is not able to migrate those devices. + // See following CHV issue: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7054 + /* de-activate netdevs after stopping vm */ + ignore_value(virDomainInterfaceStopDevices(vm->def)); + for (i = 0; i < vm->def->nnets; i++) { + virDomainInterfaceDeleteDevice(vm->def, vm->def->nets[i], false, cfg->stateDir); + id = g_strdup_printf("net_%lu", i); + if (virCHMonitorRemoveDevice(priv->monitor, id) < 0) { + VIR_WARN("Could not remove net device. Continue to migrate regardless."); + } + } + + if (virCHMonitorMigrationSend(priv->monitor, uri) < 0) { + VIR_WARN("Migration send failed."); + goto cleanup; + } + + cleanup: + virDomainObjEndAPI(&vm); + return 0; +} + +static virDomainPtr +chDomainMigrateFinish3(virConnectPtr dconn, + const char *dname, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + const char *dconnuri G_GNUC_UNUSED, + const char *uri G_GNUC_UNUSED, + unsigned long flags, + int cancelled) +{ + virCHDriver *driver = dconn->privateData; + virDomainObj *vm = NULL; + virDomainPtr dom = NULL; + + VIR_WARN("chDomainMigrateFinish3 %p %s %s %d %p %p %lu %d", + dconn, dname, cookiein, cookieinlen, cookieout, cookieoutlen, flags, cancelled); + + vm = virDomainObjListFindByName(driver->domains, dname); + if (!vm) { + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching name '%1$s'"), dname); + return NULL; + } + + if (virDomainMigrateFinish3EnsureACL(dconn, vm->def) < 0) { + virDomainObjEndAPI(&vm); + return NULL; + } + if (!(dom = virGetDomain(dconn, vm->def->name, vm->def->uuid, vm->def->id))) { + virDomainObjEndAPI(&vm); + VIR_WARN("virGetDomain failed."); + return NULL; + + } + if (virCHProcessUpdateInfo(vm) < 0) { + VIR_WARN("Could not update console info. Consider that non-fatal."); + } + + if (virCHProcessInitNetwork(driver, vm) < 0) { + VIR_WARN("Could not updatenetwork info. Consider that non-fatal."); + } + + virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_MIGRATED); + + virDomainObjEndAPI(&vm); + return dom; +} + +static int +chDomainMigrateConfirm3(virDomainPtr domain, + const char *cookiein, + int cookieinlen, + unsigned long flags, + int cancelled) +{ + virCHDriver *driver = domain->conn->privateData; + virObjectEvent *event = NULL; + virDomainObj *vm; + + VIR_WARN("chDomainMigrateConfirm3 %p %s %d %lu %d", + domain, cookiein, cookieinlen, flags, cancelled); + + if (!(vm = virCHDomainObjFromDomain(domain))) + return -1; + + if (virDomainMigrateConfirm3EnsureACL(domain->conn, vm->def) < 0) { + virDomainObjEndAPI(&vm); + return -1; + } + + // Code from chDestroyFlags + virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_MIGRATED); + virCHProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_DESTROYED); + virCHDomainRemoveInactive(driver, vm); + virDomainObjEndAPI(&vm); + + virObjectEventStateQueue(driver->domainEventState, event); + return 0; +}
/* Function Tables */ static virHypervisorDriver chHypervisorDriver = { @@ -2400,6 +2755,11 @@ static virHypervisorDriver chHypervisorDriver = { .connectDomainEventRegisterAny = chConnectDomainEventRegisterAny, /* 10.10.0 */ .connectDomainEventDeregisterAny = chConnectDomainEventDeregisterAny, /* 10.10.0 */ .domainInterfaceAddresses = chDomainInterfaceAddresses, /* 11.0.0 */ + .domainMigrateBegin3 = chDomainMigrateBegin3, /* 11.4.0 */ + .domainMigratePrepare3 = chDomainMigratePrepare3, /* 11.4.0 */ + .domainMigratePerform3 = chDomainMigratePerform3, /* 11.4.0 */ + .domainMigrateFinish3 = chDomainMigrateFinish3, /* 11.4.0 */ + .domainMigrateConfirm3 = chDomainMigrateConfirm3, /* 11.4.0 */ };
static virConnectDriver chConnectDriver = { diff --git a/src/ch/ch_monitor.c b/src/ch/ch_monitor.c index 3d3b4cb87d..595fa30be0 100644 --- a/src/ch/ch_monitor.c +++ b/src/ch/ch_monitor.c @@ -548,6 +548,7 @@ virCHMonitorBuildVMJson(virCHDriver *driver, virDomainDef *vmdef, if (!(*jsonstr = virJSONValueToString(content, false))) return -1;
+ VIR_WARN("Build VM JSON: \n %s \n", *jsonstr); return 0; }
@@ -684,6 +685,8 @@ virCHMonitorNew(virDomainObj *vm, virCHDriverConfig *cfg, int logfile) return NULL; }
+ VIR_WARN("Start emulator with cmd: %s", vm->def->emulator); + cmd = virCommandNew(vm->def->emulator); virCommandSetOutputFD(cmd, &logfile); virCommandSetErrorFD(cmd, &logfile); @@ -1163,6 +1166,159 @@ virCHMonitorSaveVM(virCHMonitor *mon, return ret; }
+int virCHMonitorRemoveDevice(virCHMonitor *mon, + const char* device_id) +{ + g_autofree char *url = NULL; + int responseCode = 0; + int ret = -1; + g_autofree char *payload = NULL; + struct curl_slist *headers = NULL; + struct curl_data data = {0}; + + url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_REMOVE_DEVICE); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + if (virCHMonitorBuildKeyValueStringJson(&payload, "id", device_id) != 0) + return -1; + + VIR_WARN("Remove device id %s json %s", device_id, payload); + + 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 virCHMonitorMigrationSend(virCHMonitor *mon, + const char *dst_uri) +{ + g_autofree char *url = NULL; + int responseCode = 0; + int ret = -1; + g_autofree char *payload = NULL; + struct curl_slist *headers = NULL; + struct curl_data data = {0}; + + url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_SEND_MIGRATION); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + if (virCHMonitorBuildKeyValueStringJson(&payload, "destination_url", dst_uri) != 0) + return -1; + + VIR_WARN("Send VM to url %s json %s", dst_uri, payload); + + 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, _("Error sending VM: '%1$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 virCHMonitorMigrationReceive(virCHMonitor *mon, + const char *rcv_uri) +{ + g_autofree char *url = NULL; + int responseCode = 0; + int ret = -1; + g_autofree char *payload = NULL; + struct curl_slist *headers = NULL; + struct curl_data data = {0}; + + url = g_strdup_printf("%s/%s", URL_ROOT, URL_VM_RECEIVE_MIGRATION); + + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + if (virCHMonitorBuildKeyValueStringJson(&payload, "receiver_url", rcv_uri) != 0) + return -1; + + VIR_WARN("Receive VM from url %s json: %s", rcv_uri, payload); + + 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, _("Error receiving VM: '%1$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 virCHMonitorBuildRestoreJson(virDomainDef *vmdef, const char *from, diff --git a/src/ch/ch_monitor.h b/src/ch/ch_monitor.h index ffac9e938e..256ab0d8a1 100644 --- a/src/ch/ch_monitor.h +++ b/src/ch/ch_monitor.h @@ -40,6 +40,9 @@ #define URL_VM_INFO "vm.info" #define URL_VM_SAVE "vm.snapshot" #define URL_VM_RESTORE "vm.restore" +#define URL_VM_RECEIVE_MIGRATION "vm.receive-migration" +#define URL_VM_SEND_MIGRATION "vm.send-migration" +#define URL_VM_REMOVE_DEVICE "vm.remove-device"
#define VIRCH_THREAD_NAME_LEN 16
@@ -128,6 +131,11 @@ int virCHMonitorSuspendVM(virCHMonitor *mon); int virCHMonitorResumeVM(virCHMonitor *mon); int virCHMonitorSaveVM(virCHMonitor *mon, const char *to); +int virCHMonitorMigrationSend(virCHMonitor *mon, + const char *dst_uri); +int virCHMonitorMigrationReceive(virCHMonitor *mon, + const char *rcv_uri); +int virCHMonitorRemoveDevice(virCHMonitor *mon, const char* device_id); int virCHMonitorGetInfo(virCHMonitor *mon, virJSONValue **info);
size_t virCHMonitorGetThreadInfo(virCHMonitor *mon, bool refresh, diff --git a/src/ch/ch_process.c b/src/ch/ch_process.c index 95c808cb41..4d2bbc2b3d 100644 --- a/src/ch/ch_process.c +++ b/src/ch/ch_process.c @@ -135,6 +135,7 @@ int virCHProcessUpdateInfo(virDomainObj *vm) { g_autoptr(virJSONValue) info = NULL; + virCHDomainObjPrivate *priv = vm->privateData; if (virCHMonitorGetInfo(priv->monitor, &info) < 0) return -1; @@ -643,7 +644,7 @@ chProcessAddNetworkDevices(virCHDriver *driver, int **nicindexes, size_t *nnicindexes) { - size_t i; + size_t i, j; VIR_AUTOCLOSE mon_sockfd = -1; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) http_headers = VIR_BUFFER_INITIALIZER; @@ -654,8 +655,10 @@ chProcessAddNetworkDevices(virCHDriver *driver, return -1; }
- if ((mon_sockfd = chMonitorSocketConnect(mon)) < 0) + if ((mon_sockfd = chMonitorSocketConnect(mon)) < 0) { + VIR_WARN("chProcessAddNetworkDevices failed"); return -1; + }
virBufferAddLit(&http_headers, "PUT /api/v1/vm.add-net HTTP/1.1\r\n"); virBufferAddLit(&http_headers, "Host: localhost\r\n"); @@ -681,24 +684,33 @@ chProcessAddNetworkDevices(virCHDriver *driver, if (virCHDomainValidateActualNetDef(vmdef->nets[i]) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("net definition failed validation")); + VIR_WARN("virCHDomainValidateActualNetDef failed."); return -1; }
tapfds = g_new0(int, tapfd_len); memset(tapfds, -1, (tapfd_len) * sizeof(int));
+ VIR_WARN("net type: %u", vmdef->nets[i]->type); /* Connect Guest interfaces */ if (virCHConnetNetworkInterfaces(driver, vmdef, vmdef->nets[i], tapfds, - nicindexes, nnicindexes) < 0) + nicindexes, nnicindexes) < 0) { + VIR_WARN("chProcessAddNetworkDevices failed."); return -1; + }
if (virCHMonitorBuildNetJson(vmdef->nets[i], i, &payload) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to build net json")); + VIR_WARN("virCHMonitorBuildNetJson failed."); return -1; }
- VIR_DEBUG("payload sent with net-add request to CH = %s", payload); + VIR_WARN("payload sent with net-add request to CH = %s", payload); + + for (j = 0; j < tapfd_len; j++) { + VIR_WARN("tapfd %lu : %d", j, tapfds[j]); + }
virBufferAsprintf(&buf, "%s", virBufferCurrentContent(&http_headers)); virBufferAsprintf(&buf, "Content-Length: %zu\r\n\r\n", strlen(payload)); @@ -892,6 +904,122 @@ virCHProcessPrepareDomain(virDomainObj *vm) return 0; }
+int virCHProcessInitNetwork(virCHDriver *driver, + virDomainObj *vm) +{ + int ret = -1; + virCHDomainObjPrivate *priv = vm->privateData; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(priv->driver); + g_autofree int *nicindexes = NULL; + size_t nnicindexes = 0; + + if (chProcessAddNetworkDevices(driver, priv->monitor, vm->def, + &nicindexes, &nnicindexes) < 0) { + VIR_WARN("Failed chProcessAddNetworkDevices"); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed while adding guest interfaces")); + goto cleanup; + } + + /* Bring up netdevs before starting CPUs */ + if (virDomainInterfaceStartDevices(vm->def) < 0) { + VIR_WARN("Failed virDomainInterfaceStartDevices"); + return -1; + } + + return 0; + + cleanup: + + return ret; +} + +/** + * A variant of virCHProcessStart that does not start the vCPU threads and the + * VM. Sets up the CH process along most configuration. + * Is used to setup CH in order to receive a live migration afterwards. + */ +int +virCHProcessInit(virCHDriver *driver, + virDomainObj *vm) +{
This function copied parts of virCHProcessStart. If there's a thing that needs to be changed then we have two places to change. I think virCHProcessStart() should call virCHProcessInit(). You can invent an additional argument to do hacks around incoming migration ().
+ int ret = -1; + virCHDomainObjPrivate *priv = vm->privateData; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(priv->driver); + g_autofree int *nicindexes = NULL; + size_t nnicindexes = 0; + g_autoptr(domainLogContext) logCtxt = NULL; + int logfile = -1; + + if (virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("VM is already active")); + return -1; + } + + if (virCHProcessStartValidate(driver, vm) < 0) { + return -1; + } + + VIR_WARN("Creating domain log file for %s domain", vm->def->name); + if (!(logCtxt = domainLogContextNew(cfg->stdioLogD, cfg->logDir, + CH_DRIVER_NAME, + vm, driver->privileged, + vm->def->name))) { + virLastErrorPrefixMessage("%s", _("can't connect to virtlogd")); + return -1; + } + logfile = domainLogContextGetWriteFD(logCtxt); + + if (virCHProcessPrepareDomain(vm) < 0) { + return -1; + } + + if (virCHProcessPrepareHost(driver, vm) < 0) + return -1; + + if (!priv->monitor) { + /* And we can get the first monitor connection now too */ + if (!(priv->monitor = virCHProcessConnectMonitor(driver, vm, logfile))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create connection to CH socket")); + goto cleanup; + } + + if (virCHMonitorCreateVM(driver, priv->monitor) < 0) { + VIR_WARN("Failed virCHMonitorCreateVM"); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create guest VM")); + goto cleanup; + } + } + + vm->def->id = vm->pid; + priv->machineName = virCHDomainGetMachineName(vm); + + if (virDomainCgroupSetupCgroup("ch", vm, + nnicindexes, nicindexes, + &priv->cgroup, + cfg->cgroupControllers, + 0, /*maxThreadsPerProc*/ + priv->driver->privileged, + priv->machineName) < 0) + { + VIR_WARN("Failed virDomainCgroupSetupCgroup"); + goto cleanup; + } + + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_MIGRATION); + + return 0; + + cleanup: + if (ret) + virCHProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED); + + return ret; +} + /** * virCHProcessStart: * @driver: pointer to driver structure diff --git a/src/ch/ch_process.h b/src/ch/ch_process.h index 7a6995b7cf..3a5a2d17d7 100644 --- a/src/ch/ch_process.h +++ b/src/ch/ch_process.h @@ -23,6 +23,9 @@ #include "ch_conf.h" #include "internal.h"
+int virCHProcessInit(virCHDriver *driver, + virDomainObj *vm); + int virCHProcessStart(virCHDriver *driver, virDomainObj *vm, virDomainRunningReason reason); @@ -38,3 +41,6 @@ int virCHProcessStartRestore(virCHDriver *driver, const char *from);
int virCHProcessUpdateInfo(virDomainObj *vm); + +int virCHProcessInitNetwork(virCHDriver *driver, + virDomainObj *vm); diff --git a/src/hypervisor/domain_interface.c b/src/hypervisor/domain_interface.c index 5bc698d272..a13fcfb7d2 100644 --- a/src/hypervisor/domain_interface.c +++ b/src/hypervisor/domain_interface.c @@ -82,6 +82,7 @@ virDomainInterfaceEthernetConnect(virDomainDef *def, bool template_ifname = false; const char *tunpath = "/dev/net/tun"; const char *auditdev = tunpath; + VIR_WARN("virDomainInterfaceEthernetConnect %s", net->ifname);
if (net->backend.tap) { tunpath = net->backend.tap; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 93e8f5b853..867876b23f 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -3307,6 +3307,11 @@ virDomainMigrateVersion3Full(virDomainPtr domain, "params=%p, nparams=%d, useParams=%d, flags=0x%x", dconn, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), bandwidth, params, nparams, useParams, flags); + VIR_WARN( + "dconn=%p, xmlin=%s, dname=%s, uri=%s, bandwidth=%llu, " + "params=%p, nparams=%d, useParams=%d, flags=0x%x", + dconn, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), + bandwidth, params, nparams, useParams, flags); VIR_TYPED_PARAMS_DEBUG(params, nparams);
virCheckNonEmptyOptStringArgReturn(dname, NULL); @@ -3337,7 +3342,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, if (ret) protection = VIR_MIGRATE_CHANGE_PROTECTION;
- VIR_DEBUG("Begin3 %p", domain->conn); + VIR_WARN("Begin3 %p", domain->conn); if (useParams) { dom_xml = domain->conn->driver->domainMigrateBegin3Params (domain, params, nparams, &cookieout, &cookieoutlen, @@ -3366,7 +3371,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, destflags = flags & ~(VIR_MIGRATE_ABORT_ON_ERROR | VIR_MIGRATE_AUTO_CONVERGE);
- VIR_DEBUG("Prepare3 %p flags=0x%x", dconn, destflags); + VIR_WARN("Prepare3 %p flags=0x%x", dconn, destflags); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; cookieoutlen = 0; @@ -3427,7 +3432,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, * running, but in paused state until the destination can * confirm migration completion. */ - VIR_DEBUG("Perform3 %p uri=%s", domain->conn, uri); + VIR_WARN("Perform3 %p uri=%s", domain->conn, uri); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; @@ -3465,7 +3470,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, * send all migration data. Returns NULL for ddomain if * the dest was unable to complete migration. */ - VIR_DEBUG("Finish3 %p ret=%d", dconn, ret); + VIR_WARN("Finish3 %p ret=%d", dconn, ret); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; @@ -3540,7 +3545,7 @@ virDomainMigrateVersion3Full(virDomainPtr domain, * cancelled there. */ if (notify_source) { - VIR_DEBUG("Confirm3 %p ret=%d domain=%p", domain->conn, ret, domain); + VIR_WARN("Confirm3 %p ret=%d domain=%p", domain->conn, ret, domain); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen;
diff --git c/src/ch/ch_conf.h w/src/ch/ch_conf.h index 40d639eb2a..042507fcc6 100644 --- c/src/ch/ch_conf.h +++ w/src/ch/ch_conf.h @@ -31,6 +31,9 @@ #define CH_DRIVER_NAME "CH" #define CH_CMD "cloud-hypervisor" +#define CH_MIGRATION_PORT_MIN 49152 +#define CH_MIGRATION_PORT_MAX 49216 + typedef struct _virCHDriver virCHDriver; typedef struct _virCHDriverConfig virCHDriverConfig; diff --git c/src/ch/ch_domain.c w/src/ch/ch_domain.c index 7231fdc49f..c15410e474 100644 --- c/src/ch/ch_domain.c +++ w/src/ch/ch_domain.c @@ -68,6 +68,11 @@ virCHDomainObjPrivateFree(void *data) virBitmapFree(priv->autoCpuset); virBitmapFree(priv->autoNodeset); virCgroupFree(priv->cgroup); + if (priv->migrationDstReceiveThr) { + virThreadCancel(priv->migrationDstReceiveThr); + virThreadJoin(priv->migrationDstReceiveThr); + g_free(priv->migrationDstReceiveThr); + } g_free(priv->pidfile); g_free(priv); } diff --git c/src/ch/ch_driver.c w/src/ch/ch_driver.c index c53607251b..a0df931c80 100644 --- c/src/ch/ch_driver.c +++ w/src/ch/ch_driver.c @@ -1458,11 +1458,10 @@ chStateInitialize(bool privileged, if (!(ch_driver->domainEventState = virObjectEventStateNew())) goto cleanup; - /* Allocate bitmap for migration port reservation */ - if (!(ch_driver->migrationPorts = - virPortAllocatorRangeNew(_("migration"), - 49152, - 49216))) + /* Allocate bitmap for migration port reservation */ + if (!(ch_driver->migrationPorts = virPortAllocatorRangeNew(_("migration"), + CH_MIGRATION_PORT_MIN, + CH_MIGRATION_PORT_MAX))) goto cleanup; if ((rv = chExtractVersion(ch_driver)) < 0) { @@ -2358,51 +2357,52 @@ chDomainInterfaceAddresses(virDomain *dom, static char * chDomainMigrateBegin3(virDomainPtr domain, const char *xmlin, - char **cookieout, - int *cookieoutlen, + char **cookieout G_GNUC_UNUSED, + int *cookieoutlen G_GNUC_UNUSED, unsigned long flags, - const char *dname, - unsigned long resource G_GNUC_UNUSED) + const char *dname G_GNUC_UNUSED, + unsigned long bandwidth) { + virCHDriver *driver = domain->conn->privateData; virDomainObj *vm; char *xml = NULL; - virCHDriver *driver = domain->conn->privateData; - VIR_WARN("chDomainMigrateBegin3 %p %s %p %p %lu %s", - domain, xmlin, cookieout, cookieoutlen, flags, dname); + virCheckFlags(0, NULL); + + if (xmlin) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("xml modification unsupported")); + return NULL; + } + + if (bandwidth != 0) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("migration bandwidth unsupported")); + return NULL; + } + if (!(vm = virCHDomainObjFromDomain(domain))) return NULL; - if (virDomainMigrateBegin3EnsureACL(domain->conn, vm->def) < 0) { - virDomainObjEndAPI(&vm); - return NULL; - } + if (virDomainMigrateBegin3EnsureACL(domain->conn, vm->def) < 0) + goto cleanup; - // Copied from libxl_migration.c:386 if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) goto cleanup; xml = virDomainDefFormat(vm->def, driver->xmlopt, VIR_DOMAIN_DEF_FORMAT_SECURE); - if (xml) { - VIR_WARN("chDomainMigrateBegin3 success. xml: %s", xml); - goto cleanup; - } - - return NULL; cleanup: virDomainObjEndAPI(&vm); return xml; } -static -virDomainDef * +static virDomainDef * chMigrationAnyPrepareDef(virCHDriver *driver, - const char *dom_xml, - const char *dname) + const char *dom_xml, + const char *dname) { virDomainDef *def; - char *name = NULL; if (!dom_xml) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", @@ -2412,15 +2412,15 @@ chMigrationAnyPrepareDef(virCHDriver *driver, if (!(def = virDomainDefParseString(dom_xml, driver->xmlopt, NULL, - VIR_DOMAIN_DEF_PARSE_INACTIVE))) - goto cleanup; + VIR_DOMAIN_DEF_PARSE_INACTIVE))) { + return NULL; + } if (dname) { - VIR_FREE(name); + VIR_FREE(def->name); def->name = g_strdup(dname); } - cleanup: return def; } @@ -2432,13 +2432,14 @@ typedef struct _chMigrationDstArgs { static void chDoMigrateDstReceive(void *opaque) { - chMigrationDstArgs *args = opaque; + g_autofree chMigrationDstArgs *args = opaque; virCHDomainObjPrivate *priv = args->priv; g_autofree char* rcv_uri = NULL; - VIR_WARN("In thread. %u %p", args->port, args->priv); + VIR_DEBUG("In thread. %u %p", args->port, args->priv); if (!priv->monitor) { VIR_ERROR(_("VMs monitor not initialized")); + return; } rcv_uri = g_strdup_printf("tcp:0.0.0.0:%d", args->port); @@ -2447,7 +2448,7 @@ chDoMigrateDstReceive(void *opaque) VIR_WARN("Migration receive failed."); } - VIR_WARN("Migration thread finished its duty"); + VIR_DEBUG("Migration thread finished its duty"); } /** @@ -2457,30 +2458,27 @@ chDoMigrateDstReceive(void *opaque) */ static int chDomainMigratePrepare3(virConnectPtr dconn, - const char *cookiein, - int cookieinlen, - char **cookieout, - int *cookieoutlen, - const char *uri_in, + const char *cookiein G_GNUC_UNUSED, + int cookieinlen G_GNUC_UNUSED, + char **cookieout G_GNUC_UNUSED, + int *cookieoutlen G_GNUC_UNUSED, + const char *uri_in G_GNUC_UNUSED, char **uri_out, unsigned long flags, const char *dname, - unsigned long resource G_GNUC_UNUSED, + unsigned long bandwidth G_GNUC_UNUSED, const char *dom_xml) { virCHDriver *driver = dconn->privateData; virDomainObj *vm = NULL; virCHDomainObjPrivate *priv = NULL; - chMigrationDstArgs *args = g_new0(chMigrationDstArgs, 1); + g_autofree chMigrationDstArgs *args = g_new0(chMigrationDstArgs, 1); unsigned short port = 0; g_autofree char *hostname = NULL; - const char *threadname = "mig-ch"; g_autoptr(virDomainDef) def = NULL; - int rc = 0; - const char *incFormat = "%s:%s:%d"; // seems to differ for AF_INET6 + int ret = -1; - VIR_WARN("chDomainMigratePrepare3 %p %s %u %p %p %s %p %lu %s %s", - dconn, cookiein, cookieinlen, cookieout, cookieoutlen, uri_in, uri_out, flags, dname, dom_xml); + virCheckFlags(0, -1); if (virDomainMigratePrepare3EnsureACL(dconn, def) < 0) return -1; @@ -2488,68 +2486,55 @@ chDomainMigratePrepare3(virConnectPtr dconn, if (!(def = chMigrationAnyPrepareDef(driver, dom_xml, dname))) return -1; - VIR_WARN("Got DomainDef prepared successfully"); + if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) + return -1; - if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) { - rc = -1; - goto cleanup; - } - VIR_WARN("Got port %i", port); + if (!(hostname = virGetHostname())) + return -1; - if ((hostname = virGetHostname()) == NULL) { - rc = -1; - goto cleanup; - } - - *uri_out = g_strdup_printf(incFormat, "tcp", hostname, port); - VIR_WARN("uri out %s", *uri_out); + /* Seems to differ for AF_INET6 */ + *uri_out = g_strdup_printf("tcp:%s:%d", hostname, port); + VIR_DEBUG("uri out %s", *uri_out); if (!(vm = virDomainObjListAdd(driver->domains, &def, driver->xmlopt, VIR_DOMAIN_OBJ_LIST_ADD_LIVE | VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, - NULL))) - { - rc = -1; - VIR_WARN("Could not add Domain Obj to List"); + NULL))) { goto cleanup; } - if (virCHProcessInit(driver, vm) < 0) { - rc = -1; - VIR_WARN("Could not init process"); + if (virCHProcessInit(driver, vm) < 0) goto cleanup; - } - VIR_WARN("Try creating migration thread"); priv = vm->privateData; args->port = port; args->priv = priv; - // VM receiving is blocking which we cannot do here, because it would block - // the Libvirt migration protocol. - // Prepare a thread to receive the migration data - // VIR_FREE(priv->migrationDstReceiveThr); + /* VM receiving is blocking which we cannot do here, because it would block + * the Libvirt migration protocol. Prepare a thread to receive the + * migration data. */ priv->migrationDstReceiveThr = g_new0(virThread, 1); - if (virThreadCreateFull(priv->migrationDstReceiveThr, true, + VIR_DEBUG("Creating migration thread"); + if (virThreadCreateFull(priv->migrationDstReceiveThr, + true, chDoMigrateDstReceive, - threadname, + "mig-ch", false, args) < 0) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("Failed to create thread for receiving migration data")); goto cleanup; } + VIR_DEBUG("Finished creating migration thread"); - VIR_WARN("Finished creating migration thread"); - - - VIR_WARN("Fin migrationPrepare"); - + /* The thread owns the @args now. */ + args = NULL; + ret = 0; cleanup: virDomainObjEndAPI(&vm); - return rc; + return ret; } static int diff --git c/src/ch/ch_monitor.c w/src/ch/ch_monitor.c index 595fa30be0..a6fa37e9f8 100644 --- c/src/ch/ch_monitor.c +++ w/src/ch/ch_monitor.c @@ -1286,7 +1286,7 @@ int virCHMonitorMigrationReceive(virCHMonitor *mon, if (virCHMonitorBuildKeyValueStringJson(&payload, "receiver_url", rcv_uri) != 0) return -1; - VIR_WARN("Receive VM from url %s json: %s", rcv_uri, payload); + VIR_DEBUG("Receive VM from url %s json: %s", rcv_uri, payload); VIR_WITH_OBJECT_LOCK_GUARD(mon) { /* reset all options of a libcurl session handle at first */ diff --git c/src/hypervisor/domain_interface.c w/src/hypervisor/domain_interface.c index a13fcfb7d2..5bc698d272 100644 --- c/src/hypervisor/domain_interface.c +++ w/src/hypervisor/domain_interface.c @@ -82,7 +82,6 @@ virDomainInterfaceEthernetConnect(virDomainDef *def, bool template_ifname = false; const char *tunpath = "/dev/net/tun"; const char *auditdev = tunpath; - VIR_WARN("virDomainInterfaceEthernetConnect %s", net->ifname); if (net->backend.tap) { tunpath = net->backend.tap; diff --git c/src/libvirt-domain.c w/src/libvirt-domain.c index 28347947d1..86ede6b038 100644 --- c/src/libvirt-domain.c +++ w/src/libvirt-domain.c @@ -3307,11 +3307,6 @@ virDomainMigrateVersion3Full(virDomainPtr domain, "params=%p, nparams=%d, useParams=%d, flags=0x%x", dconn, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), bandwidth, params, nparams, useParams, flags); - VIR_WARN( - "dconn=%p, xmlin=%s, dname=%s, uri=%s, bandwidth=%llu, " - "params=%p, nparams=%d, useParams=%d, flags=0x%x", - dconn, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), - bandwidth, params, nparams, useParams, flags); VIR_TYPED_PARAMS_DEBUG(params, nparams); virCheckNonEmptyOptStringArgReturn(dname, NULL); Michal

Hi Michal, thank you for your feedback! Much appreciated! As it was meant for early review, I wasn't caring too much about the commit structure and logging details. I promise to be more thoroughly next time. I will clean up the whole implementation later on and will address your feedback. Best regards Stefan

The POC currently detaches the network device prior to live migration because there is some missing cloud hypervisor support. We might want to restrict live mive migration to VMs without network devices until the issue is resolved in cloud hypervisor. See https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7054 for further information.
participants (3)
-
Michal Prívozník
-
Stefan Kober
-
stefan.kober@cyberus-technology.de