/*
* qemu_migration_params.c: QEMU migration parameters handling
*
* Copyright (C) 2006-2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*
*/
#include
#include "virlog.h"
#include "virerror.h"
#include "viralloc.h"
#include "virstring.h"
#include "qemu_alias.h"
#include "qemu_hotplug.h"
#include "qemu_migration.h"
#include "qemu_migration_params.h"
#include "qemu_monitor.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
VIR_LOG_INIT("qemu.qemu_migration_params");
#define QEMU_MIGRATION_TLS_ALIAS_BASE "libvirt_migrate"
struct _qemuMigrationParams {
virBitmapPtr caps;
qemuMonitorMigrationParams params;
};
typedef struct _qemuMigrationParamsAlwaysOnItem qemuMigrationParamsAlwaysOnItem;
struct _qemuMigrationParamsAlwaysOnItem {
qemuMonitorMigrationCaps cap;
int party; /* bit-wise OR of qemuMigrationParty */
};
typedef struct _qemuMigrationParamsFlagMapItem qemuMigrationParamsFlagMapItem;
struct _qemuMigrationParamsFlagMapItem {
virDomainMigrateFlags flag;
qemuMonitorMigrationCaps cap;
int party; /* bit-wise OR of qemuMigrationParty */
};
/* Migration capabilities which should always be enabled as long as they
* are supported by QEMU. */
static const qemuMigrationParamsAlwaysOnItem qemuMigrationParamsAlwaysOn[] = {
{QEMU_MONITOR_MIGRATION_CAPS_PAUSE_BEFORE_SWITCHOVER,
QEMU_MIGRATION_SOURCE},
};
/* Translation from virDomainMigrateFlags to qemuMonitorMigrationCaps. */
static const qemuMigrationParamsFlagMapItem qemuMigrationParamsFlagMap[] = {
{VIR_MIGRATE_RDMA_PIN_ALL,
QEMU_MONITOR_MIGRATION_CAPS_RDMA_PIN_ALL,
QEMU_MIGRATION_SOURCE | QEMU_MIGRATION_DESTINATION},
{VIR_MIGRATE_AUTO_CONVERGE,
QEMU_MONITOR_MIGRATION_CAPS_AUTO_CONVERGE,
QEMU_MIGRATION_SOURCE},
{VIR_MIGRATE_POSTCOPY,
QEMU_MONITOR_MIGRATION_CAPS_POSTCOPY,
QEMU_MIGRATION_SOURCE | QEMU_MIGRATION_DESTINATION},
};
static qemuMigrationParamsPtr
qemuMigrationParamsNew(void)
{
qemuMigrationParamsPtr params;
if (VIR_ALLOC(params) < 0)
return NULL;
params->caps = virBitmapNew(QEMU_MONITOR_MIGRATION_CAPS_LAST);
if (!params->caps)
goto error;
return params;
error:
qemuMigrationParamsFree(params);
return NULL;
}
void
qemuMigrationParamsFree(qemuMigrationParamsPtr migParams)
{
if (!migParams)
return;
virBitmapFree(migParams->caps);
VIR_FREE(migParams->params.tlsCreds);
VIR_FREE(migParams->params.tlsHostname);
VIR_FREE(migParams);
}
qemuMigrationParamsPtr
qemuMigrationParamsFromFlags(virTypedParameterPtr params,
int nparams,
unsigned long flags,
qemuMigrationParty party)
{
qemuMigrationParamsPtr migParams;
size_t i;
if (!(migParams = qemuMigrationParamsNew()))
return NULL;
for (i = 0; i < ARRAY_CARDINALITY(qemuMigrationParamsFlagMap); i++) {
qemuMonitorMigrationCaps cap = qemuMigrationParamsFlagMap[i].cap;
if (qemuMigrationParamsFlagMap[i].party & party &&
flags & qemuMigrationParamsFlagMap[i].flag) {
VIR_DEBUG("Enabling migration capability '%s'",
qemuMonitorMigrationCapsTypeToString(cap));
ignore_value(virBitmapSetBit(migParams->caps, cap));
}
}
#define GET(PARAM, VAR) \
do { \
int rc; \
if ((rc = virTypedParamsGetInt(params, nparams, \
VIR_MIGRATE_PARAM_ ## PARAM, \
&migParams->params.VAR)) < 0) \
goto error; \
\
if (rc == 1) \
migParams->params.VAR ## _set = true; \
} while (0)
if (params) {
if (party == QEMU_MIGRATION_SOURCE) {
GET(AUTO_CONVERGE_INITIAL, cpuThrottleInitial);
GET(AUTO_CONVERGE_INCREMENT, cpuThrottleIncrement);
}
}
#undef GET
if ((migParams->params.cpuThrottleInitial_set ||
migParams->params.cpuThrottleIncrement_set) &&
!(flags & VIR_MIGRATE_AUTO_CONVERGE)) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("Turn auto convergence on to tune it"));
goto error;
}
return migParams;
error:
qemuMigrationParamsFree(migParams);
return NULL;
}
/**
* qemuMigrationParamsApply
* @driver: qemu driver
* @vm: domain object
* @asyncJob: migration job
* @migParams: migration parameters to send to QEMU
*
* Send all parameters stored in @migParams to QEMU.
*
* Returns 0 on success, -1 on failure.
*/
int
qemuMigrationParamsApply(virQEMUDriverPtr driver,
virDomainObjPtr vm,
int asyncJob,
qemuMigrationParamsPtr migParams)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
bool xbzrleCacheSize_old = false;
int ret = -1;
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
return -1;
if (qemuMonitorSetMigrationCapabilities(priv->mon, priv->migrationCaps,
migParams->caps) < 0)
goto cleanup;
/* If QEMU is too old to support xbzrle-cache-size migration parameter,
* we need to set it via migrate-set-cache-size and tell
* qemuMonitorSetMigrationParams to ignore this parameter.
*/
if (migParams->params.xbzrleCacheSize_set &&
(!priv->job.migParams ||
!priv->job.migParams->params.xbzrleCacheSize_set)) {
if (qemuMonitorSetMigrationCacheSize(priv->mon,
migParams->params.xbzrleCacheSize) < 0)
goto cleanup;
xbzrleCacheSize_old = true;
migParams->params.xbzrleCacheSize_set = false;
}
if (qemuMonitorSetMigrationParams(priv->mon, &migParams->params) < 0)
goto cleanup;
ret = 0;
cleanup:
if (qemuDomainObjExitMonitor(driver, vm) < 0)
ret = -1;
if (xbzrleCacheSize_old)
migParams->params.xbzrleCacheSize_set = true;
return ret;
}
static int
qemuMigrationParamsSetCapability(virDomainObjPtr vm ATTRIBUTE_UNUSED,
qemuMonitorMigrationCaps capability,
bool state,
qemuMigrationParamsPtr migParams)
{
if (state)
ignore_value(virBitmapSetBit(migParams->caps, capability));
else
ignore_value(virBitmapClearBit(migParams->caps, capability));
return 0;
}
/* qemuMigrationParamsEnableTLS
* @driver: pointer to qemu driver
* @vm: domain object
* @tlsListen: server or client
* @asyncJob: Migration job to join
* @tlsAlias: alias to be generated for TLS object
* @secAlias: alias to be generated for a secinfo object
* @hostname: hostname of the migration destination
* @migParams: migration parameters to set
*
* Create the TLS objects for the migration and set the migParams value.
* If QEMU itself does not connect to the destination @hostname must be
* provided for certificate verification.
*
* Returns 0 on success, -1 on failure
*/
int
qemuMigrationParamsEnableTLS(virQEMUDriverPtr driver,
virDomainObjPtr vm,
bool tlsListen,
int asyncJob,
char **tlsAlias,
char **secAlias,
const char *hostname,
qemuMigrationParamsPtr migParams)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
virJSONValuePtr tlsProps = NULL;
virJSONValuePtr secProps = NULL;
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
int ret = -1;
if (!cfg->migrateTLSx509certdir) {
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
_("host migration TLS directory not configured"));
goto error;
}
if ((!priv->job.migParams->params.tlsCreds)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("TLS migration is not supported with this "
"QEMU binary"));
goto error;
}
/* If there's a secret, then grab/store it now using the connection */
if (cfg->migrateTLSx509secretUUID &&
!(priv->migSecinfo =
qemuDomainSecretInfoTLSNew(priv, QEMU_MIGRATION_TLS_ALIAS_BASE,
cfg->migrateTLSx509secretUUID)))
goto error;
if (qemuDomainGetTLSObjects(priv->qemuCaps, priv->migSecinfo,
cfg->migrateTLSx509certdir, tlsListen,
cfg->migrateTLSx509verify,
QEMU_MIGRATION_TLS_ALIAS_BASE,
&tlsProps, tlsAlias, &secProps, secAlias) < 0)
goto error;
/* Ensure the domain doesn't already have the TLS objects defined...
* This should prevent any issues just in case some cleanup wasn't
* properly completed (both src and dst use the same alias) or
* some other error path between now and perform . */
qemuDomainDelTLSObjects(driver, vm, asyncJob, *secAlias, *tlsAlias);
if (qemuDomainAddTLSObjects(driver, vm, asyncJob, *secAlias, &secProps,
*tlsAlias, &tlsProps) < 0)
goto error;
if (VIR_STRDUP(migParams->params.tlsCreds, *tlsAlias) < 0 ||
VIR_STRDUP(migParams->params.tlsHostname, hostname ? hostname : "") < 0)
goto error;
ret = 0;
cleanup:
virObjectUnref(cfg);
return ret;
error:
virJSONValueFree(tlsProps);
virJSONValueFree(secProps);
goto cleanup;
}
/* qemuMigrationParamsDisableTLS
* @vm: domain object
* @migParams: Pointer to a migration parameters block
*
* If we support setting the tls-creds, then set both tls-creds and
* tls-hostname to the empty string ("") which indicates to not use
* TLS on this migration.
*
* Returns 0 on success, -1 on failure
*/
int
qemuMigrationParamsDisableTLS(virDomainObjPtr vm,
qemuMigrationParamsPtr migParams)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
if (!priv->job.migParams->params.tlsCreds)
return 0;
if (VIR_STRDUP(migParams->params.tlsCreds, "") < 0 ||
VIR_STRDUP(migParams->params.tlsHostname, "") < 0)
return -1;
return 0;
}
int
qemuMigrationParamsSetCompression(virDomainObjPtr vm,
qemuMigrationCompressionPtr compression,
qemuMigrationParamsPtr migParams)
{
if (qemuMigrationParamsSetCapability(vm,
QEMU_MONITOR_MIGRATION_CAPS_XBZRLE,
compression->methods &
(1ULL << QEMU_MIGRATION_COMPRESS_XBZRLE),
migParams) < 0)
return -1;
if (qemuMigrationParamsSetCapability(vm,
QEMU_MONITOR_MIGRATION_CAPS_COMPRESS,
compression->methods &
(1ULL << QEMU_MIGRATION_COMPRESS_MT),
migParams) < 0)
return -1;
migParams->params.compressLevel_set = compression->level_set;
migParams->params.compressLevel = compression->level;
migParams->params.compressThreads_set = compression->threads_set;
migParams->params.compressThreads = compression->threads;
migParams->params.decompressThreads_set = compression->dthreads_set;
migParams->params.decompressThreads = compression->dthreads;
migParams->params.xbzrleCacheSize_set = compression->xbzrle_cache_set;
migParams->params.xbzrleCacheSize = compression->xbzrle_cache;
return 0;
}
/* qemuMigrationParamsResetTLS
* @driver: pointer to qemu driver
* @vm: domain object
* @asyncJob: migration job to join
*
* Deconstruct all the setup possibly done for TLS - delete the TLS and
* security objects, free the secinfo, and reset the migration params to "".
*/
static void
qemuMigrationParamsResetTLS(virQEMUDriverPtr driver,
virDomainObjPtr vm,
int asyncJob,
qemuMigrationParamsPtr origParams)
{
char *tlsAlias = NULL;
char *secAlias = NULL;
/* If QEMU does not support TLS migration we didn't set the aliases. */
if (!origParams->params.tlsCreds)
return;
/* NB: If either or both fail to allocate memory we can still proceed
* since the next time we migrate another deletion attempt will be
* made after successfully generating the aliases. */
tlsAlias = qemuAliasTLSObjFromSrcAlias(QEMU_MIGRATION_TLS_ALIAS_BASE);
secAlias = qemuDomainGetSecretAESAlias(QEMU_MIGRATION_TLS_ALIAS_BASE, false);
qemuDomainDelTLSObjects(driver, vm, asyncJob, secAlias, tlsAlias);
qemuDomainSecretInfoFree(&QEMU_DOMAIN_PRIVATE(vm)->migSecinfo);
VIR_FREE(tlsAlias);
VIR_FREE(secAlias);
}
/**
* qemuMigrationParamsCheck:
*
* Check supported migration parameters and keep their original values in
* qemuDomainJobObj so that we can properly reset them at the end of migration.
* Reports an error if any of the currently used capabilities in @migParams
* are unsupported by QEMU.
*/
int
qemuMigrationParamsCheck(virQEMUDriverPtr driver,
virDomainObjPtr vm,
int asyncJob,
qemuMigrationParamsPtr migParams)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
qemuMigrationParamsPtr origParams = NULL;
qemuMonitorMigrationCaps cap;
qemuMigrationParty party;
size_t i;
int ret = -1;
if (asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT)
party = QEMU_MIGRATION_SOURCE;
else
party = QEMU_MIGRATION_DESTINATION;
for (cap = 0; cap < QEMU_MONITOR_MIGRATION_CAPS_LAST; cap++) {
bool state = false;
ignore_value(virBitmapGetBit(migParams->caps, cap, &state));
if (state && !qemuMigrationCapsGet(vm, cap)) {
virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED,
_("Migration option '%s' is not supported by QEMU binary"),
qemuMonitorMigrationCapsTypeToString(cap));
return -1;
}
}
for (i = 0; i < ARRAY_CARDINALITY(qemuMigrationParamsAlwaysOn); i++) {
cap = qemuMigrationParamsAlwaysOn[i].cap;
if (qemuMigrationParamsAlwaysOn[i].party & party &&
qemuMigrationCapsGet(vm, cap)) {
VIR_DEBUG("Enabling migration capability '%s'",
qemuMonitorMigrationCapsTypeToString(cap));
ignore_value(virBitmapSetBit(migParams->caps, cap));
}
}
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
return -1;
if (!(origParams = qemuMigrationParamsNew()))
goto cleanup;
/*
* We want to disable all migration capabilities after migration, no need
* to ask QEMU for their current settings.
*/
if (qemuMonitorGetMigrationParams(priv->mon, &origParams->params) < 0)
goto cleanup;
ret = 0;
cleanup:
if (qemuDomainObjExitMonitor(driver, vm) < 0)
ret = -1;
if (ret == 0)
VIR_STEAL_PTR(priv->job.migParams, origParams);
qemuMigrationParamsFree(origParams);
return ret;
}
/*
* qemuMigrationParamsReset:
*
* Reset all migration parameters so that the next job which internally uses
* migration (save, managedsave, snapshots, dump) will not try to use them.
*/
void
qemuMigrationParamsReset(virQEMUDriverPtr driver,
virDomainObjPtr vm,
int asyncJob,
qemuMigrationParamsPtr origParams)
{
virErrorPtr err = virSaveLastError();
VIR_DEBUG("Resetting migration parameters %p", origParams);
if (!virDomainObjIsActive(vm) || !origParams)
goto cleanup;
if (qemuMigrationParamsApply(driver, vm, asyncJob, origParams) < 0)
goto cleanup;
qemuMigrationParamsResetTLS(driver, vm, asyncJob, origParams);
cleanup:
if (err) {
virSetError(err);
virFreeError(err);
}
}
int
qemuMigrationCapsCheck(virQEMUDriverPtr driver,
virDomainObjPtr vm,
int asyncJob)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
virBitmapPtr migEvent = NULL;
char **caps = NULL;
char **capStr;
int ret = -1;
int rc;
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
return -1;
rc = qemuMonitorGetMigrationCapabilities(priv->mon, &caps);
if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
goto cleanup;
if (!caps) {
ret = 0;
goto cleanup;
}
priv->migrationCaps = virBitmapNew(QEMU_MONITOR_MIGRATION_CAPS_LAST);
if (!priv->migrationCaps)
goto cleanup;
for (capStr = caps; *capStr; capStr++) {
int cap = qemuMonitorMigrationCapsTypeFromString(*capStr);
if (cap < 0) {
VIR_DEBUG("Unknown migration capability: '%s'", *capStr);
} else {
ignore_value(virBitmapSetBit(priv->migrationCaps, cap));
VIR_DEBUG("Found migration capability: '%s'", *capStr);
}
}
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT)) {
migEvent = virBitmapNew(QEMU_MONITOR_MIGRATION_CAPS_LAST);
if (!migEvent)
goto cleanup;
ignore_value(virBitmapSetBit(migEvent, QEMU_MONITOR_MIGRATION_CAPS_EVENTS));
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
goto cleanup;
rc = qemuMonitorSetMigrationCapabilities(priv->mon, migEvent, migEvent);
if (qemuDomainObjExitMonitor(driver, vm) < 0)
goto cleanup;
if (rc < 0) {
virResetLastError();
VIR_DEBUG("Cannot enable migration events; clearing capability");
virQEMUCapsClear(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT);
}
}
/* Migration events capability must always be enabled, clearing it from
* migration capabilities bitmap makes sure it won't be touched anywhere
* else.
*/
ignore_value(virBitmapClearBit(priv->migrationCaps,
QEMU_MONITOR_MIGRATION_CAPS_EVENTS));
ret = 0;
cleanup:
virStringListFree(caps);
return ret;
}
bool
qemuMigrationCapsGet(virDomainObjPtr vm,
qemuMonitorMigrationCaps cap)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
bool enabled = false;
if (priv->migrationCaps)
ignore_value(virBitmapGetBit(priv->migrationCaps, cap, &enabled));
return enabled;
}