[libvirt] [PATCH 1/5] util: multi-value virTypedParameter

Michal Privoznik mprivozn at redhat.com
Thu May 14 15:18:40 UTC 2015


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 at 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




More information about the libvir-list mailing list