[libvirt] [PATCH 0/5] Selective block device migration implementation

The patchset represented in the mail thread implements the selective block device migration for the QEMU driver. This closes the referenced bug [1]. First the supplementary API implemented allowing for multiple key-values pair in the virTypedParameter arrays. This is used to pass the list of block devices to migrate in the following patches. Unit tests for this are added as well. This is the subject of the first three patches. Fourth patch is adding the necessary parameter `migrate_disks' and passes it through the QEMU driver call stack. The introduced `qemuMigrateBlockDevice' function is then checks that the disk is to be migrated because it is in the list. If there is no list specified then the legacy check is used: the device is not shared, not readonly and has a source. Fifth and the last patch is adding the necessary code to the `virsh' utility making it able for user to specify a comma-separated list of the block device names that are to be migrated. The implemented Python bindings patch is to be presented. [1] https://bugzilla.redhat.com/show_bug.cgi?id=1203032 Pavel Boldin (5): util: multi-value virTypedParameter util: virTypedParamsPick* multikey API util: add virTypedParamsPackStrings qemu: migration: selective block device migration virsh: selective block device migration include/libvirt/libvirt-domain.h | 9 ++ include/libvirt/libvirt-host.h | 24 ++++ src/libvirt_public.syms | 3 + src/qemu/qemu_driver.c | 63 ++++++--- src/qemu/qemu_migration.c | 197 ++++++++++++++++---------- src/qemu/qemu_migration.h | 23 ++-- src/util/virtypedparam.c | 290 +++++++++++++++++++++++++++++++-------- tests/Makefile.am | 6 + tests/virtypedparamtest.c | 289 ++++++++++++++++++++++++++++++++++++++ tools/virsh-domain.c | 44 ++++++ 10 files changed, 788 insertions(+), 160 deletions(-) create mode 100644 tests/virtypedparamtest.c -- 1.9.1

The `virTypedParamsValidate' function now can be instructed to allow multiple entries for some of the keys. For this flag the type with the `VIR_TYPED_PARAM_MULTIPLE' flag. Add unit tests for this new behaviour. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 8 ++ src/util/virtypedparam.c | 107 ++++++++++++++++--------- tests/Makefile.am | 6 ++ tests/virtypedparamtest.c | 177 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 tests/virtypedparamtest.c diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index 953366b..a3e8b88 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -180,6 +180,14 @@ typedef enum { } virTypedParameterType; /** + * VIR_TYPED_PARAM_MULTIPLE: + * + * Flag indiciating that the params has multiple occurences of the parameter. + * Only used as a flag for @type argument of the virTypedParamsValidate. + */ +# define VIR_TYPED_PARAM_MULTIPLE (1 << 31) + +/** * virTypedParameterFlags: * * Flags related to libvirt APIs that use virTypedParameter. diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index de2d447..bd47609 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -47,11 +47,18 @@ VIR_ENUM_IMPL(virTypedParameter, VIR_TYPED_PARAM_LAST, * internal utility functions (those in libvirt_private.syms) may * report errors that the caller will dispatch. */ +static int _virTypedParamsSortName(const void *left, const void *right) +{ + const virTypedParameter *param_left = left, *param_right = right; + return strcmp(param_left->field, param_right->field); +} + /* Validate that PARAMS contains only recognized parameter names with - * correct types, and with no duplicates. Pass in as many name/type - * pairs as appropriate, and pass NULL to end the list of accepted - * parameters. Return 0 on success, -1 on failure with error message - * already issued. */ + * correct types, and with no duplicates except for parameters + * specified with VIR_TYPED_PARAM_MULTIPLE flag in type. + * Pass in as many name/type pairs as appropriate, and pass NULL to end + * the list of accepted parameters. Return 0 on success, -1 on failure + * with error message already issued. */ int virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) { @@ -60,60 +67,82 @@ virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) size_t i, j; const char *name; int type; + size_t nkeys = 0, nkeysmax = 0; + virTypedParameterPtr sorted = NULL, keys = NULL; va_start(ap, nparams); - /* Yes, this is quadratic, but since we reject duplicates and - * unknowns, it is constrained by the number of var-args passed - * in, which is expected to be small enough to not be - * noticeable. */ - for (i = 0; i < nparams; i++) { - va_end(ap); - va_start(ap, nparams); + if (VIR_ALLOC_N(sorted, nparams) < 0) + goto cleanup; - name = va_arg(ap, const char *); - while (name) { - type = va_arg(ap, int); - if (STREQ(params[i].field, name)) { - if (params[i].type != type) { - const char *badtype; - - badtype = virTypedParameterTypeToString(params[i].type); - if (!badtype) - badtype = virTypedParameterTypeToString(0); - virReportError(VIR_ERR_INVALID_ARG, - _("invalid type '%s' for parameter '%s', " - "expected '%s'"), - badtype, params[i].field, - virTypedParameterTypeToString(type)); - } - break; - } - name = va_arg(ap, const char *); - } - if (!name) { - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, - _("parameter '%s' not supported"), - params[i].field); + /* Here we intentially don't copy values */ + memcpy(sorted, params, sizeof(*params) * nparams); + qsort(sorted, nparams, sizeof(*sorted), _virTypedParamsSortName); + + name = va_arg(ap, const char *); + while (name) { + type = va_arg(ap, int); + if (VIR_RESIZE_N(keys, nkeysmax, nkeys, 1) < 0) + goto cleanup; + + if (virStrcpyStatic(keys[nkeys].field, name) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Field name '%s' too long"), name); goto cleanup; } - for (j = 0; j < i; j++) { - if (STREQ(params[i].field, params[j].field)) { + + keys[nkeys].type = type & ~VIR_TYPED_PARAM_MULTIPLE; + /* Value is not used anyway */ + keys[nkeys].value.i = type & VIR_TYPED_PARAM_MULTIPLE; + + nkeys++; + name = va_arg(ap, const char *); + } + + qsort(keys, nkeys, sizeof(*keys), _virTypedParamsSortName); + + for (i = 0, j = 0; i < nparams && j < nkeys;) { + if (STRNEQ(sorted[i].field, keys[j].field)) { + j++; + } else { + if (i > j && !(keys[j].value.i & VIR_TYPED_PARAM_MULTIPLE)) { virReportError(VIR_ERR_INVALID_ARG, _("parameter '%s' occurs multiple times"), - params[i].field); + sorted[i].field); goto cleanup; } + if (sorted[i].type != keys[j].type) { + const char *badtype; + + badtype = virTypedParameterTypeToString(sorted[i].type); + if (!badtype) + badtype = virTypedParameterTypeToString(0); + virReportError(VIR_ERR_INVALID_ARG, + _("invalid type '%s' for parameter '%s', " + "expected '%s'"), + badtype, sorted[i].field, + virTypedParameterTypeToString(keys[j].type)); + } + i++; } } + if (j == nkeys && i != nparams) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("parameter '%s' not supported"), + sorted[i].field); + goto cleanup; + } + ret = 0; cleanup: va_end(ap); + VIR_FREE(sorted); + VIR_FREE(keys); return ret; - } + /* Check if params contains only specified parameter names. Return true if * only specified names are present in params, false if params contains any * unspecified parameter name. */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 8e2dbec..9efb441 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -182,6 +182,7 @@ test_programs = virshtest sockettest \ virhostdevtest \ vircaps2xmltest \ virnetdevtest \ + virtypedparamtest \ $(NULL) if WITH_REMOTE @@ -1225,6 +1226,11 @@ objecteventtest_SOURCES = \ testutils.c testutils.h objecteventtest_LDADD = $(LDADDS) +virtypedparamtest_SOURCES = \ + virtypedparamtest.c testutils.h testutils.c +virtypedparamtest_LDADD = $(LDADDS) + + if WITH_LINUX fchosttest_SOURCES = \ fchosttest.c testutils.h testutils.c diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c new file mode 100644 index 0000000..27b7e60 --- /dev/null +++ b/tests/virtypedparamtest.c @@ -0,0 +1,177 @@ +/* + * virtypedparamtest.c: Test typed param functions + * + * Copyright (C) 2015 Mirantis, 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include <stdio.h> +#include <virtypedparam.h> + +#include "testutils.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +typedef struct _TypedParameterTest { + /* Test name for logging */ + const char *name; + /* Flags of the "foobar" parameter check */ + int foobar_flags; + /* Parameters to validate */ + virTypedParameterPtr params; + /* Amount of parameters */ + int nparams; + + /* Expected error code */ + int expected_errcode; + /* Expected error message */ + const char *expected_errmessage; +} TypedParameterTest; + +static int +testTypedParamsValidate(const void *opaque) +{ + int rv; + TypedParameterTest *test = (TypedParameterTest *)opaque; + virErrorPtr errptr; + + rv = virTypedParamsValidate( + test->params, test->nparams, + "foobar", VIR_TYPED_PARAM_STRING | test->foobar_flags, + "foo", VIR_TYPED_PARAM_INT, + "bar", VIR_TYPED_PARAM_UINT, + NULL); + + if (test->expected_errcode) { + errptr = virGetLastError(); + + rv = (errptr == NULL) || ((rv < 0) && + !(errptr->code == test->expected_errcode)); + if (errptr && test->expected_errmessage) { + rv = STRNEQ(test->expected_errmessage, errptr->message); + if (rv) + printf("%s\n", errptr->message); + } + } + + return rv; +} + +#define TEST(testname) { \ + .name = testname, + +#define ENDTEST }, + +#define FOOBAR_SINGLE .foobar_flags = 0, +#define FOOBAR_MULTIPLE .foobar_flags = VIR_TYPED_PARAM_MULTIPLE, + +#define EXPECTED_OK .expected_errcode = 0, .expected_errmessage = NULL, +#define EXPECTED_ERROR(code, msg) .expected_errcode = code, \ + .expected_errmessage = msg, + +#define PARAMS_ARRAY(...) ((virTypedParameter[]){ __VA_ARGS__ }) +#define PARAMS_SIZE(...) ARRAY_CARDINALITY(PARAMS_ARRAY(__VA_ARGS__)) + +#define PARAMS(...) \ + .params = PARAMS_ARRAY(__VA_ARGS__), \ + .nparams = PARAMS_SIZE(__VA_ARGS__), + +#define PARAM(field_, type_) { .field = field_, .type = type_ } + +static int +testTypedParamsValidator(void) +{ + size_t i; + int rv = 0; + + TypedParameterTest test[] = { + TEST("Invalid arg type") + FOOBAR_SINGLE + PARAMS(PARAM("foobar", VIR_TYPED_PARAM_INT)) + EXPECTED_ERROR( + VIR_ERR_INVALID_ARG, + "invalid argument: invalid type 'int' for parameter " + "'foobar', expected 'string'" + ) + ENDTEST + TEST("Extra arg") + FOOBAR_SINGLE + PARAMS(PARAM("f", VIR_TYPED_PARAM_INT)) + EXPECTED_ERROR( + VIR_ERR_INVALID_ARG, + "argument unsupported: parameter 'f' not supported" + ) + ENDTEST + TEST("Valid parameters") + FOOBAR_SINGLE + PARAMS( + PARAM("bar", VIR_TYPED_PARAM_UINT), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foo", VIR_TYPED_PARAM_INT) + ) + EXPECTED_OK + ENDTEST + TEST("Duplicates incorrect") + FOOBAR_SINGLE + PARAMS( + PARAM("bar", VIR_TYPED_PARAM_UINT), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foo", VIR_TYPED_PARAM_INT) + ) + EXPECTED_ERROR( + VIR_ERR_INVALID_ARG, + "invalid argument: parameter 'foobar' occurs multiple times" + ) + ENDTEST + TEST("Duplicates OK for marked") + FOOBAR_MULTIPLE + PARAMS( + PARAM("bar", VIR_TYPED_PARAM_UINT), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foo", VIR_TYPED_PARAM_INT) + ) + EXPECTED_OK + }, + TEST(NULL) ENDTEST + }; + + for (i = 0; test[i].name; ++i) { + if (virtTestRun(test[i].name, testTypedParamsValidate, &test[i]) < 0) + rv = -1; + } + + return rv; +} + +static int +mymain(void) +{ + int rv = 0; + + if (testTypedParamsValidator() < 0) + rv = -1; + + if (rv < 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + +VIRT_TEST_MAIN(mymain) -- 1.9.1

On 12.05.2015 14:07, Pavel Boldin wrote:
The `virTypedParamsValidate' function now can be instructed to allow multiple entries for some of the keys. For this flag the type with the `VIR_TYPED_PARAM_MULTIPLE' flag.
Add unit tests for this new behaviour.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 8 ++ src/util/virtypedparam.c | 107 ++++++++++++++++--------- tests/Makefile.am | 6 ++ tests/virtypedparamtest.c | 177 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 tests/virtypedparamtest.c
diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index 953366b..a3e8b88 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -180,6 +180,14 @@ typedef enum { } virTypedParameterType;
/** + * VIR_TYPED_PARAM_MULTIPLE: + * + * Flag indiciating that the params has multiple occurences of the parameter. + * Only used as a flag for @type argument of the virTypedParamsValidate. + */ +# define VIR_TYPED_PARAM_MULTIPLE (1 << 31) + +/**
I think we should not expose this flag. libvirt-host.h is a public header file, and the flag is/should be private. I think virtypedparam.h is more appropriate.
* virTypedParameterFlags: * * Flags related to libvirt APIs that use virTypedParameter. diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index de2d447..bd47609 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -47,11 +47,18 @@ VIR_ENUM_IMPL(virTypedParameter, VIR_TYPED_PARAM_LAST, * internal utility functions (those in libvirt_private.syms) may * report errors that the caller will dispatch. */
+static int _virTypedParamsSortName(const void *left, const void *right) +{ + const virTypedParameter *param_left = left, *param_right = right; + return strcmp(param_left->field, param_right->field); +} +
We don't like function names starting with an underscore.
/* Validate that PARAMS contains only recognized parameter names with - * correct types, and with no duplicates. Pass in as many name/type - * pairs as appropriate, and pass NULL to end the list of accepted - * parameters. Return 0 on success, -1 on failure with error message - * already issued. */ + * correct types, and with no duplicates except for parameters + * specified with VIR_TYPED_PARAM_MULTIPLE flag in type. + * Pass in as many name/type pairs as appropriate, and pass NULL to end + * the list of accepted parameters. Return 0 on success, -1 on failure + * with error message already issued. */ int virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) { @@ -60,60 +67,82 @@ virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) size_t i, j; const char *name; int type; + size_t nkeys = 0, nkeysmax = 0; + virTypedParameterPtr sorted = NULL, keys = NULL;
va_start(ap, nparams);
- /* Yes, this is quadratic, but since we reject duplicates and - * unknowns, it is constrained by the number of var-args passed - * in, which is expected to be small enough to not be - * noticeable. */ - for (i = 0; i < nparams; i++) { - va_end(ap); - va_start(ap, nparams); + if (VIR_ALLOC_N(sorted, nparams) < 0) + goto cleanup;
- name = va_arg(ap, const char *); - while (name) { - type = va_arg(ap, int); - if (STREQ(params[i].field, name)) { - if (params[i].type != type) { - const char *badtype; - - badtype = virTypedParameterTypeToString(params[i].type); - if (!badtype) - badtype = virTypedParameterTypeToString(0); - virReportError(VIR_ERR_INVALID_ARG, - _("invalid type '%s' for parameter '%s', " - "expected '%s'"), - badtype, params[i].field, - virTypedParameterTypeToString(type)); - } - break; - } - name = va_arg(ap, const char *); - } - if (!name) { - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, - _("parameter '%s' not supported"), - params[i].field); + /* Here we intentially don't copy values */
intentionally
+ memcpy(sorted, params, sizeof(*params) * nparams); + qsort(sorted, nparams, sizeof(*sorted), _virTypedParamsSortName); + + name = va_arg(ap, const char *); + while (name) { + type = va_arg(ap, int); + if (VIR_RESIZE_N(keys, nkeysmax, nkeys, 1) < 0) + goto cleanup; + + if (virStrcpyStatic(keys[nkeys].field, name) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Field name '%s' too long"), name); goto cleanup; } - for (j = 0; j < i; j++) { - if (STREQ(params[i].field, params[j].field)) { + + keys[nkeys].type = type & ~VIR_TYPED_PARAM_MULTIPLE; + /* Value is not used anyway */ + keys[nkeys].value.i = type & VIR_TYPED_PARAM_MULTIPLE; + + nkeys++; + name = va_arg(ap, const char *); + } + + qsort(keys, nkeys, sizeof(*keys), _virTypedParamsSortName); + + for (i = 0, j = 0; i < nparams && j < nkeys;) { + if (STRNEQ(sorted[i].field, keys[j].field)) { + j++; + } else { + if (i > j && !(keys[j].value.i & VIR_TYPED_PARAM_MULTIPLE)) { virReportError(VIR_ERR_INVALID_ARG, _("parameter '%s' occurs multiple times"), - params[i].field); + sorted[i].field); goto cleanup; } + if (sorted[i].type != keys[j].type) { + const char *badtype; + + badtype = virTypedParameterTypeToString(sorted[i].type); + if (!badtype) + badtype = virTypedParameterTypeToString(0); + virReportError(VIR_ERR_INVALID_ARG, + _("invalid type '%s' for parameter '%s', " + "expected '%s'"), + badtype, sorted[i].field, + virTypedParameterTypeToString(keys[j].type));
missing goto cleanup;
+ } + i++; } }
+ if (j == nkeys && i != nparams) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("parameter '%s' not supported"), + sorted[i].field); + goto cleanup; + } + ret = 0; cleanup: va_end(ap); + VIR_FREE(sorted); + VIR_FREE(keys); return ret; - }
+ /* Check if params contains only specified parameter names. Return true if * only specified names are present in params, false if params contains any * unspecified parameter name. */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 8e2dbec..9efb441 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -182,6 +182,7 @@ test_programs = virshtest sockettest \ virhostdevtest \ vircaps2xmltest \ virnetdevtest \ + virtypedparamtest \ $(NULL)
if WITH_REMOTE @@ -1225,6 +1226,11 @@ objecteventtest_SOURCES = \ testutils.c testutils.h objecteventtest_LDADD = $(LDADDS)
+virtypedparamtest_SOURCES = \ + virtypedparamtest.c testutils.h testutils.c +virtypedparamtest_LDADD = $(LDADDS) + + if WITH_LINUX fchosttest_SOURCES = \ fchosttest.c testutils.h testutils.c diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c new file mode 100644 index 0000000..27b7e60 --- /dev/null +++ b/tests/virtypedparamtest.c @@ -0,0 +1,177 @@ +/* + * virtypedparamtest.c: Test typed param functions + * + * Copyright (C) 2015 Mirantis, 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include <stdio.h> +#include <virtypedparam.h> + +#include "testutils.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +typedef struct _TypedParameterTest { + /* Test name for logging */ + const char *name; + /* Flags of the "foobar" parameter check */ + int foobar_flags; + /* Parameters to validate */ + virTypedParameterPtr params; + /* Amount of parameters */ + int nparams; + + /* Expected error code */ + int expected_errcode; + /* Expected error message */ + const char *expected_errmessage; +} TypedParameterTest; + +static int +testTypedParamsValidate(const void *opaque) +{ + int rv; + TypedParameterTest *test = (TypedParameterTest *)opaque; + virErrorPtr errptr; + + rv = virTypedParamsValidate( + test->params, test->nparams, + "foobar", VIR_TYPED_PARAM_STRING | test->foobar_flags, + "foo", VIR_TYPED_PARAM_INT, + "bar", VIR_TYPED_PARAM_UINT, + NULL); + + if (test->expected_errcode) { + errptr = virGetLastError(); + + rv = (errptr == NULL) || ((rv < 0) && + !(errptr->code == test->expected_errcode)); + if (errptr && test->expected_errmessage) { + rv = STRNEQ(test->expected_errmessage, errptr->message); + if (rv) + printf("%s\n", errptr->message); + } + } + + return rv; +} + +#define TEST(testname) { \ + .name = testname, + +#define ENDTEST }, + +#define FOOBAR_SINGLE .foobar_flags = 0, +#define FOOBAR_MULTIPLE .foobar_flags = VIR_TYPED_PARAM_MULTIPLE, + +#define EXPECTED_OK .expected_errcode = 0, .expected_errmessage = NULL, +#define EXPECTED_ERROR(code, msg) .expected_errcode = code, \ + .expected_errmessage = msg, + +#define PARAMS_ARRAY(...) ((virTypedParameter[]){ __VA_ARGS__ }) +#define PARAMS_SIZE(...) ARRAY_CARDINALITY(PARAMS_ARRAY(__VA_ARGS__)) + +#define PARAMS(...) \ + .params = PARAMS_ARRAY(__VA_ARGS__), \ + .nparams = PARAMS_SIZE(__VA_ARGS__), + +#define PARAM(field_, type_) { .field = field_, .type = type_ } + +static int +testTypedParamsValidator(void) +{ + size_t i; + int rv = 0; + + TypedParameterTest test[] = { + TEST("Invalid arg type") + FOOBAR_SINGLE + PARAMS(PARAM("foobar", VIR_TYPED_PARAM_INT)) + EXPECTED_ERROR( + VIR_ERR_INVALID_ARG, + "invalid argument: invalid type 'int' for parameter " + "'foobar', expected 'string'" + ) + ENDTEST + TEST("Extra arg") + FOOBAR_SINGLE + PARAMS(PARAM("f", VIR_TYPED_PARAM_INT)) + EXPECTED_ERROR( + VIR_ERR_INVALID_ARG, + "argument unsupported: parameter 'f' not supported" + ) + ENDTEST + TEST("Valid parameters") + FOOBAR_SINGLE + PARAMS( + PARAM("bar", VIR_TYPED_PARAM_UINT), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foo", VIR_TYPED_PARAM_INT) + ) + EXPECTED_OK + ENDTEST + TEST("Duplicates incorrect") + FOOBAR_SINGLE + PARAMS( + PARAM("bar", VIR_TYPED_PARAM_UINT), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foo", VIR_TYPED_PARAM_INT) + ) + EXPECTED_ERROR( + VIR_ERR_INVALID_ARG, + "invalid argument: parameter 'foobar' occurs multiple times" + ) + ENDTEST + TEST("Duplicates OK for marked") + FOOBAR_MULTIPLE + PARAMS( + PARAM("bar", VIR_TYPED_PARAM_UINT), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foobar", VIR_TYPED_PARAM_STRING), + PARAM("foo", VIR_TYPED_PARAM_INT) + ) + EXPECTED_OK + }, + TEST(NULL) ENDTEST + };
I must say this looks like a plain English programming to me. It's not that I would hate English, but I think it would be more readable if it was C. I'm not saying you must drop all the macros, but ENDTEST? Although kudos for the idea.
+ + for (i = 0; test[i].name; ++i) { + if (virtTestRun(test[i].name, testTypedParamsValidate, &test[i]) < 0) + rv = -1; + } + + return rv; +} + +static int +mymain(void) +{ + int rv = 0; + + if (testTypedParamsValidator() < 0) + rv = -1; + + if (rv < 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + +VIRT_TEST_MAIN(mymain)
Hm. How are users supposed to create multi value array of typed parameters? Usually, they create it by calling virTypedParamsAdd* which among other things call VIR_TYPED_PARAM_CHECK(), which checks whether there already exists a key with given name. I *think* this check can be removed from all the callers and we can rely just on the server validating the array. On the other hand - there's not much that a client can know which keys are allowed to occur multiple times and which not. Michal

Add multikey APIs for virTypedParams*: * virTypedParamsPick that returns all the parameters with the specified name and type. * virTypedParamsPickStrings that returns a NULL-terminated `const char**' list with all the values for specified name and string type. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 10 +++++ src/libvirt_public.syms | 6 +++ src/util/virtypedparam.c | 90 ++++++++++++++++++++++++++++++++++++++++++ tests/virtypedparamtest.c | 87 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index a3e8b88..afa730f 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -256,6 +256,12 @@ virTypedParameterPtr virTypedParamsGet (virTypedParameterPtr params, int nparams, const char *name); +virTypedParameterPtr* +virTypedParamsPick (virTypedParameterPtr params, + int nparams, + const char *name, + int type, + size_t *picked); int virTypedParamsGetInt (virTypedParameterPtr params, int nparams, @@ -291,6 +297,10 @@ virTypedParamsGetString (virTypedParameterPtr params, int nparams, const char *name, const char **value); +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, + const char *name); int virTypedParamsAddInt (virTypedParameterPtr *params, int *nparams, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index ef3d2f0..d886967 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -710,4 +710,10 @@ LIBVIRT_1.2.15 { virDomainDelIOThread; } LIBVIRT_1.2.14; +LIBVIRT_1.2.16 { + global: + virTypedParamsPick; + virTypedParamsPickStrings; +} LIBVIRT_1.2.15; + # .... define new API here using predicted next version number .... diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index bd47609..643d10e 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -480,6 +480,56 @@ virTypedParamsGet(virTypedParameterPtr params, } +/** + * virTypedParamsPick: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * @type: type of the parameter to find + * @picked: pointer to a size_t with amount of picked + * + * + * Finds typed parameters called @name. + * + * Returns pointers to the parameters or NULL if there are none in @params. + * This function does not raise an error, even when returning NULL. + * Callee should call VIR_FREE on the returned array. + */ +virTypedParameterPtr* +virTypedParamsPick(virTypedParameterPtr params, + int nparams, + const char *name, + int type, + size_t *picked) +{ + size_t i, max = 0; + virTypedParameterPtr *values = NULL; + + *picked = 0; + + if (!params || !name) + return NULL; + + for (i = 0; i < nparams; i++) { + if (params[i].type == type && STREQ(params[i].field, name)) { + if (VIR_RESIZE_N(values, max, *picked, 1) < 0) + goto error; + + values[*picked] = ¶ms[i]; + + *picked += 1; + } + } + + return values; + + error: + *picked = 0; + VIR_FREE(values); + return NULL; +} + + #define VIR_TYPED_PARAM_CHECK_TYPE(check_type) \ do { if (param->type != check_type) { \ virReportError(VIR_ERR_INVALID_ARG, \ @@ -747,6 +797,46 @@ virTypedParamsGetString(virTypedParameterPtr params, } +/** + * virTypedParamsPickStrings: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * + * + * Finds typed parameters called @name. + * + * Returns pointers to the parameters or NULL if there are none in @params. + * This function does not raise an error, even when returning NULL. + * Callee should call VIR_FREE on the returned array. + */ +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, const char *name) +{ + const char **values = NULL; + size_t i, picked; + virTypedParameterPtr *picked_params; + + picked_params = virTypedParamsPick(params, nparams, + name, VIR_TYPED_PARAM_STRING, + &picked); + + if (picked_params == NULL) + return NULL; + + if (VIR_ALLOC_N(values, picked + 1) < 0) + goto cleanup; + + for (i = 0; i < picked; i++) + values[i] = picked_params[i]->value.s; + + cleanup: + VIR_FREE(picked_params); + return values; +} + + #define VIR_TYPED_PARAM_CHECK() \ do { if (virTypedParamsGet(*params, n, name)) { \ virReportError(VIR_ERR_INVALID_ARG, \ diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c index 27b7e60..287d3f1 100644 --- a/tests/virtypedparamtest.c +++ b/tests/virtypedparamtest.c @@ -95,6 +95,87 @@ testTypedParamsValidate(const void *opaque) #define PARAM(field_, type_) { .field = field_, .type = type_ } static int +testTypedParamsPick(const void *opaque ATTRIBUTE_UNUSED) +{ + size_t i, picked; + int rv = -1; + virTypedParameter params[] = { + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + }; + virTypedParameterPtr *picked_params = NULL; + + + picked_params = virTypedParamsPick(params, ARRAY_CARDINALITY(params), + "foo", VIR_TYPED_PARAM_INT, &picked); + if (picked != 3) + goto cleanup; + + for (i = 0; i < picked; i++) { + if (picked_params[i] != ¶ms[1 + i * 2]) + goto cleanup; + } + VIR_FREE(picked_params); + + picked_params = virTypedParamsPick(params, ARRAY_CARDINALITY(params), + "bar", VIR_TYPED_PARAM_UINT, &picked); + + if (picked != 2) + goto cleanup; + + for (i = 0; i < picked; i++) { + if (picked_params[i] != ¶ms[i * 2]) + goto cleanup; + } + + rv = 0; + cleanup: + VIR_FREE(picked_params); + return rv; +} + +static int +testTypedParamsPickStrings(const void *opaque ATTRIBUTE_UNUSED) +{ + size_t i; + int rv = -1; + const char **strings = NULL; + + virTypedParameter params[] = { + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar1"} }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar2"} }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar3"} } + }; + + strings = virTypedParamsPickStrings(params, + ARRAY_CARDINALITY(params), + "bar"); + + for (i = 0; strings[i]; i++) { + if (!STREQLEN(strings[i], "bar", 3)) + goto cleanup; + if (strings[i][3] != i + '1') + goto cleanup; + } + + rv = 0; + cleanup: + VIR_FREE(strings); + return rv; +} + +static int testTypedParamsValidator(void) { size_t i; @@ -169,6 +250,12 @@ mymain(void) if (testTypedParamsValidator() < 0) rv = -1; + if (virtTestRun("Picking", testTypedParamsPick, NULL) < 0) + rv = -1; + + if (virtTestRun("Picking Strings", testTypedParamsPickStrings, NULL) < 0) + rv = -1; + if (rv < 0) return EXIT_FAILURE; return EXIT_SUCCESS; -- 1.9.1

On 12.05.2015 14:07, Pavel Boldin wrote:
Add multikey APIs for virTypedParams*:
* virTypedParamsPick that returns all the parameters with the specified name and type. * virTypedParamsPickStrings that returns a NULL-terminated `const char**' list with all the values for specified name and string type.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 10 +++++ src/libvirt_public.syms | 6 +++ src/util/virtypedparam.c | 90 ++++++++++++++++++++++++++++++++++++++++++ tests/virtypedparamtest.c | 87 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+)
diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index a3e8b88..afa730f 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -256,6 +256,12 @@ virTypedParameterPtr virTypedParamsGet (virTypedParameterPtr params, int nparams, const char *name); +virTypedParameterPtr*
s/Ptr*/Ptr */
+virTypedParamsPick (virTypedParameterPtr params, + int nparams, + const char *name, + int type, + size_t *picked); int virTypedParamsGetInt (virTypedParameterPtr params, int nparams, @@ -291,6 +297,10 @@ virTypedParamsGetString (virTypedParameterPtr params, int nparams, const char *name, const char **value); +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, + const char *name); int virTypedParamsAddInt (virTypedParameterPtr *params, int *nparams, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index ef3d2f0..d886967 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -710,4 +710,10 @@ LIBVIRT_1.2.15 { virDomainDelIOThread; } LIBVIRT_1.2.14;
+LIBVIRT_1.2.16 { + global: + virTypedParamsPick; + virTypedParamsPickStrings; +} LIBVIRT_1.2.15; + # .... define new API here using predicted next version number .... diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index bd47609..643d10e 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -480,6 +480,56 @@ virTypedParamsGet(virTypedParameterPtr params, }
+/** + * virTypedParamsPick: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * @type: type of the parameter to find + * @picked: pointer to a size_t with amount of picked
Maybe "the length of returned array"?
+ * + * + * Finds typed parameters called @name. + * + * Returns pointers to the parameters or NULL if there are none in @params. + * This function does not raise an error, even when returning NULL.
Generally, it's not okay for a public error to not throw an error. Returning NULL is a valid case though. However I think we need to distinguish between no result returned and an error occurring .. [1]
+ * Callee should call VIR_FREE on the returned array.
Don't want to be picky, but s/Callee/Caller/ and /call VIR_FREE on/free/. And maybe mention that it's only the array that needs to be freed - items within it are just copied.
+ */ +virTypedParameterPtr* +virTypedParamsPick(virTypedParameterPtr params, + int nparams, + const char *name, + int type, + size_t *picked) +{ + size_t i, max = 0; + virTypedParameterPtr *values = NULL; +
This is a public API. The very first thing it has to do is call virResetLastError().
+ *picked = 0;
Nah, this should be checked for NULL.
+ + if (!params || !name) + return NULL; + + for (i = 0; i < nparams; i++) { + if (params[i].type == type && STREQ(params[i].field, name)) { + if (VIR_RESIZE_N(values, max, *picked, 1) < 0)
1: .. as a result of OOM. So maybe the function header should look more like this: int virTypedParamsFilter(virTypedParameterPtr params, int nparams, const char *name, int type, virTypedParameterPtr *ret, size_t *picked); Also, is the @type needed? I guess we can go with just @name. Picking all the params that have desired key name.
+ goto error; + + values[*picked] = ¶ms[i]; + + *picked += 1; + } + } + + return values; + + error: + *picked = 0; + VIR_FREE(values); + return NULL; +} + + #define VIR_TYPED_PARAM_CHECK_TYPE(check_type) \ do { if (param->type != check_type) { \ virReportError(VIR_ERR_INVALID_ARG, \ @@ -747,6 +797,46 @@ virTypedParamsGetString(virTypedParameterPtr params, }
+/** + * virTypedParamsPickStrings: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * + * + * Finds typed parameters called @name. + * + * Returns pointers to the parameters or NULL if there are none in @params.
Returns a string list, which is a NULL terminated array of pointers to strings.
+ * This function does not raise an error, even when returning NULL. + * Callee should call VIR_FREE on the returned array. + */ +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, const char *name) +{ + const char **values = NULL; + size_t i, picked; + virTypedParameterPtr *picked_params;
Again, public API, virResetLastError() should go here.
+ + picked_params = virTypedParamsPick(params, nparams, + name, VIR_TYPED_PARAM_STRING, + &picked); + + if (picked_params == NULL) + return NULL; + + if (VIR_ALLOC_N(values, picked + 1) < 0) + goto cleanup; + + for (i = 0; i < picked; i++) + values[i] = picked_params[i]->value.s;
Hm.. Currently we allow virTypedParamsAddString(params, &nparams, &maxparams, "some key name", NULL); where NULL is the value. Not that it would be used anywhere, but we allow it. It would break the list here. So maybe we need to return the length of the list too?
+ + cleanup: + VIR_FREE(picked_params); + return values; +}
This function suffers the same problems as the previous one. I guess we need slightly different function header.
+ + #define VIR_TYPED_PARAM_CHECK() \ do { if (virTypedParamsGet(*params, n, name)) { \ virReportError(VIR_ERR_INVALID_ARG, \
See? This is the check I'm mentioning in 1/5.
diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c index 27b7e60..287d3f1 100644 --- a/tests/virtypedparamtest.c +++ b/tests/virtypedparamtest.c @@ -95,6 +95,87 @@ testTypedParamsValidate(const void *opaque) #define PARAM(field_, type_) { .field = field_, .type = type_ }
static int +testTypedParamsPick(const void *opaque ATTRIBUTE_UNUSED) +{ + size_t i, picked; + int rv = -1; + virTypedParameter params[] = { + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + }; + virTypedParameterPtr *picked_params = NULL; + + + picked_params = virTypedParamsPick(params, ARRAY_CARDINALITY(params), + "foo", VIR_TYPED_PARAM_INT, &picked); + if (picked != 3) + goto cleanup; + + for (i = 0; i < picked; i++) { + if (picked_params[i] != ¶ms[1 + i * 2]) + goto cleanup; + } + VIR_FREE(picked_params); + + picked_params = virTypedParamsPick(params, ARRAY_CARDINALITY(params), + "bar", VIR_TYPED_PARAM_UINT, &picked); + + if (picked != 2) + goto cleanup; + + for (i = 0; i < picked; i++) { + if (picked_params[i] != ¶ms[i * 2]) + goto cleanup; + } + + rv = 0; + cleanup: + VIR_FREE(picked_params); + return rv; +} + +static int +testTypedParamsPickStrings(const void *opaque ATTRIBUTE_UNUSED) +{ + size_t i; + int rv = -1; + const char **strings = NULL; + + virTypedParameter params[] = { + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar1"} }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar2"} }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar3"} } + }; + + strings = virTypedParamsPickStrings(params, + ARRAY_CARDINALITY(params), + "bar"); + + for (i = 0; strings[i]; i++) { + if (!STREQLEN(strings[i], "bar", 3)) + goto cleanup; + if (strings[i][3] != i + '1') + goto cleanup; + } + + rv = 0; + cleanup: + VIR_FREE(strings); + return rv; +} + +static int testTypedParamsValidator(void) { size_t i; @@ -169,6 +250,12 @@ mymain(void) if (testTypedParamsValidator() < 0) rv = -1;
+ if (virtTestRun("Picking", testTypedParamsPick, NULL) < 0) + rv = -1; + + if (virtTestRun("Picking Strings", testTypedParamsPickStrings, NULL) < 0) + rv = -1; + if (rv < 0) return EXIT_FAILURE; return EXIT_SUCCESS;
Michal

The `virTypedParamsPackStrings' function provides interface to pack multiple string values under the same key to the `virTypedParameter' array. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 6 +++ src/libvirt_public.syms | 1 + src/util/virtypedparam.c | 94 +++++++++++++++++++++++++++++++++--------- tests/virtypedparamtest.c | 28 +++++++++++++ 4 files changed, 109 insertions(+), 20 deletions(-) diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index afa730f..090ac83 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -344,6 +344,12 @@ virTypedParamsAddString (virTypedParameterPtr *params, const char *name, const char *value); int +virTypedParamsPackStrings(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char **values); +int virTypedParamsAddFromString(virTypedParameterPtr *params, int *nparams, int *maxparams, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index d886967..8a24bb6 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -714,6 +714,7 @@ LIBVIRT_1.2.16 { global: virTypedParamsPick; virTypedParamsPickStrings; + virTypedParamsPackStrings; } LIBVIRT_1.2.15; # .... define new API here using predicted next version number .... diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index 643d10e..9f2ab3c 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -1132,6 +1132,43 @@ virTypedParamsAddBoolean(virTypedParameterPtr *params, return -1; } +static int +virTypedParamsAddStringFull(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char *value, + bool unique) +{ + char *str = NULL; + size_t max = *maxparams; + size_t n = *nparams; + + virResetLastError(); + + if (unique) + VIR_TYPED_PARAM_CHECK(); + if (VIR_RESIZE_N(*params, max, n, 1) < 0) + goto error; + *maxparams = max; + + if (VIR_STRDUP(str, value) < 0) + goto error; + + if (virTypedParameterAssign(*params + n, name, + VIR_TYPED_PARAM_STRING, str) < 0) { + VIR_FREE(str); + goto error; + } + + *nparams += 1; + return 0; + + error: + virDispatchError(NULL); + return -1; +} + /** * virTypedParamsAddString: @@ -1160,32 +1197,49 @@ virTypedParamsAddString(virTypedParameterPtr *params, const char *name, const char *value) { - char *str = NULL; - size_t max = *maxparams; - size_t n = *nparams; + return virTypedParamsAddStringFull(params, + nparams, + maxparams, + name, + value, + 1); +} - virResetLastError(); - VIR_TYPED_PARAM_CHECK(); - if (VIR_RESIZE_N(*params, max, n, 1) < 0) - goto error; - *maxparams = max; +/** + * virTypedParamsPackStrings: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @maxparams: maximum number of parameters that can be stored in @params + * array without allocating more memory + * @name: name of the parameter to store values to + * @values: the values to store into the new parameters + * + * Packs NULL-terminated list of strings @values into @params under the + * key @name. + * + * Returns 0 on success, -1 on error. + */ +int +virTypedParamsPackStrings(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char **values) +{ + size_t i; + int rv = -1; - if (VIR_STRDUP(str, value) < 0) - goto error; + if (!values) + return 0; - if (virTypedParameterAssign(*params + n, name, - VIR_TYPED_PARAM_STRING, str) < 0) { - VIR_FREE(str); - goto error; + for (i = 0; values[i]; i++) { + if ((rv = virTypedParamsAddStringFull(params, nparams, maxparams, + name, values[i], 0)) < 0) + break; } - *nparams += 1; - return 0; - - error: - virDispatchError(NULL); - return -1; + return rv; } diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c index 287d3f1..037d5d1 100644 --- a/tests/virtypedparamtest.c +++ b/tests/virtypedparamtest.c @@ -139,6 +139,31 @@ testTypedParamsPick(const void *opaque ATTRIBUTE_UNUSED) } static int +testTypedParamsPackStrings(const void *opaque ATTRIBUTE_UNUSED) +{ + int rv = 0; + virTypedParameterPtr params = NULL; + int nparams = 0, maxparams = 0, i; + + const char *values[] = { + "foo", "bar", "foobar", NULL + }; + + rv = virTypedParamsPackStrings(¶ms, &nparams, &maxparams, "param", + values); + + for (i = 0; i < nparams; i++) { + if (STRNEQ(params[i].field, "param") || + STRNEQ(params[i].value.s, values[i]) || + params[i].type != VIR_TYPED_PARAM_STRING) + rv = -1; + } + + virTypedParamsFree(params, nparams); + return rv; +} + +static int testTypedParamsPickStrings(const void *opaque ATTRIBUTE_UNUSED) { size_t i; @@ -256,6 +281,9 @@ mymain(void) if (virtTestRun("Picking Strings", testTypedParamsPickStrings, NULL) < 0) rv = -1; + if (virtTestRun("Packing Strings", testTypedParamsPackStrings, NULL) < 0) + rv = -1; + if (rv < 0) return EXIT_FAILURE; return EXIT_SUCCESS; -- 1.9.1

On 12.05.2015 14:07, Pavel Boldin wrote:
The `virTypedParamsPackStrings' function provides interface to pack multiple string values under the same key to the `virTypedParameter' array.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 6 +++ src/libvirt_public.syms | 1 + src/util/virtypedparam.c | 94 +++++++++++++++++++++++++++++++++--------- tests/virtypedparamtest.c | 28 +++++++++++++ 4 files changed, 109 insertions(+), 20 deletions(-)
diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index afa730f..090ac83 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -344,6 +344,12 @@ virTypedParamsAddString (virTypedParameterPtr *params, const char *name, const char *value); int +virTypedParamsPackStrings(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char **values);
Rather unusual name. How about virTypedParamsAddStringList()?
+int virTypedParamsAddFromString(virTypedParameterPtr *params, int *nparams, int *maxparams, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index d886967..8a24bb6 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -714,6 +714,7 @@ LIBVIRT_1.2.16 { global: virTypedParamsPick; virTypedParamsPickStrings; + virTypedParamsPackStrings; } LIBVIRT_1.2.15;
# .... define new API here using predicted next version number .... diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index 643d10e..9f2ab3c 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -1132,6 +1132,43 @@ virTypedParamsAddBoolean(virTypedParameterPtr *params, return -1; }
+static int +virTypedParamsAddStringFull(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char *value, + bool unique) +{ + char *str = NULL; + size_t max = *maxparams; + size_t n = *nparams; + + virResetLastError();
No. This is not a public API. This has nothing to do here.
+ + if (unique) + VIR_TYPED_PARAM_CHECK();
So you are kind of trying to solve the problem I've raised in 1/5. Unfortunately, I guess we ought to drop the check entirely. Not only for strings, but for other types too.
+ if (VIR_RESIZE_N(*params, max, n, 1) < 0) + goto error; + *maxparams = max; + + if (VIR_STRDUP(str, value) < 0) + goto error; + + if (virTypedParameterAssign(*params + n, name, + VIR_TYPED_PARAM_STRING, str) < 0) { + VIR_FREE(str); + goto error; + } + + *nparams += 1; + return 0; + + error: + virDispatchError(NULL); + return -1; +} +
/** * virTypedParamsAddString: @@ -1160,32 +1197,49 @@ virTypedParamsAddString(virTypedParameterPtr *params, const char *name, const char *value) { - char *str = NULL; - size_t max = *maxparams; - size_t n = *nparams; + return virTypedParamsAddStringFull(params, + nparams, + maxparams, + name, + value, + 1);
s/1/true/
+}
- virResetLastError();
- VIR_TYPED_PARAM_CHECK(); - if (VIR_RESIZE_N(*params, max, n, 1) < 0) - goto error; - *maxparams = max; +/** + * virTypedParamsPackStrings: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @maxparams: maximum number of parameters that can be stored in @params + * array without allocating more memory + * @name: name of the parameter to store values to + * @values: the values to store into the new parameters + * + * Packs NULL-terminated list of strings @values into @params under the + * key @name. + * + * Returns 0 on success, -1 on error. + */ +int +virTypedParamsPackStrings(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char **values) +{ + size_t i; + int rv = -1;
- if (VIR_STRDUP(str, value) < 0) - goto error; + if (!values) + return 0;
- if (virTypedParameterAssign(*params + n, name, - VIR_TYPED_PARAM_STRING, str) < 0) { - VIR_FREE(str); - goto error; + for (i = 0; values[i]; i++) { + if ((rv = virTypedParamsAddStringFull(params, nparams, maxparams, + name, values[i], 0)) < 0)
s/0/false/
+ break; }
- *nparams += 1; - return 0; - - error: - virDispatchError(NULL); - return -1; + return rv; }
diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c index 287d3f1..037d5d1 100644 --- a/tests/virtypedparamtest.c +++ b/tests/virtypedparamtest.c @@ -139,6 +139,31 @@ testTypedParamsPick(const void *opaque ATTRIBUTE_UNUSED) }
static int +testTypedParamsPackStrings(const void *opaque ATTRIBUTE_UNUSED) +{ + int rv = 0; + virTypedParameterPtr params = NULL; + int nparams = 0, maxparams = 0, i; + + const char *values[] = { + "foo", "bar", "foobar", NULL + }; + + rv = virTypedParamsPackStrings(¶ms, &nparams, &maxparams, "param", + values); + + for (i = 0; i < nparams; i++) { + if (STRNEQ(params[i].field, "param") || + STRNEQ(params[i].value.s, values[i]) || + params[i].type != VIR_TYPED_PARAM_STRING) + rv = -1; + } + + virTypedParamsFree(params, nparams); + return rv; +} + +static int testTypedParamsPickStrings(const void *opaque ATTRIBUTE_UNUSED) { size_t i; @@ -256,6 +281,9 @@ mymain(void) if (virtTestRun("Picking Strings", testTypedParamsPickStrings, NULL) < 0) rv = -1;
+ if (virtTestRun("Packing Strings", testTypedParamsPackStrings, NULL) < 0) + rv = -1; + if (rv < 0) return EXIT_FAILURE; return EXIT_SUCCESS;
Michal

Implement a `migrate_disks' parameters for the QEMU driver. This multi- value parameter can be used to explicitly specify what block devices are to be migrated using the NBD server. Tunnelled migration using NBD is to be done. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-domain.h | 9 ++ src/qemu/qemu_driver.c | 63 +++++++++---- src/qemu/qemu_migration.c | 198 ++++++++++++++++++++++++--------------- src/qemu/qemu_migration.h | 23 +++-- 4 files changed, 192 insertions(+), 101 deletions(-) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 0f465b9..6b48923 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -748,6 +748,15 @@ typedef enum { */ # define VIR_MIGRATE_PARAM_LISTEN_ADDRESS "listen_address" +/** + * VIR_MIGRATE_PARAM_MIGRATE_DISKS: + * + * virDomainMigrate* params multiple field: The multiple values that list + * the block devices to be migrated. At the moment this is only supported + * by the QEMU driver but not for the tunnelled migration. + */ +# define VIR_MIGRATE_PARAM_MIGRATE_DISKS "migrate_disks" + /* Domain migration. */ virDomainPtr virDomainMigrate (virDomainPtr domain, virConnectPtr dconn, unsigned long flags, const char *dname, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 3695b26..2f53df6 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12472,7 +12472,7 @@ qemuDomainMigratePrepare2(virConnectPtr dconn, ret = qemuMigrationPrepareDirect(driver, dconn, NULL, 0, NULL, NULL, /* No cookies */ uri_in, uri_out, - &def, origname, NULL, flags); + &def, origname, NULL, flags, NULL); cleanup: VIR_FREE(origname); @@ -12525,7 +12525,7 @@ qemuDomainMigratePerform(virDomainPtr dom, * Consume any cookie we were able to decode though */ ret = qemuMigrationPerform(driver, dom->conn, vm, - NULL, dconnuri, uri, NULL, NULL, + NULL, dconnuri, uri, NULL, NULL, NULL, cookie, cookielen, NULL, NULL, /* No output cookies in v2 */ flags, dname, resource, false); @@ -12602,7 +12602,7 @@ qemuDomainMigrateBegin3(virDomainPtr domain, } return qemuMigrationBegin(domain->conn, vm, xmlin, dname, - cookieout, cookieoutlen, flags); + cookieout, cookieoutlen, flags, NULL); } static char * @@ -12615,11 +12615,13 @@ qemuDomainMigrateBegin3Params(virDomainPtr domain, { const char *xmlin = NULL; const char *dname = NULL; + const char **migrate_disks = NULL; + char *ret = NULL; virDomainObjPtr vm; virCheckFlags(QEMU_MIGRATION_FLAGS, NULL); if (virTypedParamsValidate(params, nparams, QEMU_MIGRATION_PARAMETERS) < 0) - return NULL; + goto cleanup; if (virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_DEST_XML, @@ -12627,18 +12629,25 @@ qemuDomainMigrateBegin3Params(virDomainPtr domain, virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_DEST_NAME, &dname) < 0) - return NULL; + goto cleanup; + + migrate_disks = virTypedParamsPickStrings(params, nparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS); if (!(vm = qemuDomObjFromDomain(domain))) - return NULL; + goto cleanup; if (virDomainMigrateBegin3ParamsEnsureACL(domain->conn, vm->def) < 0) { virDomainObjEndAPI(&vm); - return NULL; + goto cleanup; } - return qemuMigrationBegin(domain->conn, vm, xmlin, dname, - cookieout, cookieoutlen, flags); + ret = qemuMigrationBegin(domain->conn, vm, xmlin, dname, + cookieout, cookieoutlen, flags, migrate_disks); + + cleanup: + VIR_FREE(migrate_disks); + return ret; } @@ -12682,7 +12691,8 @@ qemuDomainMigratePrepare3(virConnectPtr dconn, cookiein, cookieinlen, cookieout, cookieoutlen, uri_in, uri_out, - &def, origname, NULL, flags); + &def, origname, NULL, flags, + NULL); cleanup: VIR_FREE(origname); @@ -12708,6 +12718,7 @@ qemuDomainMigratePrepare3Params(virConnectPtr dconn, const char *dname = NULL; const char *uri_in = NULL; const char *listenAddress = cfg->migrationAddress; + const char **migrate_disks = NULL; char *origname = NULL; int ret = -1; @@ -12729,6 +12740,9 @@ qemuDomainMigratePrepare3Params(virConnectPtr dconn, &listenAddress) < 0) goto cleanup; + migrate_disks = virTypedParamsPickStrings(params, nparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS); + if (flags & VIR_MIGRATE_TUNNELLED) { /* this is a logical error; we never should have gotten here with * VIR_MIGRATE_TUNNELLED set @@ -12749,9 +12763,11 @@ qemuDomainMigratePrepare3Params(virConnectPtr dconn, cookiein, cookieinlen, cookieout, cookieoutlen, uri_in, uri_out, - &def, origname, listenAddress, flags); + &def, origname, listenAddress, flags, + migrate_disks); cleanup: + VIR_FREE(migrate_disks); VIR_FREE(origname); virDomainDefFree(def); virObjectUnref(cfg); @@ -12882,7 +12898,7 @@ qemuDomainMigratePerform3(virDomainPtr dom, } return qemuMigrationPerform(driver, dom->conn, vm, xmlin, - dconnuri, uri, NULL, NULL, + dconnuri, uri, NULL, NULL, NULL, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, true); @@ -12906,7 +12922,9 @@ qemuDomainMigratePerform3Params(virDomainPtr dom, const char *uri = NULL; const char *graphicsuri = NULL; const char *listenAddress = NULL; + const char **migrate_disks = NULL; unsigned long long bandwidth = 0; + int ret = -1; virCheckFlags(QEMU_MIGRATION_FLAGS, -1); if (virTypedParamsValidate(params, nparams, QEMU_MIGRATION_PARAMETERS) < 0) @@ -12930,20 +12948,27 @@ qemuDomainMigratePerform3Params(virDomainPtr dom, virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_LISTEN_ADDRESS, &listenAddress) < 0) - return -1; + goto cleanup; + + migrate_disks = virTypedParamsPickStrings(params, nparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS); if (!(vm = qemuDomObjFromDomain(dom))) - return -1; + goto cleanup; if (virDomainMigratePerform3ParamsEnsureACL(dom->conn, vm->def) < 0) { virDomainObjEndAPI(&vm); - return -1; + goto cleanup; } - return qemuMigrationPerform(driver, dom->conn, vm, dom_xml, - dconnuri, uri, graphicsuri, listenAddress, - cookiein, cookieinlen, cookieout, cookieoutlen, - flags, dname, bandwidth, true); + ret = qemuMigrationPerform(driver, dom->conn, vm, dom_xml, + dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, + cookiein, cookieinlen, cookieout, cookieoutlen, + flags, dname, bandwidth, true); + cleanup: + VIR_FREE(migrate_disks); + return ret; } diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index eb70f29..60c09d8 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1571,12 +1571,32 @@ qemuMigrationPrecreateDisk(virConnectPtr conn, return ret; } +static int +qemuMigrateDisk(virDomainDiskDefPtr const disk, const char **migrate_disks) +{ + size_t i; + /* Check if the disk alias is in the list */ + if (disk->info.alias && migrate_disks) { + for (i = 0; migrate_disks[i]; i++) { + if (STREQ(disk->info.alias, migrate_disks[i])) + return 1; + } + return 0; + } + + /* Default is to migrate only non-shared non-readonly disks + * with source */ + return !disk->src->shared && !disk->src->readonly && + virDomainDiskGetSource(disk); +} + static int qemuMigrationPrecreateStorage(virConnectPtr conn, virQEMUDriverPtr driver ATTRIBUTE_UNUSED, virDomainObjPtr vm, - qemuMigrationCookieNBDPtr nbd) + qemuMigrationCookieNBDPtr nbd, + const char **migrate_disks) { int ret = -1; size_t i = 0; @@ -1603,9 +1623,9 @@ qemuMigrationPrecreateStorage(virConnectPtr conn, disk = vm->def->disks[indx]; diskSrcPath = virDomainDiskGetSource(disk); - if (disk->src->shared || disk->src->readonly || + /* Skip disks we don't want to migrate and already existing disks. */ + if (!qemuMigrateDisk(disk, migrate_disks) || (diskSrcPath && virFileExists(diskSrcPath))) { - /* Skip shared, read-only and already existing disks. */ continue; } @@ -1636,7 +1656,8 @@ qemuMigrationPrecreateStorage(virConnectPtr conn, static int qemuMigrationStartNBDServer(virQEMUDriverPtr driver, virDomainObjPtr vm, - const char *listenAddr) + const char *listenAddr, + const char **migrate_disks) { int ret = -1; qemuDomainObjPrivatePtr priv = vm->privateData; @@ -1647,9 +1668,8 @@ qemuMigrationStartNBDServer(virQEMUDriverPtr driver, for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk = vm->def->disks[i]; - /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + if (!qemuMigrateDisk(disk, migrate_disks)) continue; VIR_FREE(diskAlias); @@ -1728,7 +1748,8 @@ qemuMigrationStopNBDServer(virQEMUDriverPtr driver, */ static int qemuMigrationCheckDriveMirror(virQEMUDriverPtr driver, - virDomainObjPtr vm) + virDomainObjPtr vm, + const char **migrate_disks) { size_t i; int ret = 1; @@ -1736,9 +1757,8 @@ qemuMigrationCheckDriveMirror(virQEMUDriverPtr driver, for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk = vm->def->disks[i]; - /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + if (!qemuMigrateDisk(disk, migrate_disks)) continue; /* skip disks that didn't start mirroring */ @@ -1864,9 +1884,10 @@ qemuMigrationCancelDriveMirror(virQEMUDriverPtr driver, for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk = vm->def->disks[i]; - /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + /* TODO: pass migrate_disks here or loookup list of + * disks under migration using some kind of qemu monitor command */ + if (!qemuMigrateDisk(disk, NULL)) continue; /* skip disks that didn't start mirroring */ @@ -1907,7 +1928,8 @@ qemuMigrationDriveMirror(virQEMUDriverPtr driver, qemuMigrationCookiePtr mig, const char *host, unsigned long speed, - unsigned int *migrate_flags) + unsigned int *migrate_flags, + const char **migrate_disks) { qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; @@ -1937,9 +1959,8 @@ qemuMigrationDriveMirror(virQEMUDriverPtr driver, virDomainDiskDefPtr disk = vm->def->disks[i]; int mon_ret; - /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + if (!qemuMigrateDisk(disk, migrate_disks)) continue; VIR_FREE(diskAlias); @@ -1972,9 +1993,8 @@ qemuMigrationDriveMirror(virQEMUDriverPtr driver, for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk = vm->def->disks[i]; - /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + if (!qemuMigrateDisk(disk, migrate_disks)) continue; while (disk->mirrorState != VIR_DOMAIN_DISK_MIRROR_STATE_READY) { @@ -1993,7 +2013,7 @@ qemuMigrationDriveMirror(virQEMUDriverPtr driver, 500ull, NULL) < 0) goto cleanup; - if (qemuMigrationCheckDriveMirror(driver, vm) < 0) + if (qemuMigrationCheckDriveMirror(driver, vm, migrate_disks) < 0) goto cleanup; } } @@ -2115,7 +2135,7 @@ qemuMigrationIsAllowed(virQEMUDriverPtr driver, virDomainObjPtr vm, } static bool -qemuMigrationIsSafe(virDomainDefPtr def) +qemuMigrationIsSafe(virDomainDefPtr def, const char **migrate_disks) { size_t i; @@ -2125,9 +2145,7 @@ qemuMigrationIsSafe(virDomainDefPtr def) /* Our code elsewhere guarantees shared disks are either readonly (in * which case cache mode doesn't matter) or used with cache=none */ - if (src && - !disk->src->shared && - !disk->src->readonly && + if (qemuMigrateDisk(disk, migrate_disks) && disk->cachemode != VIR_DOMAIN_DISK_CACHE_DISABLE) { int rc; @@ -2698,7 +2716,8 @@ static char const char *dname, char **cookieout, int *cookieoutlen, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { char *rv = NULL; qemuMigrationCookiePtr mig = NULL; @@ -2709,9 +2728,10 @@ static char bool abort_on_error = !!(flags & VIR_MIGRATE_ABORT_ON_ERROR); VIR_DEBUG("driver=%p, vm=%p, xmlin=%s, dname=%s," - " cookieout=%p, cookieoutlen=%p, flags=%lx", + " cookieout=%p, cookieoutlen=%p, flags=%lx," + " migrate_disks=%p", driver, vm, NULLSTR(xmlin), NULLSTR(dname), - cookieout, cookieoutlen, flags); + cookieout, cookieoutlen, flags, migrate_disks); if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; @@ -2726,7 +2746,8 @@ static char if (!qemuMigrationIsAllowed(driver, vm, NULL, true, abort_on_error)) goto cleanup; - if (!(flags & VIR_MIGRATE_UNSAFE) && !qemuMigrationIsSafe(vm->def)) + if (!(flags & VIR_MIGRATE_UNSAFE) && + !qemuMigrationIsSafe(vm->def, migrate_disks)) goto cleanup; if (flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC) && @@ -2797,7 +2818,8 @@ qemuMigrationBegin(virConnectPtr conn, const char *dname, char **cookieout, int *cookieoutlen, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { virQEMUDriverPtr driver = conn->privateData; char *xml = NULL; @@ -2830,7 +2852,7 @@ qemuMigrationBegin(virConnectPtr conn, if (!(xml = qemuMigrationBeginPhase(driver, vm, xmlin, dname, cookieout, cookieoutlen, - flags))) + flags, migrate_disks))) goto endjob; if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { @@ -2898,7 +2920,8 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, unsigned short port, bool autoPort, const char *listenAddress, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { virDomainObjPtr vm = NULL; virObjectEventPtr event = NULL; @@ -3093,7 +3116,8 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, goto cleanup; } - if (qemuMigrationPrecreateStorage(dconn, driver, vm, mig->nbd) < 0) + if (qemuMigrationPrecreateStorage(dconn, driver, vm, mig->nbd, + migrate_disks) < 0) goto cleanup; if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_IN) < 0) @@ -3167,7 +3191,8 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, if (mig->nbd && flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC) && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NBD_SERVER)) { - if (qemuMigrationStartNBDServer(driver, vm, listenAddress) < 0) { + if (qemuMigrationStartNBDServer(driver, vm, listenAddress, + migrate_disks) < 0) { /* error already reported */ goto endjob; } @@ -3266,7 +3291,7 @@ qemuMigrationPrepareTunnel(virQEMUDriverPtr driver, ret = qemuMigrationPrepareAny(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, def, origname, - st, NULL, 0, false, NULL, flags); + st, NULL, 0, false, NULL, flags, NULL); return ret; } @@ -3306,7 +3331,8 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver, virDomainDefPtr *def, const char *origname, const char *listenAddress, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { unsigned short port = 0; bool autoPort = true; @@ -3318,10 +3344,12 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, dconn=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, uri_in=%s, uri_out=%p, " - "def=%p, origname=%s, listenAddress=%s, flags=%lx", + "def=%p, origname=%s, listenAddress=%s, flags=%lx, " + "migrate_disks=%p", driver, dconn, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, NULLSTR(uri_in), uri_out, - *def, origname, NULLSTR(listenAddress), flags); + *def, origname, NULLSTR(listenAddress), flags, + migrate_disks); *uri_out = NULL; @@ -3425,7 +3453,8 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver, ret = qemuMigrationPrepareAny(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, def, origname, NULL, uri ? uri->scheme : "tcp", - port, autoPort, listenAddress, flags); + port, autoPort, listenAddress, flags, + migrate_disks); cleanup: virURIFree(uri); VIR_FREE(hostname); @@ -3897,7 +3926,8 @@ qemuMigrationRun(virQEMUDriverPtr driver, unsigned long resource, qemuMigrationSpecPtr spec, virConnectPtr dconn, - const char *graphicsuri) + const char *graphicsuri, + const char **migrate_disks) { int ret = -1; unsigned int migrate_flags = QEMU_MONITOR_MIGRATE_BACKGROUND; @@ -3913,11 +3943,12 @@ qemuMigrationRun(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, resource=%lu, " - "spec=%p (dest=%d, fwd=%d), dconn=%p, graphicsuri=%s", + "spec=%p (dest=%d, fwd=%d), dconn=%p, graphicsuri=%s, " + "migrate_disks=%p", driver, vm, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, spec, spec->destType, spec->fwdType, dconn, - NULLSTR(graphicsuri)); + NULLSTR(graphicsuri), migrate_disks); if (flags & VIR_MIGRATE_NON_SHARED_DISK) { migrate_flags |= QEMU_MONITOR_MIGRATE_NON_SHARED_DISK; @@ -3953,7 +3984,8 @@ qemuMigrationRun(virQEMUDriverPtr driver, if (qemuMigrationDriveMirror(driver, vm, mig, spec->dest.host.name, migrate_speed, - &migrate_flags) < 0) { + &migrate_flags, + migrate_disks) < 0) { goto cleanup; } } else { @@ -4090,7 +4122,7 @@ qemuMigrationRun(virQEMUDriverPtr driver, /* Confirm state of drive mirrors */ if (mig->nbd) { - if (qemuMigrationCheckDriveMirror(driver, vm) != 1) { + if (qemuMigrationCheckDriveMirror(driver, vm, migrate_disks) != 1) { ret = -1; goto cancel; } @@ -4187,7 +4219,8 @@ static int doNativeMigrate(virQEMUDriverPtr driver, unsigned long flags, unsigned long resource, virConnectPtr dconn, - const char *graphicsuri) + const char *graphicsuri, + const char **migrate_disks) { qemuDomainObjPrivatePtr priv = vm->privateData; virURIPtr uribits = NULL; @@ -4196,10 +4229,10 @@ static int doNativeMigrate(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, vm=%p, uri=%s, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, resource=%lu, " - "graphicsuri=%s", + "graphicsuri=%s, migrate_disks=%p", driver, vm, uri, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, - NULLSTR(graphicsuri)); + NULLSTR(graphicsuri), migrate_disks); if (!(uribits = qemuMigrationParseURI(uri, NULL))) return -1; @@ -4238,7 +4271,7 @@ static int doNativeMigrate(virQEMUDriverPtr driver, ret = qemuMigrationRun(driver, vm, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, &spec, dconn, - graphicsuri); + graphicsuri, migrate_disks); if (spec.destType == MIGRATION_DEST_FD) VIR_FORCE_CLOSE(spec.dest.fd.qemu); @@ -4260,7 +4293,8 @@ static int doTunnelMigrate(virQEMUDriverPtr driver, unsigned long flags, unsigned long resource, virConnectPtr dconn, - const char *graphicsuri) + const char *graphicsuri, + const char **migrate_disks) { qemuDomainObjPrivatePtr priv = vm->privateData; virNetSocketPtr sock = NULL; @@ -4270,10 +4304,10 @@ static int doTunnelMigrate(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, vm=%p, st=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, resource=%lu, " - "graphicsuri=%s", + "graphicsuri=%s, migrate_disks=%p", driver, vm, st, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, - NULLSTR(graphicsuri)); + NULLSTR(graphicsuri), migrate_disks); if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATE_QEMU_FD) && !virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATE_QEMU_UNIX) && @@ -4326,7 +4360,7 @@ static int doTunnelMigrate(virQEMUDriverPtr driver, ret = qemuMigrationRun(driver, vm, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, &spec, dconn, - graphicsuri); + graphicsuri, migrate_disks); cleanup: if (spec.destType == MIGRATION_DEST_FD) { @@ -4436,12 +4470,12 @@ static int doPeer2PeerMigrate2(virQEMUDriverPtr driver, if (flags & VIR_MIGRATE_TUNNELLED) ret = doTunnelMigrate(driver, vm, st, NULL, 0, NULL, NULL, - flags, resource, dconn, NULL); + flags, resource, dconn, NULL, NULL); else ret = doNativeMigrate(driver, vm, uri_out, cookie, cookielen, NULL, NULL, /* No out cookie with v2 migration */ - flags, resource, dconn, NULL); + flags, resource, dconn, NULL, NULL); /* Perform failed. Make sure Finish doesn't overwrite the error */ if (ret < 0) @@ -4503,6 +4537,7 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, unsigned long long bandwidth, bool useParams, unsigned long flags) @@ -4525,10 +4560,11 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, sconn=%p, dconn=%p, dconnuri=%s, vm=%p, xmlin=%s, " "dname=%s, uri=%s, graphicsuri=%s, listenAddress=%s, " - "bandwidth=%llu, useParams=%d, flags=%lx", + "migrate_disks=%p, bandwidth=%llu, useParams=%d, flags=%lx", driver, sconn, dconn, NULLSTR(dconnuri), vm, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), NULLSTR(graphicsuri), - NULLSTR(listenAddress), bandwidth, useParams, flags); + NULLSTR(listenAddress), migrate_disks, bandwidth, useParams, + flags); /* Unlike the virDomainMigrateVersion3 counterpart, we don't need * to worry about auto-setting the VIR_MIGRATE_CHANGE_PROTECTION @@ -4536,7 +4572,8 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, * a single job. */ dom_xml = qemuMigrationBeginPhase(driver, vm, xmlin, dname, - &cookieout, &cookieoutlen, flags); + &cookieout, &cookieoutlen, flags, + migrate_disks); if (!dom_xml) goto cleanup; @@ -4571,6 +4608,11 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, VIR_MIGRATE_PARAM_LISTEN_ADDRESS, listenAddress) < 0) goto cleanup; + if (migrate_disks && + virTypedParamsPackStrings(¶ms, &nparams, &maxparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + migrate_disks) < 0) + goto cleanup; } if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) @@ -4655,12 +4697,14 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, ret = doTunnelMigrate(driver, vm, st, cookiein, cookieinlen, &cookieout, &cookieoutlen, - flags, bandwidth, dconn, graphicsuri); + flags, bandwidth, dconn, graphicsuri, + migrate_disks); } else { ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, &cookieout, &cookieoutlen, - flags, bandwidth, dconn, graphicsuri); + flags, bandwidth, dconn, graphicsuri, + migrate_disks); } /* Perform failed. Make sure Finish doesn't overwrite the error */ @@ -4794,6 +4838,7 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, unsigned long flags, const char *dname, unsigned long resource, @@ -4807,12 +4852,12 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); bool useParams; - VIR_DEBUG("driver=%p, sconn=%p, vm=%p, xmlin=%s, dconnuri=%s, " - "uri=%s, graphicsuri=%s, listenAddress=%s, flags=%lx, " - "dname=%s, resource=%lu", + VIR_DEBUG("driver=%p, sconn=%p, vm=%p, xmlin=%s, dconnuri=%s, uri=%s, " + "graphicsuri=%s, listenAddress=%s, migrate_disks=%p, " + "flags=%lx, dname=%s, resource=%lu", driver, sconn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), NULLSTR(uri), NULLSTR(graphicsuri), NULLSTR(listenAddress), - flags, NULLSTR(dname), resource); + migrate_disks, flags, NULLSTR(dname), resource); /* the order of operations is important here; we make sure the * destination side is completely setup before we touch the source @@ -4857,7 +4902,7 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, /* Only xmlin, dname, uri, and bandwidth parameters can be used with * old-style APIs. */ - if (!useParams && graphicsuri) { + if (!useParams && (graphicsuri || migrate_disks)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Migration APIs with extensible parameters are not " "supported but extended parameters were passed")); @@ -4888,7 +4933,7 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, if (*v3proto) { ret = doPeer2PeerMigrate3(driver, sconn, dconn, dconnuri, vm, xmlin, dname, uri, graphicsuri, listenAddress, - resource, useParams, flags); + migrate_disks, resource, useParams, flags); } else { ret = doPeer2PeerMigrate2(driver, sconn, dconn, vm, dconnuri, flags, dname, resource); @@ -4922,6 +4967,7 @@ qemuMigrationPerformJob(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, @@ -4949,7 +4995,8 @@ qemuMigrationPerformJob(virQEMUDriverPtr driver, if (!qemuMigrationIsAllowed(driver, vm, NULL, true, abort_on_error)) goto endjob; - if (!(flags & VIR_MIGRATE_UNSAFE) && !qemuMigrationIsSafe(vm->def)) + if (!(flags & VIR_MIGRATE_UNSAFE) && + !qemuMigrationIsSafe(vm->def, migrate_disks)) goto endjob; qemuMigrationStoreDomainState(vm); @@ -4957,12 +5004,13 @@ qemuMigrationPerformJob(virQEMUDriverPtr driver, if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { ret = doPeer2PeerMigrate(driver, conn, vm, xmlin, dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, flags, dname, resource, &v3proto); } else { qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PERFORM2); ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, cookieout, cookieoutlen, - flags, resource, NULL, NULL); + flags, resource, NULL, NULL, NULL); } if (ret < 0) goto endjob; @@ -5021,6 +5069,7 @@ qemuMigrationPerformPhase(virQEMUDriverPtr driver, virDomainObjPtr vm, const char *uri, const char *graphicsuri, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, @@ -5045,7 +5094,7 @@ qemuMigrationPerformPhase(virQEMUDriverPtr driver, ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, cookieout, cookieoutlen, - flags, resource, NULL, graphicsuri); + flags, resource, NULL, graphicsuri, migrate_disks); if (ret < 0) { if (qemuMigrationRestoreDomainState(conn, vm)) { @@ -5086,6 +5135,7 @@ qemuMigrationPerform(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, @@ -5096,13 +5146,13 @@ qemuMigrationPerform(virQEMUDriverPtr driver, bool v3proto) { VIR_DEBUG("driver=%p, conn=%p, vm=%p, xmlin=%s, dconnuri=%s, " - "uri=%s, graphicsuri=%s, listenAddress=%s" + "uri=%s, graphicsuri=%s, listenAddress=%s, migrate_disks=%p, " "cookiein=%s, cookieinlen=%d, cookieout=%p, cookieoutlen=%p, " "flags=%lx, dname=%s, resource=%lu, v3proto=%d", driver, conn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), NULLSTR(uri), NULLSTR(graphicsuri), NULLSTR(listenAddress), - NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, - flags, NULLSTR(dname), resource, v3proto); + migrate_disks, NULLSTR(cookiein), cookieinlen, cookieout, + cookieoutlen, flags, NULLSTR(dname), resource, v3proto); if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { if (cookieinlen) { @@ -5113,6 +5163,7 @@ qemuMigrationPerform(virQEMUDriverPtr driver, return qemuMigrationPerformJob(driver, conn, vm, xmlin, dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, v3proto); @@ -5125,13 +5176,14 @@ qemuMigrationPerform(virQEMUDriverPtr driver, if (v3proto) { return qemuMigrationPerformPhase(driver, conn, vm, uri, - graphicsuri, + graphicsuri, migrate_disks, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource); } else { return qemuMigrationPerformJob(driver, conn, vm, xmlin, dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, v3proto); diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index 1726455..42d64e9 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -44,13 +44,15 @@ VIR_MIGRATE_RDMA_PIN_ALL) /* All supported migration parameters and their types. */ -# define QEMU_MIGRATION_PARAMETERS \ - VIR_MIGRATE_PARAM_URI, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_DEST_NAME, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_DEST_XML, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_BANDWIDTH, VIR_TYPED_PARAM_ULLONG, \ - VIR_MIGRATE_PARAM_GRAPHICS_URI, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_LISTEN_ADDRESS, VIR_TYPED_PARAM_STRING, \ +# define QEMU_MIGRATION_PARAMETERS \ + VIR_MIGRATE_PARAM_URI, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_DEST_NAME, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_DEST_XML, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_BANDWIDTH, VIR_TYPED_PARAM_ULLONG, \ + VIR_MIGRATE_PARAM_GRAPHICS_URI, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_LISTEN_ADDRESS, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_MIGRATE_DISKS, VIR_TYPED_PARAM_STRING | \ + VIR_TYPED_PARAM_MULTIPLE, \ NULL @@ -99,7 +101,8 @@ char *qemuMigrationBegin(virConnectPtr conn, const char *dname, char **cookieout, int *cookieoutlen, - unsigned long flags); + unsigned long flags, + const char **migrate_disks); virDomainDefPtr qemuMigrationPrepareDef(virQEMUDriverPtr driver, const char *dom_xml, @@ -128,7 +131,8 @@ int qemuMigrationPrepareDirect(virQEMUDriverPtr driver, virDomainDefPtr *def, const char *origname, const char *listenAddress, - unsigned long flags); + unsigned long flags, + const char **migrate_disks); int qemuMigrationPerform(virQEMUDriverPtr driver, virConnectPtr conn, @@ -138,6 +142,7 @@ int qemuMigrationPerform(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, -- 1.9.1

On Tue, May 12, 2015 at 15:07:31 +0300, Pavel Boldin wrote:
Implement a `migrate_disks' parameters for the QEMU driver. This multi- value parameter can be used to explicitly specify what block devices are to be migrated using the NBD server. Tunnelled migration using NBD is to be done.
...
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index eb70f29..60c09d8 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c ... @@ -1864,9 +1884,10 @@ qemuMigrationCancelDriveMirror(virQEMUDriverPtr driver, for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk = vm->def->disks[i];
- /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + /* TODO: pass migrate_disks here or loookup list of + * disks under migration using some kind of qemu monitor command */ + if (!qemuMigrateDisk(disk, NULL))
Some of the code in this patch including this TODO will not be necessary if https://www.redhat.com/archives/libvir-list/2015-May/msg00352.html is accepted... Jirka

Add `virsh migrate' option `--migratedisks' that allows CLI user to explicitly specify block devices to migrate. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- tools/virsh-domain.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 4b627e1..4f43a25 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -9793,6 +9793,10 @@ static const vshCmdOptDef opts_migrate[] = { .type = VSH_OT_STRING, .help = N_("filename containing updated XML for the target") }, + {.name = "migratedisks", + .type = VSH_OT_STRING, + .help = N_("comma separated list of disks to be migrated") + }, {.name = NULL} }; @@ -9852,6 +9856,45 @@ doMigrate(void *opaque) VIR_MIGRATE_PARAM_DEST_NAME, opt) < 0) goto save_error; + if (vshCommandOptStringReq(ctl, cmd, "migratedisks", &opt) < 0) + goto out; + if (opt) { + const char **val = NULL; + char *tok, *saveptr = NULL, *opt_copy = NULL, *optp; + size_t max = 0, n = 0; + + if (VIR_STRDUP(opt_copy, opt) < 0) + goto save_error; + + optp = opt_copy; + do { + tok = strtok_r(optp, ",", &saveptr); + optp = NULL; + + if (VIR_RESIZE_N(val, max, n, 1) < 0) { + VIR_FREE(opt_copy); + VIR_FREE(val); + goto save_error; + } + + val[n] = tok; + n++; + } while (tok != NULL); + + if (virTypedParamsPackStrings(¶ms, + &nparams, + &maxparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + val) < 0) { + VIR_FREE(opt_copy); + VIR_FREE(val); + goto save_error; + } + + VIR_FREE(opt_copy); + VIR_FREE(val); + } + if (vshCommandOptStringReq(ctl, cmd, "xml", &opt) < 0) goto out; if (opt) { -- 1.9.1

On 12.05.2015 14:07, Pavel Boldin wrote:
Add `virsh migrate' option `--migratedisks' that allows CLI user to explicitly specify block devices to migrate.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- tools/virsh-domain.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+)
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 4b627e1..4f43a25 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -9793,6 +9793,10 @@ static const vshCmdOptDef opts_migrate[] = { .type = VSH_OT_STRING, .help = N_("filename containing updated XML for the target") }, + {.name = "migratedisks", + .type = VSH_OT_STRING, + .help = N_("comma separated list of disks to be migrated") + },
This needs to be documented in the manpage too.
{.name = NULL} };
@@ -9852,6 +9856,45 @@ doMigrate(void *opaque) VIR_MIGRATE_PARAM_DEST_NAME, opt) < 0) goto save_error;
+ if (vshCommandOptStringReq(ctl, cmd, "migratedisks", &opt) < 0) + goto out; + if (opt) { + const char **val = NULL; + char *tok, *saveptr = NULL, *opt_copy = NULL, *optp; + size_t max = 0, n = 0; + + if (VIR_STRDUP(opt_copy, opt) < 0) + goto save_error; + + optp = opt_copy; + do { + tok = strtok_r(optp, ",", &saveptr); + optp = NULL; + + if (VIR_RESIZE_N(val, max, n, 1) < 0) { + VIR_FREE(opt_copy); + VIR_FREE(val); + goto save_error; + } + + val[n] = tok; + n++; + } while (tok != NULL);
Consider virStringSplit().
+ + if (virTypedParamsPackStrings(¶ms, + &nparams, + &maxparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + val) < 0) { + VIR_FREE(opt_copy); + VIR_FREE(val); + goto save_error; + } + + VIR_FREE(opt_copy); + VIR_FREE(val); + } + if (vshCommandOptStringReq(ctl, cmd, "xml", &opt) < 0) goto out; if (opt) {
Otherwise looking good. Michal

On 12.05.2015 14:07, Pavel Boldin wrote:
The patchset represented in the mail thread implements the selective block device migration for the QEMU driver. This closes the referenced bug [1].
First the supplementary API implemented allowing for multiple key-values pair in the virTypedParameter arrays. This is used to pass the list of block devices to migrate in the following patches. Unit tests for this are added as well. This is the subject of the first three patches.
Fourth patch is adding the necessary parameter `migrate_disks' and passes it through the QEMU driver call stack. The introduced `qemuMigrateBlockDevice' function is then checks that the disk is to be migrated because it is in the list. If there is no list specified then the legacy check is used: the device is not shared, not readonly and has a source.
Fifth and the last patch is adding the necessary code to the `virsh' utility making it able for user to specify a comma-separated list of the block device names that are to be migrated.
The implemented Python bindings patch is to be presented.
[1] https://bugzilla.redhat.com/show_bug.cgi?id=1203032
Pavel Boldin (5): util: multi-value virTypedParameter util: virTypedParamsPick* multikey API util: add virTypedParamsPackStrings qemu: migration: selective block device migration virsh: selective block device migration
include/libvirt/libvirt-domain.h | 9 ++ include/libvirt/libvirt-host.h | 24 ++++ src/libvirt_public.syms | 3 + src/qemu/qemu_driver.c | 63 ++++++--- src/qemu/qemu_migration.c | 197 ++++++++++++++++---------- src/qemu/qemu_migration.h | 23 ++-- src/util/virtypedparam.c | 290 +++++++++++++++++++++++++++++++-------- tests/Makefile.am | 6 + tests/virtypedparamtest.c | 289 ++++++++++++++++++++++++++++++++++++++ tools/virsh-domain.c | 44 ++++++ 10 files changed, 788 insertions(+), 160 deletions(-) create mode 100644 tests/virtypedparamtest.c
Hey, I'm glad to see things moving. Unfortunately, I'm afraid we need v2. The overall feeling is good though. Michal

The patchset represented in the mail thread implements the selective block device migration for the QEMU driver. This closes the referenced bug [1]. First the supplementary API implemented allowing for multiple key-values pair in the virTypedParameter arrays. This is used to pass the list of block devices to migrate in the following patches. Unit tests for this are added as well. This is the subject of the first four patches. Fifth patch is adding the necessary parameter `migrate_disks' and passes it through the QEMU driver call stack. The introduced `qemuMigrateDisk' function is then checks that the disk is to be migrated because it is in the list. If there is no list specified then the legacy check is used: the device is migrate if it is not shared, not readonly and has a source. Sixth and the last patch is adding the necessary code to the `virsh' utility making it able for user to specify a comma-separated list of the block device names that are to be migrated. The implemented Python bindings patch is to be presented. Changes in v2: * Addressed review comments * Removed all the duplicates check in the commit 'multi-value parameters in virTypedParamsAdd*' * reimplemented virTypedParamsPick as virTypedParamsFilter * renamed virTypedParamsPackStrings to virTypedParamsAddStringList [1] https://bugzilla.redhat.com/show_bug.cgi?id=1203032 Pavel Boldin (6): util: multi-value virTypedParameter util: multi-value parameters in virTypedParamsAdd* util: virTypedParams{Filter,PickStrings} util: add virTypedParamsAddStringList qemu: migration: selective block device migration virsh: selective block device migration include/libvirt/libvirt-domain.h | 9 ++ include/libvirt/libvirt-host.h | 16 +++ src/libvirt_public.syms | 7 + src/qemu/qemu_driver.c | 66 ++++++--- src/qemu/qemu_migration.c | 174 +++++++++++++++-------- src/qemu/qemu_migration.h | 23 ++-- src/util/virtypedparam.c | 263 ++++++++++++++++++++++++++++------- src/util/virtypedparam.h | 10 ++ tests/Makefile.am | 6 + tests/virtypedparamtest.c | 291 +++++++++++++++++++++++++++++++++++++++ tools/virsh-domain.c | 23 ++++ tools/virsh.pod | 5 +- 12 files changed, 750 insertions(+), 143 deletions(-) create mode 100644 tests/virtypedparamtest.c -- 1.9.1

The `virTypedParamsValidate' function now can be instructed to allow multiple entries for some of the keys. For this flag the type with the `VIR_TYPED_PARAM_MULTIPLE' flag. Add unit tests for this new behaviour. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- src/util/virtypedparam.c | 108 +++++++++++++++++++----------- src/util/virtypedparam.h | 10 +++ tests/Makefile.am | 6 ++ tests/virtypedparamtest.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+), 39 deletions(-) create mode 100644 tests/virtypedparamtest.c diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index de2d447..43e49ca 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -47,11 +47,18 @@ VIR_ENUM_IMPL(virTypedParameter, VIR_TYPED_PARAM_LAST, * internal utility functions (those in libvirt_private.syms) may * report errors that the caller will dispatch. */ +static int virTypedParamsSortName(const void *left, const void *right) +{ + const virTypedParameter *param_left = left, *param_right = right; + return strcmp(param_left->field, param_right->field); +} + /* Validate that PARAMS contains only recognized parameter names with - * correct types, and with no duplicates. Pass in as many name/type - * pairs as appropriate, and pass NULL to end the list of accepted - * parameters. Return 0 on success, -1 on failure with error message - * already issued. */ + * correct types, and with no duplicates except for parameters + * specified with VIR_TYPED_PARAM_MULTIPLE flag in type. + * Pass in as many name/type pairs as appropriate, and pass NULL to end + * the list of accepted parameters. Return 0 on success, -1 on failure + * with error message already issued. */ int virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) { @@ -60,60 +67,83 @@ virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) size_t i, j; const char *name; int type; + size_t nkeys = 0, nkeysmax = 0; + virTypedParameterPtr sorted = NULL, keys = NULL; va_start(ap, nparams); - /* Yes, this is quadratic, but since we reject duplicates and - * unknowns, it is constrained by the number of var-args passed - * in, which is expected to be small enough to not be - * noticeable. */ - for (i = 0; i < nparams; i++) { - va_end(ap); - va_start(ap, nparams); + if (VIR_ALLOC_N(sorted, nparams) < 0) + goto cleanup; - name = va_arg(ap, const char *); - while (name) { - type = va_arg(ap, int); - if (STREQ(params[i].field, name)) { - if (params[i].type != type) { - const char *badtype; - - badtype = virTypedParameterTypeToString(params[i].type); - if (!badtype) - badtype = virTypedParameterTypeToString(0); - virReportError(VIR_ERR_INVALID_ARG, - _("invalid type '%s' for parameter '%s', " - "expected '%s'"), - badtype, params[i].field, - virTypedParameterTypeToString(type)); - } - break; - } - name = va_arg(ap, const char *); - } - if (!name) { - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, - _("parameter '%s' not supported"), - params[i].field); + /* Here we intentionally don't copy values */ + memcpy(sorted, params, sizeof(*params) * nparams); + qsort(sorted, nparams, sizeof(*sorted), virTypedParamsSortName); + + name = va_arg(ap, const char *); + while (name) { + type = va_arg(ap, int); + if (VIR_RESIZE_N(keys, nkeysmax, nkeys, 1) < 0) + goto cleanup; + + if (virStrcpyStatic(keys[nkeys].field, name) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Field name '%s' too long"), name); goto cleanup; } - for (j = 0; j < i; j++) { - if (STREQ(params[i].field, params[j].field)) { + + keys[nkeys].type = type & ~VIR_TYPED_PARAM_MULTIPLE; + /* Value is not used anyway */ + keys[nkeys].value.i = type & VIR_TYPED_PARAM_MULTIPLE; + + nkeys++; + name = va_arg(ap, const char *); + } + + qsort(keys, nkeys, sizeof(*keys), virTypedParamsSortName); + + for (i = 0, j = 0; i < nparams && j < nkeys;) { + if (STRNEQ(sorted[i].field, keys[j].field)) { + j++; + } else { + if (i > j && !(keys[j].value.i & VIR_TYPED_PARAM_MULTIPLE)) { virReportError(VIR_ERR_INVALID_ARG, _("parameter '%s' occurs multiple times"), - params[i].field); + sorted[i].field); + goto cleanup; + } + if (sorted[i].type != keys[j].type) { + const char *badtype; + + badtype = virTypedParameterTypeToString(sorted[i].type); + if (!badtype) + badtype = virTypedParameterTypeToString(0); + virReportError(VIR_ERR_INVALID_ARG, + _("invalid type '%s' for parameter '%s', " + "expected '%s'"), + badtype, sorted[i].field, + virTypedParameterTypeToString(keys[j].type)); goto cleanup; } + i++; } } + if (j == nkeys && i != nparams) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("parameter '%s' not supported"), + sorted[i].field); + goto cleanup; + } + ret = 0; cleanup: va_end(ap); + VIR_FREE(sorted); + VIR_FREE(keys); return ret; - } + /* Check if params contains only specified parameter names. Return true if * only specified names are present in params, false if params contains any * unspecified parameter name. */ diff --git a/src/util/virtypedparam.h b/src/util/virtypedparam.h index 0c18504..9f2d08c 100644 --- a/src/util/virtypedparam.h +++ b/src/util/virtypedparam.h @@ -26,6 +26,16 @@ # include "internal.h" # include "virutil.h" +/** + * VIR_TYPED_PARAM_MULTIPLE: + * + * Flag indiciating that the params has multiple occurences of the parameter. + * Only used as a flag for @type argument of the virTypedParamsValidate. + */ +# define VIR_TYPED_PARAM_MULTIPLE (1 << 31) + +verify(!(VIR_TYPED_PARAM_LAST & VIR_TYPED_PARAM_MULTIPLE)); + int virTypedParamsValidate(virTypedParameterPtr params, int nparams, /* const char *name, int type ... */ ...) ATTRIBUTE_SENTINEL ATTRIBUTE_RETURN_CHECK; diff --git a/tests/Makefile.am b/tests/Makefile.am index 8e2dbec..9efb441 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -182,6 +182,7 @@ test_programs = virshtest sockettest \ virhostdevtest \ vircaps2xmltest \ virnetdevtest \ + virtypedparamtest \ $(NULL) if WITH_REMOTE @@ -1225,6 +1226,11 @@ objecteventtest_SOURCES = \ testutils.c testutils.h objecteventtest_LDADD = $(LDADDS) +virtypedparamtest_SOURCES = \ + virtypedparamtest.c testutils.h testutils.c +virtypedparamtest_LDADD = $(LDADDS) + + if WITH_LINUX fchosttest_SOURCES = \ fchosttest.c testutils.h testutils.c diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c new file mode 100644 index 0000000..95e22a7 --- /dev/null +++ b/tests/virtypedparamtest.c @@ -0,0 +1,167 @@ +/* + * virtypedparamtest.c: Test typed param functions + * + * Copyright (C) 2015 Mirantis, 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include <stdio.h> +#include <virtypedparam.h> + +#include "testutils.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +typedef struct _TypedParameterTest { + /* Test name for logging */ + const char *name; + /* Flags of the "foobar" parameter check */ + int foobar_flags; + /* Parameters to validate */ + virTypedParameterPtr params; + /* Amount of parameters */ + int nparams; + + /* Expected error code */ + int expected_errcode; + /* Expected error message */ + const char *expected_errmessage; +} TypedParameterTest; + +static int +testTypedParamsValidate(const void *opaque) +{ + int rv; + TypedParameterTest *test = (TypedParameterTest *)opaque; + virErrorPtr errptr; + + rv = virTypedParamsValidate( + test->params, test->nparams, + "foobar", VIR_TYPED_PARAM_STRING | test->foobar_flags, + "foo", VIR_TYPED_PARAM_INT, + "bar", VIR_TYPED_PARAM_UINT, + NULL); + + if (test->expected_errcode) { + errptr = virGetLastError(); + + rv = (errptr == NULL) || ((rv < 0) && + !(errptr->code == test->expected_errcode)); + if (errptr && test->expected_errmessage) { + rv = STRNEQ(test->expected_errmessage, errptr->message); + if (rv) + printf("%s\n", errptr->message); + } + } + + return rv; +} + +#define PARAMS_ARRAY(...) ((virTypedParameter[]){ __VA_ARGS__ }) +#define PARAMS_SIZE(...) ARRAY_CARDINALITY(PARAMS_ARRAY(__VA_ARGS__)) + +#define PARAMS(...) \ + .params = PARAMS_ARRAY(__VA_ARGS__), \ + .nparams = PARAMS_SIZE(__VA_ARGS__), + +static int +testTypedParamsValidator(void) +{ + size_t i; + int rv = 0; + + TypedParameterTest test[] = { + { + .name = "Invalid arg type", + .foobar_flags = 0, + PARAMS({ .field = "foobar", .type = VIR_TYPED_PARAM_INT }) + .expected_errcode = VIR_ERR_INVALID_ARG, + .expected_errmessage = + "invalid argument: invalid type 'int' for parameter " + "'foobar', expected 'string'" + }, + { + .name = "Extra arg", + .foobar_flags = 0, + PARAMS({ .field = "f", .type = VIR_TYPED_PARAM_INT }) + .expected_errcode = VIR_ERR_INVALID_ARG, + .expected_errmessage = + "argument unsupported: parameter 'f' not supported" + }, + { + .name = "Valid parameters", + .foobar_flags = 0, + PARAMS( + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + ) + .expected_errcode = 0, .expected_errmessage = NULL, + }, + { + .name = "Duplicates incorrect", + .foobar_flags = 0, + PARAMS( + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + ) + .expected_errcode = VIR_ERR_INVALID_ARG, + .expected_errmessage = + "invalid argument: parameter 'foobar' occurs multiple times" + }, + { + .name = "Duplicates OK for marked", + .foobar_flags = VIR_TYPED_PARAM_MULTIPLE, + PARAMS( + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + ) + .expected_errcode = 0, .expected_errmessage = NULL, + }, + { + .name = NULL + } + }; + + for (i = 0; test[i].name; ++i) { + if (virtTestRun(test[i].name, testTypedParamsValidate, &test[i]) < 0) + rv = -1; + } + + return rv; +} + +static int +mymain(void) +{ + int rv = 0; + + if (testTypedParamsValidator() < 0) + rv = -1; + + if (rv < 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + +VIRT_TEST_MAIN(mymain) -- 1.9.1

On 21.05.2015 13:07, Pavel Boldin wrote:
The `virTypedParamsValidate' function now can be instructed to allow multiple entries for some of the keys. For this flag the type with the `VIR_TYPED_PARAM_MULTIPLE' flag.
Add unit tests for this new behaviour.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- src/util/virtypedparam.c | 108 +++++++++++++++++++----------- src/util/virtypedparam.h | 10 +++ tests/Makefile.am | 6 ++ tests/virtypedparamtest.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+), 39 deletions(-) create mode 100644 tests/virtypedparamtest.c
diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index de2d447..43e49ca 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -47,11 +47,18 @@ VIR_ENUM_IMPL(virTypedParameter, VIR_TYPED_PARAM_LAST, * internal utility functions (those in libvirt_private.syms) may * report errors that the caller will dispatch. */
+static int virTypedParamsSortName(const void *left, const void *right) +{ + const virTypedParameter *param_left = left, *param_right = right; + return strcmp(param_left->field, param_right->field); +} + /* Validate that PARAMS contains only recognized parameter names with - * correct types, and with no duplicates. Pass in as many name/type - * pairs as appropriate, and pass NULL to end the list of accepted - * parameters. Return 0 on success, -1 on failure with error message - * already issued. */ + * correct types, and with no duplicates except for parameters + * specified with VIR_TYPED_PARAM_MULTIPLE flag in type. + * Pass in as many name/type pairs as appropriate, and pass NULL to end + * the list of accepted parameters. Return 0 on success, -1 on failure + * with error message already issued. */ int virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) { @@ -60,60 +67,83 @@ virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) size_t i, j; const char *name; int type; + size_t nkeys = 0, nkeysmax = 0;
s/nkeysmax/nkeysalloc/ because the variable holds count of items allocated.
+ virTypedParameterPtr sorted = NULL, keys = NULL;
va_start(ap, nparams);
- /* Yes, this is quadratic, but since we reject duplicates and - * unknowns, it is constrained by the number of var-args passed - * in, which is expected to be small enough to not be - * noticeable. */ - for (i = 0; i < nparams; i++) { - va_end(ap); - va_start(ap, nparams); + if (VIR_ALLOC_N(sorted, nparams) < 0) + goto cleanup;
- name = va_arg(ap, const char *); - while (name) { - type = va_arg(ap, int); - if (STREQ(params[i].field, name)) { - if (params[i].type != type) { - const char *badtype; - - badtype = virTypedParameterTypeToString(params[i].type); - if (!badtype) - badtype = virTypedParameterTypeToString(0); - virReportError(VIR_ERR_INVALID_ARG, - _("invalid type '%s' for parameter '%s', " - "expected '%s'"), - badtype, params[i].field, - virTypedParameterTypeToString(type)); - } - break; - } - name = va_arg(ap, const char *); - } - if (!name) { - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, - _("parameter '%s' not supported"), - params[i].field); + /* Here we intentionally don't copy values */ + memcpy(sorted, params, sizeof(*params) * nparams); + qsort(sorted, nparams, sizeof(*sorted), virTypedParamsSortName); + + name = va_arg(ap, const char *); + while (name) { + type = va_arg(ap, int); + if (VIR_RESIZE_N(keys, nkeysmax, nkeys, 1) < 0) + goto cleanup; + + if (virStrcpyStatic(keys[nkeys].field, name) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Field name '%s' too long"), name); goto cleanup; } - for (j = 0; j < i; j++) { - if (STREQ(params[i].field, params[j].field)) { + + keys[nkeys].type = type & ~VIR_TYPED_PARAM_MULTIPLE; + /* Value is not used anyway */ + keys[nkeys].value.i = type & VIR_TYPED_PARAM_MULTIPLE; + + nkeys++; + name = va_arg(ap, const char *); + } + + qsort(keys, nkeys, sizeof(*keys), virTypedParamsSortName); + + for (i = 0, j = 0; i < nparams && j < nkeys;) { + if (STRNEQ(sorted[i].field, keys[j].field)) { + j++; + } else { + if (i > j && !(keys[j].value.i & VIR_TYPED_PARAM_MULTIPLE)) { virReportError(VIR_ERR_INVALID_ARG, _("parameter '%s' occurs multiple times"), - params[i].field); + sorted[i].field); + goto cleanup; + } + if (sorted[i].type != keys[j].type) { + const char *badtype; + + badtype = virTypedParameterTypeToString(sorted[i].type); + if (!badtype) + badtype = virTypedParameterTypeToString(0); + virReportError(VIR_ERR_INVALID_ARG, + _("invalid type '%s' for parameter '%s', " + "expected '%s'"), + badtype, sorted[i].field, + virTypedParameterTypeToString(keys[j].type)); goto cleanup; } + i++; } }
+ if (j == nkeys && i != nparams) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("parameter '%s' not supported"), + sorted[i].field); + goto cleanup; + } + ret = 0; cleanup: va_end(ap); + VIR_FREE(sorted); + VIR_FREE(keys); return ret; - }
ACK Michal

Allow multi-value parameters to be build using virTypedParamsAdd* functions by removing check for duplicates. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- src/util/virtypedparam.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index 43e49ca..ec20b74 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -748,14 +748,6 @@ virTypedParamsGetString(virTypedParameterPtr params, } -#define VIR_TYPED_PARAM_CHECK() \ - do { if (virTypedParamsGet(*params, n, name)) { \ - virReportError(VIR_ERR_INVALID_ARG, \ - _("Parameter '%s' is already set"), name); \ - goto error; \ - } } while (0) - - /** * virTypedParamsAddInt: * @params: pointer to the array of typed parameters @@ -786,7 +778,6 @@ virTypedParamsAddInt(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; @@ -834,7 +825,6 @@ virTypedParamsAddUInt(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; @@ -882,7 +872,6 @@ virTypedParamsAddLLong(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; @@ -930,7 +919,6 @@ virTypedParamsAddULLong(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; @@ -978,7 +966,6 @@ virTypedParamsAddDouble(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; @@ -1026,7 +1013,6 @@ virTypedParamsAddBoolean(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; @@ -1077,7 +1063,6 @@ virTypedParamsAddString(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; @@ -1135,7 +1120,6 @@ virTypedParamsAddFromString(virTypedParameterPtr *params, virResetLastError(); - VIR_TYPED_PARAM_CHECK(); if (VIR_RESIZE_N(*params, max, n, 1) < 0) goto error; *maxparams = max; -- 1.9.1

On 21.05.2015 13:07, Pavel Boldin wrote:
Allow multi-value parameters to be build using virTypedParamsAdd* functions by removing check for duplicates.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- src/util/virtypedparam.c | 16 ---------------- 1 file changed, 16 deletions(-)
ACK Michal

Add multikey API: * virTypedParamsFilter that returns all the parameters with specified name. * virTypedParamsPickStrings that returns a NULL-terminated `const char**' list with all the values for specified name and string type. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 10 ++++ src/libvirt_public.syms | 6 +++ src/util/virtypedparam.c | 107 +++++++++++++++++++++++++++++++++++++++++ tests/virtypedparamtest.c | 96 ++++++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+) diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index 070550b..36267fc 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -249,6 +249,11 @@ virTypedParamsGet (virTypedParameterPtr params, int nparams, const char *name); int +virTypedParamsFilter (virTypedParameterPtr params, + int nparams, + const char *name, + virTypedParameterPtr **ret); +int virTypedParamsGetInt (virTypedParameterPtr params, int nparams, const char *name, @@ -283,6 +288,11 @@ virTypedParamsGetString (virTypedParameterPtr params, int nparams, const char *name, const char **value); +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, + const char *name, + size_t *picked); int virTypedParamsAddInt (virTypedParameterPtr *params, int *nparams, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index ef3d2f0..8fc8c42 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -710,4 +710,10 @@ LIBVIRT_1.2.15 { virDomainDelIOThread; } LIBVIRT_1.2.14; +LIBVIRT_1.2.16 { + global: + virTypedParamsFilter; + virTypedParamsPickStrings; +} LIBVIRT_1.2.15; + # .... define new API here using predicted next version number .... diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index ec20b74..a3d959e 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -481,6 +481,58 @@ virTypedParamsGet(virTypedParameterPtr params, } +/** + * virTypedParamsFilter: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * @ret: pointer to the returned array + * + * + * Filters @params retaining only the parameters named @name in the + * resulting array @ret. Caller should free the @ret array but no the + * items since they are pointing to the @params elements. + * + * Returns amount of elements in @ret on success, -1 on error. + */ +int +virTypedParamsFilter(virTypedParameterPtr params, + int nparams, + const char *name, + virTypedParameterPtr **ret) +{ + size_t i, max = 0, n = 0; + + virResetLastError(); + + if (!params || !name || !ret) { + virReportError(VIR_ERR_INVALID_ARG, + _("Required argument is missing: %s"), + !params ? "params" : !name ? "name" : "ret"); + goto error; + } + + for (i = 0; i < nparams; i++) { + if (STREQ(params[i].field, name)) { + if (VIR_RESIZE_N(*ret, max, n, 1) < 0) + goto error; + + (*ret)[n] = ¶ms[i]; + + n++; + } + } + + return n; + + error: + if (ret) + VIR_FREE(*ret); + virDispatchError(NULL); + return -1; +} + + #define VIR_TYPED_PARAM_CHECK_TYPE(check_type) \ do { if (param->type != check_type) { \ virReportError(VIR_ERR_INVALID_ARG, \ @@ -749,6 +801,61 @@ virTypedParamsGetString(virTypedParameterPtr params, /** + * virTypedParamsPickStrings: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * @picked: pointer to the amount of picked strings. + * + * + * Finds typed parameters called @name. + * + * Returns a string list, which is a NULL terminated array of pointers to + * strings. Since a NULL is a valid parameter string value caller can ask + * for exact amount of picked strings using @picked argument. + * + * Caller should free the returned array but not the items since they are + * taken from @params array. + */ +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, const char *name, + size_t *picked) +{ + const char **values = NULL; + size_t i, n; + int nfiltered; + virTypedParameterPtr *filtered = NULL; + + virResetLastError(); + + nfiltered = virTypedParamsFilter(params, nparams, name, &filtered); + + if (nfiltered <= 0) + return NULL; + + if (VIR_ALLOC_N(values, nfiltered + 1) < 0) + goto error; + + for (n = 0, i = 0; i < nfiltered; i++) { + if (filtered[i]->type == VIR_TYPED_PARAM_STRING) + values[n++] = filtered[i]->value.s; + } + + if (picked) + *picked = n; + + VIR_FREE(filtered); + return values; + + error: + VIR_FREE(filtered); + virDispatchError(NULL); + return NULL; +} + + +/** * virTypedParamsAddInt: * @params: pointer to the array of typed parameters * @nparams: number of parameters in the @params array diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c index 95e22a7..945dbe7 100644 --- a/tests/virtypedparamtest.c +++ b/tests/virtypedparamtest.c @@ -81,6 +81,96 @@ testTypedParamsValidate(const void *opaque) .nparams = PARAMS_SIZE(__VA_ARGS__), static int +testTypedParamsFilter(const void *opaque ATTRIBUTE_UNUSED) +{ + size_t i, nfiltered; + int rv = -1; + virTypedParameter params[] = { + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + }; + virTypedParameterPtr *filtered = NULL; + + + nfiltered = virTypedParamsFilter(params, ARRAY_CARDINALITY(params), + "foo", &filtered); + if (nfiltered != 3) + goto cleanup; + + for (i = 0; i < nfiltered; i++) { + if (filtered[i] != ¶ms[1 + i * 2]) + goto cleanup; + } + VIR_FREE(filtered); + filtered = NULL; + + nfiltered = virTypedParamsFilter(params, ARRAY_CARDINALITY(params), + "bar", &filtered); + + if (nfiltered != 2) + goto cleanup; + + for (i = 0; i < nfiltered; i++) { + if (filtered[i] != ¶ms[i * 2]) + goto cleanup; + } + + rv = 0; + cleanup: + VIR_FREE(filtered); + return rv; +} + +static int +testTypedParamsPickStrings(const void *opaque ATTRIBUTE_UNUSED) +{ + size_t i, picked; + int rv = -1; + char l = '1'; + const char **strings = NULL; + + virTypedParameter params[] = { + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar1"} }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar2"} }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = NULL } }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT }, + { .field = "bar", .type = VIR_TYPED_PARAM_STRING, + .value = { .s = (char*)"bar3"} } + }; + + strings = virTypedParamsPickStrings(params, + ARRAY_CARDINALITY(params), + "bar", &picked); + + for (i = 0; i < picked; i++) { + if (i == 2) { + if (strings[i] != NULL) + goto cleanup; + continue; + } + if (!STREQLEN(strings[i], "bar", 3)) + goto cleanup; + if (strings[i][3] != l++) + goto cleanup; + } + + rv = 0; + cleanup: + VIR_FREE(strings); + return rv; +} + +static int testTypedParamsValidator(void) { size_t i; @@ -159,6 +249,12 @@ mymain(void) if (testTypedParamsValidator() < 0) rv = -1; + if (virtTestRun("Filtering", testTypedParamsFilter, NULL) < 0) + rv = -1; + + if (virtTestRun("Picking Strings", testTypedParamsPickStrings, NULL) < 0) + rv = -1; + if (rv < 0) return EXIT_FAILURE; return EXIT_SUCCESS; -- 1.9.1

On 21.05.2015 13:07, Pavel Boldin wrote:
Add multikey API:
* virTypedParamsFilter that returns all the parameters with specified name. * virTypedParamsPickStrings that returns a NULL-terminated `const char**' list with all the values for specified name and string type.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 10 ++++ src/libvirt_public.syms | 6 +++ src/util/virtypedparam.c | 107 +++++++++++++++++++++++++++++++++++++++++ tests/virtypedparamtest.c | 96 ++++++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+)
diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index 070550b..36267fc 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -249,6 +249,11 @@ virTypedParamsGet (virTypedParameterPtr params, int nparams, const char *name); int +virTypedParamsFilter (virTypedParameterPtr params, + int nparams, + const char *name, + virTypedParameterPtr **ret); +int virTypedParamsGetInt (virTypedParameterPtr params, int nparams, const char *name, @@ -283,6 +288,11 @@ virTypedParamsGetString (virTypedParameterPtr params, int nparams, const char *name, const char **value); +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, + const char *name, + size_t *picked); int virTypedParamsAddInt (virTypedParameterPtr *params, int *nparams, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index ef3d2f0..8fc8c42 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -710,4 +710,10 @@ LIBVIRT_1.2.15 { virDomainDelIOThread; } LIBVIRT_1.2.14;
+LIBVIRT_1.2.16 { + global: + virTypedParamsFilter; + virTypedParamsPickStrings; +} LIBVIRT_1.2.15; + # .... define new API here using predicted next version number .... diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index ec20b74..a3d959e 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -481,6 +481,58 @@ virTypedParamsGet(virTypedParameterPtr params, }
+/** + * virTypedParamsFilter: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * @ret: pointer to the returned array + * + * + * Filters @params retaining only the parameters named @name in the + * resulting array @ret. Caller should free the @ret array but no the
s/no/not/
+ * items since they are pointing to the @params elements. + * + * Returns amount of elements in @ret on success, -1 on error. + */ +int +virTypedParamsFilter(virTypedParameterPtr params, + int nparams, + const char *name, + virTypedParameterPtr **ret) +{ + size_t i, max = 0, n = 0;
s/max/alloc/
+ + virResetLastError(); + + if (!params || !name || !ret) { + virReportError(VIR_ERR_INVALID_ARG, + _("Required argument is missing: %s"), + !params ? "params" : !name ? "name" : "ret"); + goto error;
We have a macro for that: virCheckNonNullArgGoto()
+ } + + for (i = 0; i < nparams; i++) { + if (STREQ(params[i].field, name)) { + if (VIR_RESIZE_N(*ret, max, n, 1) < 0) + goto error; + + (*ret)[n] = ¶ms[i]; + + n++; + } + }
So if there's no match, @ret is untouched. This is not cool, consider the following code: virTypedParameterPtr *ret; if (virTypedParamsFilter(.., &ret) < 0) { error; } else { success; free(ret); } The free() may end up freeing a random pointer.
+ + return n; + + error: + if (ret) + VIR_FREE(*ret); + virDispatchError(NULL); + return -1; +} + + #define VIR_TYPED_PARAM_CHECK_TYPE(check_type) \ do { if (param->type != check_type) { \ virReportError(VIR_ERR_INVALID_ARG, \ @@ -749,6 +801,61 @@ virTypedParamsGetString(virTypedParameterPtr params,
/** + * virTypedParamsPickStrings: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @name: name of the parameter to find + * @picked: pointer to the amount of picked strings. + * + * + * Finds typed parameters called @name. + * + * Returns a string list, which is a NULL terminated array of pointers to + * strings. Since a NULL is a valid parameter string value caller can ask + * for exact amount of picked strings using @picked argument.
This does not make much sense to me. As even your own test shows, while you are returning a NULL terminated array, you allow NULLs to occur in the array too. Therefore seeing a NULL item, one can't be sure if it is an item or array margin. That's why we have @picked. But since we have it, why we need NULL terminator?
+ * + * Caller should free the returned array but not the items since they are + * taken from @params array. + */ +const char ** +virTypedParamsPickStrings(virTypedParameterPtr params, + int nparams, const char *name, + size_t *picked) +{ + const char **values = NULL; + size_t i, n; + int nfiltered; + virTypedParameterPtr *filtered = NULL; + + virResetLastError(); + + nfiltered = virTypedParamsFilter(params, nparams, name, &filtered); + + if (nfiltered <= 0)
@picked should be updated here.
+ return NULL; + + if (VIR_ALLOC_N(values, nfiltered + 1) < 0) + goto error;
So, if allocation fails, NULL is returned. How is this different to case covered in previous case (i.e. when no such param with @name was found)? We need to distinguish these two cases.
+ + for (n = 0, i = 0; i < nfiltered; i++) { + if (filtered[i]->type == VIR_TYPED_PARAM_STRING) + values[n++] = filtered[i]->value.s; + } + + if (picked) + *picked = n; + + VIR_FREE(filtered); + return values; + + error: + VIR_FREE(filtered); + virDispatchError(NULL); + return NULL; +} + + +/** * virTypedParamsAddInt: * @params: pointer to the array of typed parameters * @nparams: number of parameters in the @params array
Michal

The `virTypedParamsAddStringList' function provides interface to add a NULL-terminated array of string values as a multi-value to the params. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 6 ++++++ src/libvirt_public.syms | 1 + src/util/virtypedparam.c | 36 ++++++++++++++++++++++++++++++++++++ tests/virtypedparamtest.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index 36267fc..7d0b5b7 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -336,6 +336,12 @@ virTypedParamsAddString (virTypedParameterPtr *params, const char *name, const char *value); int +virTypedParamsAddStringList(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char **values); +int virTypedParamsAddFromString(virTypedParameterPtr *params, int *nparams, int *maxparams, diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 8fc8c42..4de23c0 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -714,6 +714,7 @@ LIBVIRT_1.2.16 { global: virTypedParamsFilter; virTypedParamsPickStrings; + virTypedParamsAddStringList; } LIBVIRT_1.2.15; # .... define new API here using predicted next version number .... diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index a3d959e..a2e99ee 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -1191,6 +1191,42 @@ virTypedParamsAddString(virTypedParameterPtr *params, return -1; } +/** + * virTypedParamsAddStringList: + * @params: array of typed parameters + * @nparams: number of parameters in the @params array + * @maxparams: maximum number of parameters that can be stored in @params + * array without allocating more memory + * @name: name of the parameter to store values to + * @values: the values to store into the new parameters + * + * Packs NULL-terminated list of strings @values into @params under the + * key @name. + * + * Returns 0 on success, -1 on error. + */ +int +virTypedParamsAddStringList(virTypedParameterPtr *params, + int *nparams, + int *maxparams, + const char *name, + const char **values) +{ + size_t i; + int rv = -1; + + if (!values) + return 0; + + for (i = 0; values[i]; i++) { + if ((rv = virTypedParamsAddString(params, nparams, maxparams, + name, values[i])) < 0) + break; + } + + return rv; +} + /** * virTypedParamsAddFromString: diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c index 945dbe7..26fa4b2 100644 --- a/tests/virtypedparamtest.c +++ b/tests/virtypedparamtest.c @@ -126,6 +126,31 @@ testTypedParamsFilter(const void *opaque ATTRIBUTE_UNUSED) } static int +testTypedParamsAddStringList(const void *opaque ATTRIBUTE_UNUSED) +{ + int rv = 0; + virTypedParameterPtr params = NULL; + int nparams = 0, maxparams = 0, i; + + const char *values[] = { + "foo", "bar", "foobar", NULL + }; + + rv = virTypedParamsAddStringList(¶ms, &nparams, &maxparams, "param", + values); + + for (i = 0; i < nparams; i++) { + if (STRNEQ(params[i].field, "param") || + STRNEQ(params[i].value.s, values[i]) || + params[i].type != VIR_TYPED_PARAM_STRING) + rv = -1; + } + + virTypedParamsFree(params, nparams); + return rv; +} + +static int testTypedParamsPickStrings(const void *opaque ATTRIBUTE_UNUSED) { size_t i, picked; @@ -255,6 +280,9 @@ mymain(void) if (virtTestRun("Picking Strings", testTypedParamsPickStrings, NULL) < 0) rv = -1; + if (virtTestRun("Add string list", testTypedParamsAddStringList, NULL) < 0) + rv = -1; + if (rv < 0) return EXIT_FAILURE; return EXIT_SUCCESS; -- 1.9.1

On 21.05.2015 13:07, Pavel Boldin wrote:
The `virTypedParamsAddStringList' function provides interface to add a NULL-terminated array of string values as a multi-value to the params.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-host.h | 6 ++++++ src/libvirt_public.syms | 1 + src/util/virtypedparam.c | 36 ++++++++++++++++++++++++++++++++++++ tests/virtypedparamtest.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+)
ACK Michal

Implement a `migrate_disks' parameters for the QEMU driver. This multi- value parameter can be used to explicitly specify what block devices are to be migrated using the NBD server. Tunnelled migration using NBD is to be done. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-domain.h | 9 ++ src/qemu/qemu_driver.c | 66 ++++++++++----- src/qemu/qemu_migration.c | 174 +++++++++++++++++++++++++-------------- src/qemu/qemu_migration.h | 23 ++++-- 4 files changed, 183 insertions(+), 89 deletions(-) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 0f465b9..6b48923 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -748,6 +748,15 @@ typedef enum { */ # define VIR_MIGRATE_PARAM_LISTEN_ADDRESS "listen_address" +/** + * VIR_MIGRATE_PARAM_MIGRATE_DISKS: + * + * virDomainMigrate* params multiple field: The multiple values that list + * the block devices to be migrated. At the moment this is only supported + * by the QEMU driver but not for the tunnelled migration. + */ +# define VIR_MIGRATE_PARAM_MIGRATE_DISKS "migrate_disks" + /* Domain migration. */ virDomainPtr virDomainMigrate (virDomainPtr domain, virConnectPtr dconn, unsigned long flags, const char *dname, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 2668011..77b7d9d 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12472,7 +12472,7 @@ qemuDomainMigratePrepare2(virConnectPtr dconn, ret = qemuMigrationPrepareDirect(driver, dconn, NULL, 0, NULL, NULL, /* No cookies */ uri_in, uri_out, - &def, origname, NULL, flags); + &def, origname, NULL, flags, NULL); cleanup: VIR_FREE(origname); @@ -12525,7 +12525,7 @@ qemuDomainMigratePerform(virDomainPtr dom, * Consume any cookie we were able to decode though */ ret = qemuMigrationPerform(driver, dom->conn, vm, - NULL, dconnuri, uri, NULL, NULL, + NULL, dconnuri, uri, NULL, NULL, NULL, cookie, cookielen, NULL, NULL, /* No output cookies in v2 */ flags, dname, resource, false); @@ -12602,7 +12602,7 @@ qemuDomainMigrateBegin3(virDomainPtr domain, } return qemuMigrationBegin(domain->conn, vm, xmlin, dname, - cookieout, cookieoutlen, flags); + cookieout, cookieoutlen, flags, NULL); } static char * @@ -12615,11 +12615,13 @@ qemuDomainMigrateBegin3Params(virDomainPtr domain, { const char *xmlin = NULL; const char *dname = NULL; + const char **migrate_disks = NULL; + char *ret = NULL; virDomainObjPtr vm; virCheckFlags(QEMU_MIGRATION_FLAGS, NULL); if (virTypedParamsValidate(params, nparams, QEMU_MIGRATION_PARAMETERS) < 0) - return NULL; + goto cleanup; if (virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_DEST_XML, @@ -12627,18 +12629,26 @@ qemuDomainMigrateBegin3Params(virDomainPtr domain, virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_DEST_NAME, &dname) < 0) - return NULL; + goto cleanup; + + migrate_disks = virTypedParamsPickStrings(params, nparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + NULL); if (!(vm = qemuDomObjFromDomain(domain))) - return NULL; + goto cleanup; if (virDomainMigrateBegin3ParamsEnsureACL(domain->conn, vm->def) < 0) { virDomainObjEndAPI(&vm); - return NULL; + goto cleanup; } - return qemuMigrationBegin(domain->conn, vm, xmlin, dname, - cookieout, cookieoutlen, flags); + ret = qemuMigrationBegin(domain->conn, vm, xmlin, dname, + cookieout, cookieoutlen, flags, migrate_disks); + + cleanup: + VIR_FREE(migrate_disks); + return ret; } @@ -12682,7 +12692,8 @@ qemuDomainMigratePrepare3(virConnectPtr dconn, cookiein, cookieinlen, cookieout, cookieoutlen, uri_in, uri_out, - &def, origname, NULL, flags); + &def, origname, NULL, flags, + NULL); cleanup: VIR_FREE(origname); @@ -12708,6 +12719,7 @@ qemuDomainMigratePrepare3Params(virConnectPtr dconn, const char *dname = NULL; const char *uri_in = NULL; const char *listenAddress = cfg->migrationAddress; + const char **migrate_disks = NULL; char *origname = NULL; int ret = -1; @@ -12729,6 +12741,10 @@ qemuDomainMigratePrepare3Params(virConnectPtr dconn, &listenAddress) < 0) goto cleanup; + migrate_disks = virTypedParamsPickStrings(params, nparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + NULL); + if (flags & VIR_MIGRATE_TUNNELLED) { /* this is a logical error; we never should have gotten here with * VIR_MIGRATE_TUNNELLED set @@ -12749,9 +12765,11 @@ qemuDomainMigratePrepare3Params(virConnectPtr dconn, cookiein, cookieinlen, cookieout, cookieoutlen, uri_in, uri_out, - &def, origname, listenAddress, flags); + &def, origname, listenAddress, flags, + migrate_disks); cleanup: + VIR_FREE(migrate_disks); VIR_FREE(origname); virDomainDefFree(def); virObjectUnref(cfg); @@ -12882,7 +12900,7 @@ qemuDomainMigratePerform3(virDomainPtr dom, } return qemuMigrationPerform(driver, dom->conn, vm, xmlin, - dconnuri, uri, NULL, NULL, + dconnuri, uri, NULL, NULL, NULL, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, true); @@ -12906,7 +12924,9 @@ qemuDomainMigratePerform3Params(virDomainPtr dom, const char *uri = NULL; const char *graphicsuri = NULL; const char *listenAddress = NULL; + const char **migrate_disks = NULL; unsigned long long bandwidth = 0; + int ret = -1; virCheckFlags(QEMU_MIGRATION_FLAGS, -1); if (virTypedParamsValidate(params, nparams, QEMU_MIGRATION_PARAMETERS) < 0) @@ -12930,20 +12950,28 @@ qemuDomainMigratePerform3Params(virDomainPtr dom, virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_LISTEN_ADDRESS, &listenAddress) < 0) - return -1; + goto cleanup; + + migrate_disks = virTypedParamsPickStrings(params, nparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + NULL); if (!(vm = qemuDomObjFromDomain(dom))) - return -1; + goto cleanup; if (virDomainMigratePerform3ParamsEnsureACL(dom->conn, vm->def) < 0) { virDomainObjEndAPI(&vm); - return -1; + goto cleanup; } - return qemuMigrationPerform(driver, dom->conn, vm, dom_xml, - dconnuri, uri, graphicsuri, listenAddress, - cookiein, cookieinlen, cookieout, cookieoutlen, - flags, dname, bandwidth, true); + ret = qemuMigrationPerform(driver, dom->conn, vm, dom_xml, + dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, + cookiein, cookieinlen, cookieout, cookieoutlen, + flags, dname, bandwidth, true); + cleanup: + VIR_FREE(migrate_disks); + return ret; } diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 8fe1cfb..708d1aa 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1579,12 +1579,32 @@ qemuMigrationPrecreateDisk(virConnectPtr conn, return ret; } +static int +qemuMigrateDisk(virDomainDiskDefPtr const disk, const char **migrate_disks) +{ + size_t i; + /* Check if the disk alias is in the list */ + if (disk->info.alias && migrate_disks) { + for (i = 0; migrate_disks[i]; i++) { + if (STREQ(disk->info.alias, migrate_disks[i])) + return 1; + } + return 0; + } + + /* Default is to migrate only non-shared non-readonly disks + * with source */ + return !disk->src->shared && !disk->src->readonly && + virDomainDiskGetSource(disk); +} + static int qemuMigrationPrecreateStorage(virConnectPtr conn, virQEMUDriverPtr driver ATTRIBUTE_UNUSED, virDomainObjPtr vm, - qemuMigrationCookieNBDPtr nbd) + qemuMigrationCookieNBDPtr nbd, + const char **migrate_disks) { int ret = -1; size_t i = 0; @@ -1611,9 +1631,9 @@ qemuMigrationPrecreateStorage(virConnectPtr conn, disk = vm->def->disks[indx]; diskSrcPath = virDomainDiskGetSource(disk); - if (disk->src->shared || disk->src->readonly || + /* Skip disks we don't want to migrate and already existing disks. */ + if (!qemuMigrateDisk(disk, migrate_disks) || (diskSrcPath && virFileExists(diskSrcPath))) { - /* Skip shared, read-only and already existing disks. */ continue; } @@ -1644,7 +1664,8 @@ qemuMigrationPrecreateStorage(virConnectPtr conn, static int qemuMigrationStartNBDServer(virQEMUDriverPtr driver, virDomainObjPtr vm, - const char *listenAddr) + const char *listenAddr, + const char **migrate_disks) { int ret = -1; qemuDomainObjPrivatePtr priv = vm->privateData; @@ -1655,9 +1676,8 @@ qemuMigrationStartNBDServer(virQEMUDriverPtr driver, for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk = vm->def->disks[i]; - /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + if (!qemuMigrateDisk(disk, migrate_disks)) continue; VIR_FREE(diskAlias); @@ -1916,7 +1936,8 @@ qemuMigrationDriveMirror(virQEMUDriverPtr driver, qemuMigrationCookiePtr mig, const char *host, unsigned long speed, - unsigned int *migrate_flags) + unsigned int *migrate_flags, + const char **migrate_disks) { qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; @@ -1947,9 +1968,8 @@ qemuMigrationDriveMirror(virQEMUDriverPtr driver, qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); int mon_ret; - /* skip shared, RO and source-less disks */ - if (disk->src->shared || disk->src->readonly || - !virDomainDiskGetSource(disk)) + /* check whether disk should be migrated */ + if (!qemuMigrateDisk(disk, migrate_disks)) continue; VIR_FREE(diskAlias); @@ -2125,7 +2145,7 @@ qemuMigrationIsAllowed(virQEMUDriverPtr driver, virDomainObjPtr vm, } static bool -qemuMigrationIsSafe(virDomainDefPtr def) +qemuMigrationIsSafe(virDomainDefPtr def, const char **migrate_disks) { size_t i; @@ -2135,9 +2155,7 @@ qemuMigrationIsSafe(virDomainDefPtr def) /* Our code elsewhere guarantees shared disks are either readonly (in * which case cache mode doesn't matter) or used with cache=none */ - if (src && - !disk->src->shared && - !disk->src->readonly && + if (qemuMigrateDisk(disk, migrate_disks) && disk->cachemode != VIR_DOMAIN_DISK_CACHE_DISABLE) { int rc; @@ -2708,7 +2726,8 @@ static char const char *dname, char **cookieout, int *cookieoutlen, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { char *rv = NULL; qemuMigrationCookiePtr mig = NULL; @@ -2719,9 +2738,10 @@ static char bool abort_on_error = !!(flags & VIR_MIGRATE_ABORT_ON_ERROR); VIR_DEBUG("driver=%p, vm=%p, xmlin=%s, dname=%s," - " cookieout=%p, cookieoutlen=%p, flags=%lx", + " cookieout=%p, cookieoutlen=%p, flags=%lx," + " migrate_disks=%p", driver, vm, NULLSTR(xmlin), NULLSTR(dname), - cookieout, cookieoutlen, flags); + cookieout, cookieoutlen, flags, migrate_disks); if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; @@ -2736,7 +2756,8 @@ static char if (!qemuMigrationIsAllowed(driver, vm, NULL, true, abort_on_error)) goto cleanup; - if (!(flags & VIR_MIGRATE_UNSAFE) && !qemuMigrationIsSafe(vm->def)) + if (!(flags & VIR_MIGRATE_UNSAFE) && + !qemuMigrationIsSafe(vm->def, migrate_disks)) goto cleanup; if (flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC) && @@ -2807,7 +2828,8 @@ qemuMigrationBegin(virConnectPtr conn, const char *dname, char **cookieout, int *cookieoutlen, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { virQEMUDriverPtr driver = conn->privateData; char *xml = NULL; @@ -2840,7 +2862,7 @@ qemuMigrationBegin(virConnectPtr conn, if (!(xml = qemuMigrationBeginPhase(driver, vm, xmlin, dname, cookieout, cookieoutlen, - flags))) + flags, migrate_disks))) goto endjob; if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { @@ -2908,7 +2930,8 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, unsigned short port, bool autoPort, const char *listenAddress, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { virDomainObjPtr vm = NULL; virObjectEventPtr event = NULL; @@ -3099,7 +3122,8 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, goto cleanup; } - if (qemuMigrationPrecreateStorage(dconn, driver, vm, mig->nbd) < 0) + if (qemuMigrationPrecreateStorage(dconn, driver, vm, mig->nbd, + migrate_disks) < 0) goto cleanup; if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_IN) < 0) @@ -3173,7 +3197,8 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, if (mig->nbd && flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC) && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NBD_SERVER)) { - if (qemuMigrationStartNBDServer(driver, vm, listenAddress) < 0) { + if (qemuMigrationStartNBDServer(driver, vm, listenAddress, + migrate_disks) < 0) { /* error already reported */ goto endjob; } @@ -3272,7 +3297,7 @@ qemuMigrationPrepareTunnel(virQEMUDriverPtr driver, ret = qemuMigrationPrepareAny(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, def, origname, - st, NULL, 0, false, NULL, flags); + st, NULL, 0, false, NULL, flags, NULL); return ret; } @@ -3312,7 +3337,8 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver, virDomainDefPtr *def, const char *origname, const char *listenAddress, - unsigned long flags) + unsigned long flags, + const char **migrate_disks) { unsigned short port = 0; bool autoPort = true; @@ -3324,10 +3350,12 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, dconn=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, uri_in=%s, uri_out=%p, " - "def=%p, origname=%s, listenAddress=%s, flags=%lx", + "def=%p, origname=%s, listenAddress=%s, flags=%lx, " + "migrate_disks=%p", driver, dconn, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, NULLSTR(uri_in), uri_out, - *def, origname, NULLSTR(listenAddress), flags); + *def, origname, NULLSTR(listenAddress), flags, + migrate_disks); *uri_out = NULL; @@ -3431,7 +3459,8 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver, ret = qemuMigrationPrepareAny(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, def, origname, NULL, uri ? uri->scheme : "tcp", - port, autoPort, listenAddress, flags); + port, autoPort, listenAddress, flags, + migrate_disks); cleanup: virURIFree(uri); VIR_FREE(hostname); @@ -3903,7 +3932,8 @@ qemuMigrationRun(virQEMUDriverPtr driver, unsigned long resource, qemuMigrationSpecPtr spec, virConnectPtr dconn, - const char *graphicsuri) + const char *graphicsuri, + const char **migrate_disks) { int ret = -1; unsigned int migrate_flags = QEMU_MONITOR_MIGRATE_BACKGROUND; @@ -3919,11 +3949,12 @@ qemuMigrationRun(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, resource=%lu, " - "spec=%p (dest=%d, fwd=%d), dconn=%p, graphicsuri=%s", + "spec=%p (dest=%d, fwd=%d), dconn=%p, graphicsuri=%s, " + "migrate_disks=%p", driver, vm, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, spec, spec->destType, spec->fwdType, dconn, - NULLSTR(graphicsuri)); + NULLSTR(graphicsuri), migrate_disks); if (flags & VIR_MIGRATE_NON_SHARED_DISK) { migrate_flags |= QEMU_MONITOR_MIGRATE_NON_SHARED_DISK; @@ -3959,7 +3990,8 @@ qemuMigrationRun(virQEMUDriverPtr driver, if (qemuMigrationDriveMirror(driver, vm, mig, spec->dest.host.name, migrate_speed, - &migrate_flags) < 0) { + &migrate_flags, + migrate_disks) < 0) { goto cleanup; } } else { @@ -4193,7 +4225,8 @@ static int doNativeMigrate(virQEMUDriverPtr driver, unsigned long flags, unsigned long resource, virConnectPtr dconn, - const char *graphicsuri) + const char *graphicsuri, + const char **migrate_disks) { qemuDomainObjPrivatePtr priv = vm->privateData; virURIPtr uribits = NULL; @@ -4202,10 +4235,10 @@ static int doNativeMigrate(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, vm=%p, uri=%s, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, resource=%lu, " - "graphicsuri=%s", + "graphicsuri=%s, migrate_disks=%p", driver, vm, uri, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, - NULLSTR(graphicsuri)); + NULLSTR(graphicsuri), migrate_disks); if (!(uribits = qemuMigrationParseURI(uri, NULL))) return -1; @@ -4244,7 +4277,7 @@ static int doNativeMigrate(virQEMUDriverPtr driver, ret = qemuMigrationRun(driver, vm, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, &spec, dconn, - graphicsuri); + graphicsuri, migrate_disks); if (spec.destType == MIGRATION_DEST_FD) VIR_FORCE_CLOSE(spec.dest.fd.qemu); @@ -4266,7 +4299,8 @@ static int doTunnelMigrate(virQEMUDriverPtr driver, unsigned long flags, unsigned long resource, virConnectPtr dconn, - const char *graphicsuri) + const char *graphicsuri, + const char **migrate_disks) { qemuDomainObjPrivatePtr priv = vm->privateData; virNetSocketPtr sock = NULL; @@ -4276,10 +4310,10 @@ static int doTunnelMigrate(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, vm=%p, st=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, resource=%lu, " - "graphicsuri=%s", + "graphicsuri=%s, migrate_disks=%p", driver, vm, st, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, - NULLSTR(graphicsuri)); + NULLSTR(graphicsuri), migrate_disks); if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATE_QEMU_FD) && !virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATE_QEMU_UNIX) && @@ -4332,7 +4366,7 @@ static int doTunnelMigrate(virQEMUDriverPtr driver, ret = qemuMigrationRun(driver, vm, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, &spec, dconn, - graphicsuri); + graphicsuri, migrate_disks); cleanup: if (spec.destType == MIGRATION_DEST_FD) { @@ -4442,12 +4476,12 @@ static int doPeer2PeerMigrate2(virQEMUDriverPtr driver, if (flags & VIR_MIGRATE_TUNNELLED) ret = doTunnelMigrate(driver, vm, st, NULL, 0, NULL, NULL, - flags, resource, dconn, NULL); + flags, resource, dconn, NULL, NULL); else ret = doNativeMigrate(driver, vm, uri_out, cookie, cookielen, NULL, NULL, /* No out cookie with v2 migration */ - flags, resource, dconn, NULL); + flags, resource, dconn, NULL, NULL); /* Perform failed. Make sure Finish doesn't overwrite the error */ if (ret < 0) @@ -4509,6 +4543,7 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, unsigned long long bandwidth, bool useParams, unsigned long flags) @@ -4531,10 +4566,11 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, VIR_DEBUG("driver=%p, sconn=%p, dconn=%p, dconnuri=%s, vm=%p, xmlin=%s, " "dname=%s, uri=%s, graphicsuri=%s, listenAddress=%s, " - "bandwidth=%llu, useParams=%d, flags=%lx", + "migrate_disks=%p, bandwidth=%llu, useParams=%d, flags=%lx", driver, sconn, dconn, NULLSTR(dconnuri), vm, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), NULLSTR(graphicsuri), - NULLSTR(listenAddress), bandwidth, useParams, flags); + NULLSTR(listenAddress), migrate_disks, bandwidth, useParams, + flags); /* Unlike the virDomainMigrateVersion3 counterpart, we don't need * to worry about auto-setting the VIR_MIGRATE_CHANGE_PROTECTION @@ -4542,7 +4578,8 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, * a single job. */ dom_xml = qemuMigrationBeginPhase(driver, vm, xmlin, dname, - &cookieout, &cookieoutlen, flags); + &cookieout, &cookieoutlen, flags, + migrate_disks); if (!dom_xml) goto cleanup; @@ -4577,6 +4614,11 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, VIR_MIGRATE_PARAM_LISTEN_ADDRESS, listenAddress) < 0) goto cleanup; + if (migrate_disks && + virTypedParamsAddStringList(¶ms, &nparams, &maxparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + migrate_disks) < 0) + goto cleanup; } if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) @@ -4661,12 +4703,14 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, ret = doTunnelMigrate(driver, vm, st, cookiein, cookieinlen, &cookieout, &cookieoutlen, - flags, bandwidth, dconn, graphicsuri); + flags, bandwidth, dconn, graphicsuri, + migrate_disks); } else { ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, &cookieout, &cookieoutlen, - flags, bandwidth, dconn, graphicsuri); + flags, bandwidth, dconn, graphicsuri, + migrate_disks); } /* Perform failed. Make sure Finish doesn't overwrite the error */ @@ -4800,6 +4844,7 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, unsigned long flags, const char *dname, unsigned long resource, @@ -4813,12 +4858,12 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); bool useParams; - VIR_DEBUG("driver=%p, sconn=%p, vm=%p, xmlin=%s, dconnuri=%s, " - "uri=%s, graphicsuri=%s, listenAddress=%s, flags=%lx, " - "dname=%s, resource=%lu", + VIR_DEBUG("driver=%p, sconn=%p, vm=%p, xmlin=%s, dconnuri=%s, uri=%s, " + "graphicsuri=%s, listenAddress=%s, migrate_disks=%p, " + "flags=%lx, dname=%s, resource=%lu", driver, sconn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), NULLSTR(uri), NULLSTR(graphicsuri), NULLSTR(listenAddress), - flags, NULLSTR(dname), resource); + migrate_disks, flags, NULLSTR(dname), resource); if (flags & VIR_MIGRATE_TUNNELLED && uri) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", @@ -4877,7 +4922,7 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, /* Only xmlin, dname, uri, and bandwidth parameters can be used with * old-style APIs. */ - if (!useParams && (graphicsuri || listenAddress)) { + if (!useParams && (graphicsuri || listenAddress || migrate_disks)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Migration APIs with extensible parameters are not " "supported but extended parameters were passed")); @@ -4908,7 +4953,7 @@ static int doPeer2PeerMigrate(virQEMUDriverPtr driver, if (*v3proto) { ret = doPeer2PeerMigrate3(driver, sconn, dconn, dconnuri, vm, xmlin, dname, uri, graphicsuri, listenAddress, - resource, useParams, flags); + migrate_disks, resource, useParams, flags); } else { ret = doPeer2PeerMigrate2(driver, sconn, dconn, vm, dconnuri, flags, dname, resource); @@ -4942,6 +4987,7 @@ qemuMigrationPerformJob(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, @@ -4969,7 +5015,8 @@ qemuMigrationPerformJob(virQEMUDriverPtr driver, if (!qemuMigrationIsAllowed(driver, vm, NULL, true, abort_on_error)) goto endjob; - if (!(flags & VIR_MIGRATE_UNSAFE) && !qemuMigrationIsSafe(vm->def)) + if (!(flags & VIR_MIGRATE_UNSAFE) && + !qemuMigrationIsSafe(vm->def, migrate_disks)) goto endjob; qemuMigrationStoreDomainState(vm); @@ -4977,12 +5024,13 @@ qemuMigrationPerformJob(virQEMUDriverPtr driver, if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { ret = doPeer2PeerMigrate(driver, conn, vm, xmlin, dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, flags, dname, resource, &v3proto); } else { qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PERFORM2); ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, cookieout, cookieoutlen, - flags, resource, NULL, NULL); + flags, resource, NULL, NULL, NULL); } if (ret < 0) goto endjob; @@ -5041,6 +5089,7 @@ qemuMigrationPerformPhase(virQEMUDriverPtr driver, virDomainObjPtr vm, const char *uri, const char *graphicsuri, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, @@ -5065,7 +5114,7 @@ qemuMigrationPerformPhase(virQEMUDriverPtr driver, ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, cookieout, cookieoutlen, - flags, resource, NULL, graphicsuri); + flags, resource, NULL, graphicsuri, migrate_disks); if (ret < 0) { if (qemuMigrationRestoreDomainState(conn, vm)) { @@ -5106,6 +5155,7 @@ qemuMigrationPerform(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, @@ -5116,13 +5166,13 @@ qemuMigrationPerform(virQEMUDriverPtr driver, bool v3proto) { VIR_DEBUG("driver=%p, conn=%p, vm=%p, xmlin=%s, dconnuri=%s, " - "uri=%s, graphicsuri=%s, listenAddress=%s" + "uri=%s, graphicsuri=%s, listenAddress=%s, migrate_disks=%p, " "cookiein=%s, cookieinlen=%d, cookieout=%p, cookieoutlen=%p, " "flags=%lx, dname=%s, resource=%lu, v3proto=%d", driver, conn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), NULLSTR(uri), NULLSTR(graphicsuri), NULLSTR(listenAddress), - NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, - flags, NULLSTR(dname), resource, v3proto); + migrate_disks, NULLSTR(cookiein), cookieinlen, cookieout, + cookieoutlen, flags, NULLSTR(dname), resource, v3proto); if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { if (cookieinlen) { @@ -5133,6 +5183,7 @@ qemuMigrationPerform(virQEMUDriverPtr driver, return qemuMigrationPerformJob(driver, conn, vm, xmlin, dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, v3proto); @@ -5145,13 +5196,14 @@ qemuMigrationPerform(virQEMUDriverPtr driver, if (v3proto) { return qemuMigrationPerformPhase(driver, conn, vm, uri, - graphicsuri, + graphicsuri, migrate_disks, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource); } else { return qemuMigrationPerformJob(driver, conn, vm, xmlin, dconnuri, uri, graphicsuri, listenAddress, + migrate_disks, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, v3proto); diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index 1726455..42d64e9 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -44,13 +44,15 @@ VIR_MIGRATE_RDMA_PIN_ALL) /* All supported migration parameters and their types. */ -# define QEMU_MIGRATION_PARAMETERS \ - VIR_MIGRATE_PARAM_URI, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_DEST_NAME, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_DEST_XML, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_BANDWIDTH, VIR_TYPED_PARAM_ULLONG, \ - VIR_MIGRATE_PARAM_GRAPHICS_URI, VIR_TYPED_PARAM_STRING, \ - VIR_MIGRATE_PARAM_LISTEN_ADDRESS, VIR_TYPED_PARAM_STRING, \ +# define QEMU_MIGRATION_PARAMETERS \ + VIR_MIGRATE_PARAM_URI, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_DEST_NAME, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_DEST_XML, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_BANDWIDTH, VIR_TYPED_PARAM_ULLONG, \ + VIR_MIGRATE_PARAM_GRAPHICS_URI, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_LISTEN_ADDRESS, VIR_TYPED_PARAM_STRING, \ + VIR_MIGRATE_PARAM_MIGRATE_DISKS, VIR_TYPED_PARAM_STRING | \ + VIR_TYPED_PARAM_MULTIPLE, \ NULL @@ -99,7 +101,8 @@ char *qemuMigrationBegin(virConnectPtr conn, const char *dname, char **cookieout, int *cookieoutlen, - unsigned long flags); + unsigned long flags, + const char **migrate_disks); virDomainDefPtr qemuMigrationPrepareDef(virQEMUDriverPtr driver, const char *dom_xml, @@ -128,7 +131,8 @@ int qemuMigrationPrepareDirect(virQEMUDriverPtr driver, virDomainDefPtr *def, const char *origname, const char *listenAddress, - unsigned long flags); + unsigned long flags, + const char **migrate_disks); int qemuMigrationPerform(virQEMUDriverPtr driver, virConnectPtr conn, @@ -138,6 +142,7 @@ int qemuMigrationPerform(virQEMUDriverPtr driver, const char *uri, const char *graphicsuri, const char *listenAddress, + const char **migrate_disks, const char *cookiein, int cookieinlen, char **cookieout, -- 1.9.1

On 21.05.2015 13:07, Pavel Boldin wrote:
Implement a `migrate_disks' parameters for the QEMU driver. This multi- value parameter can be used to explicitly specify what block devices are to be migrated using the NBD server. Tunnelled migration using NBD is to be done.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- include/libvirt/libvirt-domain.h | 9 ++ src/qemu/qemu_driver.c | 66 ++++++++++----- src/qemu/qemu_migration.c | 174 +++++++++++++++++++++++++-------------- src/qemu/qemu_migration.h | 23 ++++-- 4 files changed, 183 insertions(+), 89 deletions(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 0f465b9..6b48923 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -748,6 +748,15 @@ typedef enum { */ # define VIR_MIGRATE_PARAM_LISTEN_ADDRESS "listen_address"
+/** + * VIR_MIGRATE_PARAM_MIGRATE_DISKS: + * + * virDomainMigrate* params multiple field: The multiple values that list + * the block devices to be migrated. At the moment this is only supported + * by the QEMU driver but not for the tunnelled migration. + */ +# define VIR_MIGRATE_PARAM_MIGRATE_DISKS "migrate_disks" + /* Domain migration. */ virDomainPtr virDomainMigrate (virDomainPtr domain, virConnectPtr dconn, unsigned long flags, const char *dname, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 2668011..77b7d9d 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12472,7 +12472,7 @@ qemuDomainMigratePrepare2(virConnectPtr dconn, ret = qemuMigrationPrepareDirect(driver, dconn, NULL, 0, NULL, NULL, /* No cookies */ uri_in, uri_out, - &def, origname, NULL, flags); + &def, origname, NULL, flags, NULL);
cleanup: VIR_FREE(origname); @@ -12525,7 +12525,7 @@ qemuDomainMigratePerform(virDomainPtr dom, * Consume any cookie we were able to decode though */ ret = qemuMigrationPerform(driver, dom->conn, vm, - NULL, dconnuri, uri, NULL, NULL, + NULL, dconnuri, uri, NULL, NULL, NULL, cookie, cookielen, NULL, NULL, /* No output cookies in v2 */ flags, dname, resource, false); @@ -12602,7 +12602,7 @@ qemuDomainMigrateBegin3(virDomainPtr domain, }
return qemuMigrationBegin(domain->conn, vm, xmlin, dname, - cookieout, cookieoutlen, flags); + cookieout, cookieoutlen, flags, NULL); }
static char * @@ -12615,11 +12615,13 @@ qemuDomainMigrateBegin3Params(virDomainPtr domain, { const char *xmlin = NULL; const char *dname = NULL; + const char **migrate_disks = NULL; + char *ret = NULL; virDomainObjPtr vm;
virCheckFlags(QEMU_MIGRATION_FLAGS, NULL); if (virTypedParamsValidate(params, nparams, QEMU_MIGRATION_PARAMETERS) < 0) - return NULL; + goto cleanup;
if (virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_DEST_XML, @@ -12627,18 +12629,26 @@ qemuDomainMigrateBegin3Params(virDomainPtr domain, virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_DEST_NAME, &dname) < 0) - return NULL; + goto cleanup; + + migrate_disks = virTypedParamsPickStrings(params, nparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + NULL);
Since this function looks slightly different now, this patch needs to be changed too. Mostly, where @migrate_disks is parsed, new variable (say @nmigrate_disks) describing the array length needs to be passed too.
if (!(vm = qemuDomObjFromDomain(domain))) - return NULL; + goto cleanup;
if (virDomainMigrateBegin3ParamsEnsureACL(domain->conn, vm->def) < 0) { virDomainObjEndAPI(&vm); - return NULL; + goto cleanup; }
- return qemuMigrationBegin(domain->conn, vm, xmlin, dname, - cookieout, cookieoutlen, flags); + ret = qemuMigrationBegin(domain->conn, vm, xmlin, dname, + cookieout, cookieoutlen, flags, migrate_disks); + + cleanup: + VIR_FREE(migrate_disks); + return ret; }
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 8fe1cfb..708d1aa 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1579,12 +1579,32 @@ qemuMigrationPrecreateDisk(virConnectPtr conn, return ret; }
+static int
This should return bool rather than int.
+qemuMigrateDisk(virDomainDiskDefPtr const disk, const char **migrate_disks) +{ + size_t i; + /* Check if the disk alias is in the list */ + if (disk->info.alias && migrate_disks) { + for (i = 0; migrate_disks[i]; i++) { + if (STREQ(disk->info.alias, migrate_disks[i])) + return 1; + } + return 0; + } + + /* Default is to migrate only non-shared non-readonly disks + * with source */ + return !disk->src->shared && !disk->src->readonly && + virDomainDiskGetSource(disk); +} +
@@ -4577,6 +4614,11 @@ doPeer2PeerMigrate3(virQEMUDriverPtr driver, VIR_MIGRATE_PARAM_LISTEN_ADDRESS, listenAddress) < 0) goto cleanup; + if (migrate_disks && + virTypedParamsAddStringList(¶ms, &nparams, &maxparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + migrate_disks) < 0) + goto cleanup;
Since @migrate_disks is no longer a NULL terminated array (but hey, we have @nmigrate_disks) this must be turned into a for() loop.
}
if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED)
Otherwise looking good. Michal

Add `virsh migrate' option `--migratedisks' that allows CLI user to explicitly specify block devices to migrate. Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- tools/virsh-domain.c | 23 +++++++++++++++++++++++ tools/virsh.pod | 5 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 20f8c75..47a24ab 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -9784,6 +9784,10 @@ static const vshCmdOptDef opts_migrate[] = { .type = VSH_OT_STRING, .help = N_("filename containing updated XML for the target") }, + {.name = "migratedisks", + .type = VSH_OT_STRING, + .help = N_("comma separated list of disks to be migrated") + }, {.name = NULL} }; @@ -9843,6 +9847,25 @@ doMigrate(void *opaque) VIR_MIGRATE_PARAM_DEST_NAME, opt) < 0) goto save_error; + if (vshCommandOptStringReq(ctl, cmd, "migratedisks", &opt) < 0) + goto out; + if (opt) { + char **val = NULL; + + val = virStringSplit(opt, ",", 0); + + if (virTypedParamsAddStringList(¶ms, + &nparams, + &maxparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + (const char **)val) < 0) { + VIR_FREE(val); + goto save_error; + } + + VIR_FREE(val); + } + if (vshCommandOptStringReq(ctl, cmd, "xml", &opt) < 0) goto out; if (opt) { diff --git a/tools/virsh.pod b/tools/virsh.pod index 1bb655b..aad0f3b 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1521,6 +1521,7 @@ to the I<uri> namespace is displayed instead of being modified. [I<--compressed>] [I<--abort-on-error>] [I<--auto-converge>] I<domain> I<desturi> [I<migrateuri>] [I<graphicsuri>] [I<listen-address>] [I<dname>] [I<--timeout> B<seconds>] [I<--xml> B<file>] +[I<--migratedisks> B<comma-separated-list>] Migrate domain to another host. Add I<--live> for live migration; <--p2p> for peer-2-peer migration; I<--direct> for direct migration; or I<--tunnelled> @@ -1536,7 +1537,9 @@ with incremental copy (same base image shared between source and destination). In both cases the disk images have to exist on destination host, the I<--copy-storage-...> options only tell libvirt to transfer data from the images on source host to the images found at the same place on the destination -host. I<--change-protection> enforces that no incompatible configuration +host. By default only non-shared non-readonly images are transfered. Use +I<--migratedisks> to explicitly specify a list of images to transfer. +I<--change-protection> enforces that no incompatible configuration changes will be made to the domain while the migration is underway; this flag is implicitly enabled when supported by the hypervisor, but can be explicitly used to reject the migration if the hypervisor lacks change protection -- 1.9.1

On 21.05.2015 13:07, Pavel Boldin wrote:
Add `virsh migrate' option `--migratedisks' that allows CLI user to explicitly specify block devices to migrate.
Signed-off-by: Pavel Boldin <pboldin@mirantis.com> --- tools/virsh-domain.c | 23 +++++++++++++++++++++++ tools/virsh.pod | 5 ++++- 2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 20f8c75..47a24ab 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -9784,6 +9784,10 @@ static const vshCmdOptDef opts_migrate[] = { .type = VSH_OT_STRING, .help = N_("filename containing updated XML for the target") }, + {.name = "migratedisks", + .type = VSH_OT_STRING, + .help = N_("comma separated list of disks to be migrated") + }, {.name = NULL} };
@@ -9843,6 +9847,25 @@ doMigrate(void *opaque) VIR_MIGRATE_PARAM_DEST_NAME, opt) < 0) goto save_error;
+ if (vshCommandOptStringReq(ctl, cmd, "migratedisks", &opt) < 0) + goto out; + if (opt) { + char **val = NULL; + + val = virStringSplit(opt, ",", 0); + + if (virTypedParamsAddStringList(¶ms, + &nparams, + &maxparams, + VIR_MIGRATE_PARAM_MIGRATE_DISKS, + (const char **)val) < 0) { + VIR_FREE(val); + goto save_error; + } + + VIR_FREE(val); + } + if (vshCommandOptStringReq(ctl, cmd, "xml", &opt) < 0) goto out; if (opt) { diff --git a/tools/virsh.pod b/tools/virsh.pod index 1bb655b..aad0f3b 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1521,6 +1521,7 @@ to the I<uri> namespace is displayed instead of being modified. [I<--compressed>] [I<--abort-on-error>] [I<--auto-converge>] I<domain> I<desturi> [I<migrateuri>] [I<graphicsuri>] [I<listen-address>] [I<dname>] [I<--timeout> B<seconds>] [I<--xml> B<file>] +[I<--migratedisks> B<comma-separated-list>]
Migrate domain to another host. Add I<--live> for live migration; <--p2p> for peer-2-peer migration; I<--direct> for direct migration; or I<--tunnelled> @@ -1536,7 +1537,9 @@ with incremental copy (same base image shared between source and destination). In both cases the disk images have to exist on destination host, the I<--copy-storage-...> options only tell libvirt to transfer data from the images on source host to the images found at the same place on the destination -host. I<--change-protection> enforces that no incompatible configuration +host. By default only non-shared non-readonly images are transfered. Use +I<--migratedisks> to explicitly specify a list of images to transfer. +I<--change-protection> enforces that no incompatible configuration changes will be made to the domain while the migration is underway; this flag is implicitly enabled when supported by the hypervisor, but can be explicitly used to reject the migration if the hypervisor lacks change protection
We tend to keep info on argument format in a description rather than command definition. ACK Michal

On 21.05.2015 13:07, Pavel Boldin wrote:
The patchset represented in the mail thread implements the selective block device migration for the QEMU driver. This closes the referenced bug [1].
First the supplementary API implemented allowing for multiple key-values pair in the virTypedParameter arrays. This is used to pass the list of block devices to migrate in the following patches. Unit tests for this are added as well. This is the subject of the first four patches.
Fifth patch is adding the necessary parameter `migrate_disks' and passes it through the QEMU driver call stack. The introduced `qemuMigrateDisk' function is then checks that the disk is to be migrated because it is in the list. If there is no list specified then the legacy check is used: the device is migrate if it is not shared, not readonly and has a source.
Sixth and the last patch is adding the necessary code to the `virsh' utility making it able for user to specify a comma-separated list of the block device names that are to be migrated.
The implemented Python bindings patch is to be presented.
Changes in v2: * Addressed review comments * Removed all the duplicates check in the commit 'multi-value parameters in virTypedParamsAdd*' * reimplemented virTypedParamsPick as virTypedParamsFilter * renamed virTypedParamsPackStrings to virTypedParamsAddStringList
[1] https://bugzilla.redhat.com/show_bug.cgi?id=1203032
Pavel Boldin (6): util: multi-value virTypedParameter util: multi-value parameters in virTypedParamsAdd* util: virTypedParams{Filter,PickStrings} util: add virTypedParamsAddStringList qemu: migration: selective block device migration virsh: selective block device migration
include/libvirt/libvirt-domain.h | 9 ++ include/libvirt/libvirt-host.h | 16 +++ src/libvirt_public.syms | 7 + src/qemu/qemu_driver.c | 66 ++++++--- src/qemu/qemu_migration.c | 174 +++++++++++++++-------- src/qemu/qemu_migration.h | 23 ++-- src/util/virtypedparam.c | 263 ++++++++++++++++++++++++++++------- src/util/virtypedparam.h | 10 ++ tests/Makefile.am | 6 + tests/virtypedparamtest.c | 291 +++++++++++++++++++++++++++++++++++++++ tools/virsh-domain.c | 23 ++++ tools/virsh.pod | 5 +- 12 files changed, 750 insertions(+), 143 deletions(-) create mode 100644 tests/virtypedparamtest.c
So, I think we are almost there. I've pointed out a few things. Moreover, as I've been reviewing I keep fixing the nits I'm raising. So instead of me giving you headache of posting v3, I'll do that. Michal

Thank you. I will go through your comments just in case I'm going to push any other changes. Sorry for the inconvenience from my patches :-/. Pavel On Mon, May 25, 2015 at 5:58 PM, Michal Privoznik <mprivozn@redhat.com> wrote:
The patchset represented in the mail thread implements the selective block device migration for the QEMU driver. This closes the referenced bug [1].
First the supplementary API implemented allowing for multiple key-values
in the virTypedParameter arrays. This is used to pass the list of block devices to migrate in the following patches. Unit tests for this are added as well. This is the subject of the first four patches.
Fifth patch is adding the necessary parameter `migrate_disks' and passes it through the QEMU driver call stack. The introduced `qemuMigrateDisk' function is then checks that the disk is to be migrated because it is in the
there is no list specified then the legacy check is used: the device is migrate if it is not shared, not readonly and has a source.
Sixth and the last patch is adding the necessary code to the `virsh' utility making it able for user to specify a comma-separated list of the block device names that are to be migrated.
The implemented Python bindings patch is to be presented.
Changes in v2: * Addressed review comments * Removed all the duplicates check in the commit 'multi-value
On 21.05.2015 13:07, Pavel Boldin wrote: pair list. If parameters in
virTypedParamsAdd*' * reimplemented virTypedParamsPick as virTypedParamsFilter * renamed virTypedParamsPackStrings to virTypedParamsAddStringList
[1] https://bugzilla.redhat.com/show_bug.cgi?id=1203032
Pavel Boldin (6): util: multi-value virTypedParameter util: multi-value parameters in virTypedParamsAdd* util: virTypedParams{Filter,PickStrings} util: add virTypedParamsAddStringList qemu: migration: selective block device migration virsh: selective block device migration
include/libvirt/libvirt-domain.h | 9 ++ include/libvirt/libvirt-host.h | 16 +++ src/libvirt_public.syms | 7 + src/qemu/qemu_driver.c | 66 ++++++--- src/qemu/qemu_migration.c | 174 +++++++++++++++-------- src/qemu/qemu_migration.h | 23 ++-- src/util/virtypedparam.c | 263
++++++++++++++++++++++++++++-------
src/util/virtypedparam.h | 10 ++ tests/Makefile.am | 6 + tests/virtypedparamtest.c | 291 +++++++++++++++++++++++++++++++++++++++ tools/virsh-domain.c | 23 ++++ tools/virsh.pod | 5 +- 12 files changed, 750 insertions(+), 143 deletions(-) create mode 100644 tests/virtypedparamtest.c
So, I think we are almost there. I've pointed out a few things. Moreover, as I've been reviewing I keep fixing the nits I'm raising. So instead of me giving you headache of posting v3, I'll do that.
Michal

On 26.05.2015 01:16, Pavel Boldin wrote:
Thank you.
I will go through your comments just in case I'm going to push any other changes.
Yeah, more eyes more see.
Sorry for the inconvenience from my patches :-/.
No, it's okay. In fact your patches were good given that this would be your first libvirt contribution. Michal
participants (3)
-
Jiri Denemark
-
Michal Privoznik
-
Pavel Boldin