diff mbox

[v5,03/11] qom: support arbitrary non-scalar properties with -object

Message ID 1464885987-4039-4-git-send-email-berrange@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel P. Berrangé June 2, 2016, 4:46 p.m. UTC
The current -object command line syntax only allows for
creation of objects with scalar properties, or a list
with a fixed scalar element type. Objects which have
properties that are represented as structs in the QAPI
schema cannot be created using -object.

This is a design limitation of the way the OptsVisitor
is written. It simply iterates over the QemuOpts values
as a flat list. The support for lists is enabled by
allowing the same key to be repeated in the opts string.

It is not practical to extend the OptsVisitor to support
more complex data structures while also maintaining
the existing list handling behaviour that is relied upon
by other areas of QEMU.

Fortunately there is no existing object that implements
the UserCreatable interface that relies on the list
handling behaviour, so it is possible to swap out the
OptsVisitor for a different visitor implementation, so
-object supports non-scalar properties, thus leaving
other users of OptsVisitor unaffected.

The previously added qdict_crumple() method is able to
take a qdict containing a flat set of properties and
turn that into a arbitrarily nested set of dicts and
lists. By combining qemu_opts_to_qdict and qdict_crumple()
together, we can turn the opt string into a data structure
that is practically identical to that passed over QMP
when defining an object. The only difference is that all
the scalar values are represented as strings, rather than
strings, ints and bools. This is sufficient to let us
replace the OptsVisitor with the QMPInputVisitor for
use with -object.

Thus -object can now support non-scalar properties,
for example the QMP object

  {
    "execute": "object-add",
    "arguments": {
      "qom-type": "demo",
      "id": "demo0",
      "parameters": {
        "foo": [
          { "bar": "one", "wizz": "1" },
          { "bar": "two", "wizz": "2" }
        ]
      }
    }
  }

Would be creatable via the CLI now using

    $QEMU \
      -object demo,id=demo0,\
              foo.0.bar=one,foo.0.wizz=1,\
              foo.1.bar=two,foo.1.wizz=2

Notice that this syntax is intentionally compatible
with that currently used by block drivers.

This is also wired up to work for the 'object_add' command
in the HMP monitor with the same syntax.

  (hmp) object_add demo,id=demo0,\
                   foo.0.bar=one,foo.0.wizz=1,\
                   foo.1.bar=two,foo.1.wizz=2

NB indentation should not be used with HMP commands, this
is just for convenient formatting in this commit message.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 hmp.c                           |  18 +--
 include/qom/object_interfaces.h |  10 +-
 qmp.c                           |   2 +-
 qom/object_interfaces.c         |  49 +++++--
 tests/check-qom-proplist.c      | 317 +++++++++++++++++++++++++++++++++++++++-
 5 files changed, 363 insertions(+), 33 deletions(-)

Comments

Markus Armbruster June 9, 2016, 2:43 p.m. UTC | #1
"Daniel P. Berrange" <berrange@redhat.com> writes:

> The current -object command line syntax only allows for
> creation of objects with scalar properties, or a list
> with a fixed scalar element type. Objects which have
> properties that are represented as structs in the QAPI
> schema cannot be created using -object.
>
> This is a design limitation of the way the OptsVisitor
> is written. It simply iterates over the QemuOpts values
> as a flat list. The support for lists is enabled by
> allowing the same key to be repeated in the opts string.
>
> It is not practical to extend the OptsVisitor to support
> more complex data structures while also maintaining
> the existing list handling behaviour that is relied upon
> by other areas of QEMU.

It should be practical to create a new option input visitor that parses
command line syntax straight into a QAPI visit, without the list magic,
and with fewer or no restrictions.  Then we could start defining complex
command line arguments as QAPI types, parse them straight into generated
QAPI types, and dispense with the QDict wrangling.

But I can't blame you for solving a simpler problem instead.  In
particular since there's ample precedence for QDict wrangling.

> Fortunately there is no existing object that implements
> the UserCreatable interface that relies on the list
> handling behaviour, so it is possible to swap out the
> OptsVisitor for a different visitor implementation, so
> -object supports non-scalar properties, thus leaving
> other users of OptsVisitor unaffected.
>
> The previously added qdict_crumple() method is able to
> take a qdict containing a flat set of properties and
> turn that into a arbitrarily nested set of dicts and
> lists. By combining qemu_opts_to_qdict and qdict_crumple()
> together, we can turn the opt string into a data structure
> that is practically identical to that passed over QMP
> when defining an object. The only difference is that all
> the scalar values are represented as strings, rather than
> strings, ints and bools. This is sufficient to let us
> replace the OptsVisitor with the QMPInputVisitor for
> use with -object.
>
> Thus -object can now support non-scalar properties,
> for example the QMP object
>
>   {
>     "execute": "object-add",
>     "arguments": {
>       "qom-type": "demo",
>       "id": "demo0",
>       "parameters": {
>         "foo": [
>           { "bar": "one", "wizz": "1" },
>           { "bar": "two", "wizz": "2" }
>         ]
>       }
>     }
>   }
>
> Would be creatable via the CLI now using
>
>     $QEMU \
>       -object demo,id=demo0,\
>               foo.0.bar=one,foo.0.wizz=1,\
>               foo.1.bar=two,foo.1.wizz=2
>
> Notice that this syntax is intentionally compatible
> with that currently used by block drivers.
>
> This is also wired up to work for the 'object_add' command
> in the HMP monitor with the same syntax.
>
>   (hmp) object_add demo,id=demo0,\
>                    foo.0.bar=one,foo.0.wizz=1,\
>                    foo.1.bar=two,foo.1.wizz=2
>
> NB indentation should not be used with HMP commands, this
> is just for convenient formatting in this commit message.

Cannot be used, actually (and neither can line continuation), but I get
what you mean.

> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  hmp.c                           |  18 +--
>  include/qom/object_interfaces.h |  10 +-
>  qmp.c                           |   2 +-
>  qom/object_interfaces.c         |  49 +++++--
>  tests/check-qom-proplist.c      | 317 +++++++++++++++++++++++++++++++++++++++-
>  5 files changed, 363 insertions(+), 33 deletions(-)
>
> diff --git a/hmp.c b/hmp.c
> index a4b1d3d..1972bef 100644
> --- a/hmp.c
> +++ b/hmp.c
> @@ -25,7 +25,7 @@
>  #include "qemu/sockets.h"
>  #include "monitor/monitor.h"
>  #include "monitor/qdev.h"
> -#include "qapi/opts-visitor.h"
> +#include "qapi/qmp-input-visitor.h"
>  #include "qapi/qmp/qerror.h"
>  #include "qapi/string-output-visitor.h"
>  #include "qapi/util.h"
> @@ -1717,20 +1717,12 @@ void hmp_netdev_del(Monitor *mon, const QDict *qdict)
>  void hmp_object_add(Monitor *mon, const QDict *qdict)
>  {
>      Error *err = NULL;
> -    QemuOpts *opts;
> -    OptsVisitor *ov;
> +    QmpInputVisitor *qiv;
>      Object *obj = NULL;
>  
> -    opts = qemu_opts_from_qdict(qemu_find_opts("object"), qdict, &err);
> -    if (err) {
> -        hmp_handle_error(mon, &err);
> -        return;
> -    }
> -
> -    ov = opts_visitor_new(opts);
> -    obj = user_creatable_add(qdict, opts_get_visitor(ov), &err);
> -    opts_visitor_cleanup(ov);
> -    qemu_opts_del(opts);
> +    qiv = qmp_input_visitor_new((QObject *)qdict, true, true);
> +    obj = user_creatable_add(qdict, qmp_input_get_visitor(qiv), &err);
> +    qmp_input_visitor_cleanup(qiv);

Any patch that gets rid of a qemu_opts_from_qdict() deserves support.

>  
>      if (err) {
>          hmp_handle_error(mon, &err);
> diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
> index 8b17f4d..997563a 100644
> --- a/include/qom/object_interfaces.h
> +++ b/include/qom/object_interfaces.h
> @@ -96,18 +96,24 @@ Object *user_creatable_add(const QDict *qdict,
>   * user_creatable_add_type:
>   * @type: the object type name
>   * @id: the unique ID for the object
> + * @nested: whether to recurse into the visitor for properties
>   * @qdict: the object properties
>   * @v: the visitor
>   * @errp: if an error occurs, a pointer to an area to store the error
>   *
>   * Create an instance of the user creatable object @type, placing
>   * it in the object composition tree with name @id, initializing
> - * it with properties from @qdict
> + * it with properties from @qdict.
> + *
> + * If the visitor is already positioned to read the properties
> + * in @qdict, @nested should be false. Conversely, if it is
> + * neccessary to open/close a struct to read the properties in

necessary

> + * @qdict, @nested should be true.

I don't like this interface much, but I'm not the maintainer here.  Have
you considered creating a function that does nested=false and can be
used by the nested=true function?

>   *
>   * Returns: the newly created object or NULL on error
>   */
>  Object *user_creatable_add_type(const char *type, const char *id,
> -                                const QDict *qdict,
> +                                bool nested, const QDict *qdict,
>                                  Visitor *v, Error **errp);
>  
>  /**
> diff --git a/qmp.c b/qmp.c
> index dcd0953..dc5fff0 100644
> --- a/qmp.c
> +++ b/qmp.c
> @@ -666,7 +666,7 @@ void qmp_object_add(const char *type, const char *id,
>      }
>  
>      qiv = qmp_input_visitor_new(props, true, false);
> -    obj = user_creatable_add_type(type, id, pdict,
> +    obj = user_creatable_add_type(type, id, true, pdict,
>                                    qmp_input_get_visitor(qiv), errp);
>      qmp_input_visitor_cleanup(qiv);
>      if (obj) {
> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index 51e62e2..0883c91 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -3,6 +3,7 @@
>  #include "qom/object_interfaces.h"
>  #include "qemu/module.h"
>  #include "qapi-visit.h"
> +#include "qapi/qmp-input-visitor.h"
>  #include "qapi/qmp-output-visitor.h"
>  #include "qapi/opts-visitor.h"
>  
> @@ -63,12 +64,16 @@ Object *user_creatable_add(const QDict *qdict,
>      if (local_err) {
>          goto out_visit;
>      }
> -    visit_check_struct(v, &local_err);
> +
> +    obj = user_creatable_add_type(type, id, false, pdict, v, &local_err);
>      if (local_err) {
>          goto out_visit;
>      }
>  
> -    obj = user_creatable_add_type(type, id, pdict, v, &local_err);
> +    visit_check_struct(v, &local_err);
> +    if (local_err) {
> +        goto out_visit;
> +    }
>  
>  out_visit:
>      visit_end_struct(v);
> @@ -87,7 +92,7 @@ out:
>  
>  
>  Object *user_creatable_add_type(const char *type, const char *id,
> -                                const QDict *qdict,
> +                                bool nested, const QDict *qdict,
>                                  Visitor *v, Error **errp)
>  {
>      Object *obj;
> @@ -114,9 +119,11 @@ Object *user_creatable_add_type(const char *type, const char *id,
>  
>      assert(qdict);
>      obj = object_new(type);
> -    visit_start_struct(v, NULL, NULL, 0, &local_err);
> -    if (local_err) {
> -        goto out;
> +    if (nested) {
> +        visit_start_struct(v, NULL, NULL, 0, &local_err);
> +        if (local_err) {
> +            goto out;
> +        }
>      }
>      for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
>          object_property_set(obj, v, e->key, &local_err);
> @@ -124,12 +131,14 @@ Object *user_creatable_add_type(const char *type, const char *id,
>              break;
>          }
>      }
> -    if (!local_err) {
> -        visit_check_struct(v, &local_err);
> -    }
> -    visit_end_struct(v);
> -    if (local_err) {
> -        goto out;
> +    if (nested) {
> +        if (!local_err) {
> +            visit_check_struct(v, &local_err);
> +        }
> +        visit_end_struct(v);
> +        if (local_err) {
> +            goto out;
> +        }
>      }
>  
>      object_property_add_child(object_get_objects_root(),
> @@ -156,15 +165,23 @@ out:
>  
>  Object *user_creatable_add_opts(QemuOpts *opts, Error **errp)
>  {
> -    OptsVisitor *ov;
> +    QmpInputVisitor *qiv;
>      QDict *pdict;
> +    QObject *pobj;
>      Object *obj = NULL;
>  
> -    ov = opts_visitor_new(opts);
>      pdict = qemu_opts_to_qdict(opts, NULL);
>  
> -    obj = user_creatable_add(pdict, opts_get_visitor(ov), errp);
> -    opts_visitor_cleanup(ov);
> +    pobj = qdict_crumple(pdict, true, errp);
> +    if (!pobj) {
> +        goto cleanup;
> +    }
> +    qiv = qmp_input_visitor_new(pobj, true, true);
> +
> +    obj = user_creatable_add((QDict *)pobj, qmp_input_get_visitor(qiv), errp);
> +    qmp_input_visitor_cleanup(qiv);
> +    qobject_decref(pobj);
> + cleanup:
>      QDECREF(pdict);
>      return obj;
>  }
[...]

I'm not familiar enough with this code to provide a real review with
reasonable effort.  It looks sane to ignorant me.
Daniel P. Berrangé June 14, 2016, 2:16 p.m. UTC | #2
On Thu, Jun 09, 2016 at 04:43:35PM +0200, Markus Armbruster wrote:
> "Daniel P. Berrange" <berrange@redhat.com> writes:
> 
> > The current -object command line syntax only allows for
> > creation of objects with scalar properties, or a list
> > with a fixed scalar element type. Objects which have
> > properties that are represented as structs in the QAPI
> > schema cannot be created using -object.
> >
> > This is a design limitation of the way the OptsVisitor
> > is written. It simply iterates over the QemuOpts values
> > as a flat list. The support for lists is enabled by
> > allowing the same key to be repeated in the opts string.
> >
> > It is not practical to extend the OptsVisitor to support
> > more complex data structures while also maintaining
> > the existing list handling behaviour that is relied upon
> > by other areas of QEMU.
> 
> It should be practical to create a new option input visitor that parses
> command line syntax straight into a QAPI visit, without the list magic,
> and with fewer or no restrictions.  Then we could start defining complex
> command line arguments as QAPI types, parse them straight into generated
> QAPI types, and dispense with the QDict wrangling.

Funnily enough I did actually start out trying to implement pretty
much exactly that. I found that in my new opts visitor, I was essentially
parsing the QemuOpts into QDict/QLists to handle the recursion from the
visitor. At this point I was starting to duplicate much code from QDict
and QmpInputVisitor, so it abandoned that approach and went for the more
general QemuOpts -> QDict crumping instead, since it the goal to be
achieved with simpler code overall.

Regards,
Daniel
diff mbox

Patch

diff --git a/hmp.c b/hmp.c
index a4b1d3d..1972bef 100644
--- a/hmp.c
+++ b/hmp.c
@@ -25,7 +25,7 @@ 
 #include "qemu/sockets.h"
 #include "monitor/monitor.h"
 #include "monitor/qdev.h"
-#include "qapi/opts-visitor.h"
+#include "qapi/qmp-input-visitor.h"
 #include "qapi/qmp/qerror.h"
 #include "qapi/string-output-visitor.h"
 #include "qapi/util.h"
@@ -1717,20 +1717,12 @@  void hmp_netdev_del(Monitor *mon, const QDict *qdict)
 void hmp_object_add(Monitor *mon, const QDict *qdict)
 {
     Error *err = NULL;
-    QemuOpts *opts;
-    OptsVisitor *ov;
+    QmpInputVisitor *qiv;
     Object *obj = NULL;
 
-    opts = qemu_opts_from_qdict(qemu_find_opts("object"), qdict, &err);
-    if (err) {
-        hmp_handle_error(mon, &err);
-        return;
-    }
-
-    ov = opts_visitor_new(opts);
-    obj = user_creatable_add(qdict, opts_get_visitor(ov), &err);
-    opts_visitor_cleanup(ov);
-    qemu_opts_del(opts);
+    qiv = qmp_input_visitor_new((QObject *)qdict, true, true);
+    obj = user_creatable_add(qdict, qmp_input_get_visitor(qiv), &err);
+    qmp_input_visitor_cleanup(qiv);
 
     if (err) {
         hmp_handle_error(mon, &err);
diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
index 8b17f4d..997563a 100644
--- a/include/qom/object_interfaces.h
+++ b/include/qom/object_interfaces.h
@@ -96,18 +96,24 @@  Object *user_creatable_add(const QDict *qdict,
  * user_creatable_add_type:
  * @type: the object type name
  * @id: the unique ID for the object
+ * @nested: whether to recurse into the visitor for properties
  * @qdict: the object properties
  * @v: the visitor
  * @errp: if an error occurs, a pointer to an area to store the error
  *
  * Create an instance of the user creatable object @type, placing
  * it in the object composition tree with name @id, initializing
- * it with properties from @qdict
+ * it with properties from @qdict.
+ *
+ * If the visitor is already positioned to read the properties
+ * in @qdict, @nested should be false. Conversely, if it is
+ * neccessary to open/close a struct to read the properties in
+ * @qdict, @nested should be true.
  *
  * Returns: the newly created object or NULL on error
  */
 Object *user_creatable_add_type(const char *type, const char *id,
-                                const QDict *qdict,
+                                bool nested, const QDict *qdict,
                                 Visitor *v, Error **errp);
 
 /**
diff --git a/qmp.c b/qmp.c
index dcd0953..dc5fff0 100644
--- a/qmp.c
+++ b/qmp.c
@@ -666,7 +666,7 @@  void qmp_object_add(const char *type, const char *id,
     }
 
     qiv = qmp_input_visitor_new(props, true, false);
-    obj = user_creatable_add_type(type, id, pdict,
+    obj = user_creatable_add_type(type, id, true, pdict,
                                   qmp_input_get_visitor(qiv), errp);
     qmp_input_visitor_cleanup(qiv);
     if (obj) {
diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index 51e62e2..0883c91 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -3,6 +3,7 @@ 
 #include "qom/object_interfaces.h"
 #include "qemu/module.h"
 #include "qapi-visit.h"
+#include "qapi/qmp-input-visitor.h"
 #include "qapi/qmp-output-visitor.h"
 #include "qapi/opts-visitor.h"
 
@@ -63,12 +64,16 @@  Object *user_creatable_add(const QDict *qdict,
     if (local_err) {
         goto out_visit;
     }
-    visit_check_struct(v, &local_err);
+
+    obj = user_creatable_add_type(type, id, false, pdict, v, &local_err);
     if (local_err) {
         goto out_visit;
     }
 
-    obj = user_creatable_add_type(type, id, pdict, v, &local_err);
+    visit_check_struct(v, &local_err);
+    if (local_err) {
+        goto out_visit;
+    }
 
 out_visit:
     visit_end_struct(v);
@@ -87,7 +92,7 @@  out:
 
 
 Object *user_creatable_add_type(const char *type, const char *id,
-                                const QDict *qdict,
+                                bool nested, const QDict *qdict,
                                 Visitor *v, Error **errp)
 {
     Object *obj;
@@ -114,9 +119,11 @@  Object *user_creatable_add_type(const char *type, const char *id,
 
     assert(qdict);
     obj = object_new(type);
-    visit_start_struct(v, NULL, NULL, 0, &local_err);
-    if (local_err) {
-        goto out;
+    if (nested) {
+        visit_start_struct(v, NULL, NULL, 0, &local_err);
+        if (local_err) {
+            goto out;
+        }
     }
     for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
         object_property_set(obj, v, e->key, &local_err);
@@ -124,12 +131,14 @@  Object *user_creatable_add_type(const char *type, const char *id,
             break;
         }
     }
-    if (!local_err) {
-        visit_check_struct(v, &local_err);
-    }
-    visit_end_struct(v);
-    if (local_err) {
-        goto out;
+    if (nested) {
+        if (!local_err) {
+            visit_check_struct(v, &local_err);
+        }
+        visit_end_struct(v);
+        if (local_err) {
+            goto out;
+        }
     }
 
     object_property_add_child(object_get_objects_root(),
@@ -156,15 +165,23 @@  out:
 
 Object *user_creatable_add_opts(QemuOpts *opts, Error **errp)
 {
-    OptsVisitor *ov;
+    QmpInputVisitor *qiv;
     QDict *pdict;
+    QObject *pobj;
     Object *obj = NULL;
 
-    ov = opts_visitor_new(opts);
     pdict = qemu_opts_to_qdict(opts, NULL);
 
-    obj = user_creatable_add(pdict, opts_get_visitor(ov), errp);
-    opts_visitor_cleanup(ov);
+    pobj = qdict_crumple(pdict, true, errp);
+    if (!pobj) {
+        goto cleanup;
+    }
+    qiv = qmp_input_visitor_new(pobj, true, true);
+
+    obj = user_creatable_add((QDict *)pobj, qmp_input_get_visitor(qiv), errp);
+    qmp_input_visitor_cleanup(qiv);
+    qobject_decref(pobj);
+ cleanup:
     QDECREF(pdict);
     return obj;
 }
diff --git a/tests/check-qom-proplist.c b/tests/check-qom-proplist.c
index ffffd87..de37d3a 100644
--- a/tests/check-qom-proplist.c
+++ b/tests/check-qom-proplist.c
@@ -24,6 +24,14 @@ 
 #include "qapi/error.h"
 #include "qom/object.h"
 #include "qemu/module.h"
+#include "qapi/visitor.h"
+#include "qom/object_interfaces.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "qapi/qmp/qjson.h"
+#include "qapi/qmp-input-visitor.h"
+#include "qapi-visit.h"
+#include "qapi/dealloc-visitor.h"
 
 
 #define TYPE_DUMMY "qemu-dummy"
@@ -31,6 +39,11 @@ 
 typedef struct DummyObject DummyObject;
 typedef struct DummyObjectClass DummyObjectClass;
 
+typedef struct DummyPerson DummyPerson;
+typedef struct DummyAddr DummyAddr;
+typedef struct DummyAddrList DummyAddrList;
+typedef struct DummySizeList DummySizeList;
+
 #define DUMMY_OBJECT(obj)                               \
     OBJECT_CHECK(DummyObject, (obj), TYPE_DUMMY)
 
@@ -51,12 +64,35 @@  static const char *const dummy_animal_map[DUMMY_LAST + 1] = {
     [DUMMY_LAST] = NULL,
 };
 
+
+struct DummyAddr {
+    char *ip;
+    int64_t prefix;
+    bool ipv6only;
+};
+
+struct DummyAddrList {
+    DummyAddrList *next;
+    struct DummyAddr *value;
+};
+
+struct DummyPerson {
+    char *name;
+    int64_t age;
+};
+
 struct DummyObject {
     Object parent_obj;
 
     bool bv;
     DummyAnimal av;
     char *sv;
+
+    intList *sizes;
+
+    DummyPerson *person;
+
+    DummyAddrList *addrs;
 };
 
 struct DummyObjectClass {
@@ -118,6 +154,157 @@  static char *dummy_get_sv(Object *obj,
     return g_strdup(dobj->sv);
 }
 
+static void visit_type_DummyPerson_fields(Visitor *v, DummyPerson **obj,
+                                          Error **errp)
+{
+    Error *err = NULL;
+
+    visit_type_str(v, "name", &(*obj)->name, &err);
+    if (err) {
+        goto out;
+    }
+    visit_type_int(v, "age", &(*obj)->age, &err);
+    if (err) {
+        goto out;
+    }
+
+out:
+    error_propagate(errp, err);
+}
+
+static void visit_type_DummyPerson(Visitor *v, const char *name,
+                                   DummyPerson **obj, Error **errp)
+{
+    Error *err = NULL;
+
+    visit_start_struct(v, name, (void **)obj, sizeof(DummyPerson), &err);
+    if (err) {
+        goto out;
+    }
+    if (!*obj) {
+        goto out_obj;
+    }
+    visit_type_DummyPerson_fields(v, obj, &err);
+    error_propagate(errp, err);
+    err = NULL;
+out_obj:
+    visit_end_struct(v);
+out:
+    error_propagate(errp, err);
+}
+
+static void visit_type_DummyAddr_members(Visitor *v, DummyAddr **obj,
+                                         Error **errp)
+{
+    Error *err = NULL;
+
+    visit_type_str(v, "ip", &(*obj)->ip, &err);
+    if (err) {
+        goto out;
+    }
+    visit_type_int(v, "prefix", &(*obj)->prefix, &err);
+    if (err) {
+        goto out;
+    }
+    visit_type_bool(v, "ipv6only", &(*obj)->ipv6only, &err);
+    if (err) {
+        goto out;
+    }
+
+out:
+    error_propagate(errp, err);
+}
+
+static void visit_type_DummyAddr(Visitor *v, const char *name,
+                                 DummyAddr **obj, Error **errp)
+{
+    Error *err = NULL;
+
+    visit_start_struct(v, name, (void **)obj, sizeof(DummyAddr), &err);
+    if (err) {
+        goto out;
+    }
+    if (!*obj) {
+        goto out_obj;
+    }
+    visit_type_DummyAddr_members(v, obj, &err);
+    error_propagate(errp, err);
+    err = NULL;
+out_obj:
+    visit_end_struct(v);
+out:
+    error_propagate(errp, err);
+}
+
+static void qapi_free_DummyAddrList(DummyAddrList *obj);
+
+static void visit_type_DummyAddrList(Visitor *v, const char *name,
+                                     DummyAddrList **obj, Error **errp)
+{
+    Error *err = NULL;
+    DummyAddrList *tail;
+    size_t size = sizeof(**obj);
+
+    visit_start_list(v, name, (GenericList **)obj, size, &err);
+    if (err) {
+        goto out;
+    }
+
+    for (tail = *obj; tail;
+         tail = (DummyAddrList *)visit_next_list(v, (GenericList *)tail, size)) {
+        visit_type_DummyAddr(v, NULL, &tail->value, &err);
+        if (err) {
+            break;
+        }
+    }
+
+    visit_end_list(v);
+    if (err && visit_is_input(v)) {
+        qapi_free_DummyAddrList(*obj);
+        *obj = NULL;
+    }
+out:
+    error_propagate(errp, err);
+}
+
+static void qapi_free_DummyAddrList(DummyAddrList *obj)
+{
+    QapiDeallocVisitor *qdv;
+    Visitor *v;
+
+    if (!obj) {
+        return;
+    }
+
+    qdv = qapi_dealloc_visitor_new();
+    v = qapi_dealloc_get_visitor(qdv);
+    visit_type_DummyAddrList(v, NULL, &obj, NULL);
+    qapi_dealloc_visitor_cleanup(qdv);
+}
+
+static void dummy_set_sizes(Object *obj, Visitor *v, const char *name,
+                            void *opaque, Error **errp)
+{
+    DummyObject *dobj = DUMMY_OBJECT(obj);
+
+    visit_type_intList(v, name, &dobj->sizes, errp);
+}
+
+static void dummy_set_person(Object *obj, Visitor *v, const char *name,
+                            void *opaque, Error **errp)
+{
+    DummyObject *dobj = DUMMY_OBJECT(obj);
+
+    visit_type_DummyPerson(v, name, &dobj->person, errp);
+}
+
+static void dummy_set_addrs(Object *obj, Visitor *v, const char *name,
+                            void *opaque, Error **errp)
+{
+    DummyObject *dobj = DUMMY_OBJECT(obj);
+
+    visit_type_DummyAddrList(v, name, &dobj->addrs, errp);
+}
 
 static void dummy_init(Object *obj)
 {
@@ -127,9 +314,16 @@  static void dummy_init(Object *obj)
                              NULL);
 }
 
+static void
+dummy_complete(UserCreatable *uc, Error **errp)
+{
+}
 
 static void dummy_class_init(ObjectClass *cls, void *data)
 {
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(cls);
+    ucc->complete = dummy_complete;
+
     object_class_property_add_bool(cls, "bv",
                                    dummy_get_bv,
                                    dummy_set_bv,
@@ -144,12 +338,36 @@  static void dummy_class_init(ObjectClass *cls, void *data)
                                    dummy_get_av,
                                    dummy_set_av,
                                    NULL);
+    object_class_property_add(cls, "sizes",
+                              "int[]",
+                              NULL,
+                              dummy_set_sizes,
+                              NULL, NULL, NULL);
+    object_class_property_add(cls, "person",
+                              "DummyPerson",
+                              NULL,
+                              dummy_set_person,
+                              NULL, NULL, NULL);
+    object_class_property_add(cls, "addrs",
+                              "DummyAddrList",
+                              NULL,
+                              dummy_set_addrs,
+                              NULL, NULL, NULL);
 }
 
 
 static void dummy_finalize(Object *obj)
 {
     DummyObject *dobj = DUMMY_OBJECT(obj);
+    QapiDeallocVisitor *qdv;
+    Visitor *v;
+
+    qdv = qapi_dealloc_visitor_new();
+    v = qapi_dealloc_get_visitor(qdv);
+    visit_type_intList(v, NULL, &dobj->sizes, NULL);
+    visit_type_DummyAddrList(v, NULL, &dobj->addrs, NULL);
+    visit_type_DummyPerson(v, NULL, &dobj->person, NULL);
+    qapi_dealloc_visitor_cleanup(qdv);
 
     g_free(dobj->sv);
 }
@@ -163,6 +381,10 @@  static const TypeInfo dummy_info = {
     .instance_finalize = dummy_finalize,
     .class_size = sizeof(DummyObjectClass),
     .class_init = dummy_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
 };
 
 
@@ -458,7 +680,9 @@  static void test_dummy_iterator(void)
 
     ObjectProperty *prop;
     ObjectPropertyIterator iter;
-    bool seenbv = false, seensv = false, seenav = false, seentype;
+    bool seenbv = false, seensv = false, seenav = false,
+        seentype = false, seenaddrs = false, seenperson = false,
+        seensizes = false;
 
     object_property_iter_init(&iter, OBJECT(dobj));
     while ((prop = object_property_iter_next(&iter))) {
@@ -471,6 +695,12 @@  static void test_dummy_iterator(void)
         } else if (g_str_equal(prop->name, "type")) {
             /* This prop comes from the base Object class */
             seentype = true;
+        } else if (g_str_equal(prop->name, "addrs")) {
+            seenaddrs = true;
+        } else if (g_str_equal(prop->name, "person")) {
+            seenperson = true;
+        } else if (g_str_equal(prop->name, "sizes")) {
+            seensizes = true;
         } else {
             g_printerr("Found prop '%s'\n", prop->name);
             g_assert_not_reached();
@@ -480,6 +710,9 @@  static void test_dummy_iterator(void)
     g_assert(seenav);
     g_assert(seensv);
     g_assert(seentype);
+    g_assert(seenaddrs);
+    g_assert(seenperson);
+    g_assert(seensizes);
 
     object_unparent(OBJECT(dobj));
 }
@@ -498,11 +731,91 @@  static void test_dummy_delchild(void)
     object_unparent(OBJECT(dev));
 }
 
+
+static QemuOptsList dummy_opts = {
+    .name = "object",
+    .implied_opt_name = "qom-type",
+    .head = QTAILQ_HEAD_INITIALIZER(dummy_opts.head),
+    .desc = {
+        { }
+    },
+};
+
+static void test_dummy_create_complex(DummyObject *dummy)
+{
+    g_assert(dummy->person != NULL);
+    g_assert_cmpstr(dummy->person->name, ==, "fred");
+    g_assert_cmpint(dummy->person->age, ==, 52);
+
+    g_assert(dummy->sizes != NULL);
+    g_assert_cmpint(dummy->sizes->value, ==, 12);
+    g_assert_cmpint(dummy->sizes->next->value, ==, 65);
+    g_assert_cmpint(dummy->sizes->next->next->value, ==, 8139);
+
+    g_assert(dummy->addrs != NULL);
+    g_assert_cmpstr(dummy->addrs->value->ip, ==, "127.0.0.1");
+    g_assert_cmpint(dummy->addrs->value->prefix, ==, 24);
+    g_assert(dummy->addrs->value->ipv6only);
+    g_assert_cmpstr(dummy->addrs->next->value->ip, ==, "0.0.0.0");
+    g_assert_cmpint(dummy->addrs->next->value->prefix, ==, 16);
+    g_assert(!dummy->addrs->next->value->ipv6only);
+}
+
+
+static void test_dummy_createopts(void)
+{
+    const char *optstr = "qemu-dummy,id=dummy0,bv=yes,av=alligator,sv=hiss,"
+        "person.name=fred,person.age=52,sizes.0=12,sizes.1=65,sizes.2=8139,"
+        "addrs.0.ip=127.0.0.1,addrs.0.prefix=24,addrs.0.ipv6only=yes,"
+        "addrs.1.ip=0.0.0.0,addrs.1.prefix=16,addrs.1.ipv6only=no";
+    QemuOpts *opts;
+    DummyObject *dummy;
+
+    opts = qemu_opts_parse_noisily(&dummy_opts,
+                                   optstr, true);
+    g_assert(opts != NULL);
+
+    dummy = DUMMY_OBJECT(user_creatable_add_opts(opts, &error_abort));
+
+    test_dummy_create_complex(dummy);
+
+    object_unparent(OBJECT(dummy));
+    object_unref(OBJECT(dummy));
+}
+
+
+static void test_dummy_createqmp(void)
+{
+    const char *jsonstr =
+        "{ 'qom-type': 'qemu-dummy', 'id': 'dummy0', "
+        "  'bv': true, 'av': 'alligator', 'sv': 'hiss', "
+        "  'person': { 'name': 'fred', 'age': 52 }, "
+        "  'sizes': [12, 65, 8139], "
+        "  'addrs': [ { 'ip': '127.0.0.1', 'prefix': 24, 'ipv6only': true }, "
+        "             { 'ip': '0.0.0.0', 'prefix': 16, 'ipv6only': false } ] }";
+    QObject *obj = qobject_from_json(jsonstr);
+    QmpInputVisitor *qiv = qmp_input_visitor_new(obj, true, false);
+    DummyObject *dummy;
+    g_assert(obj);
+    dummy = DUMMY_OBJECT(user_creatable_add(qobject_to_qdict(obj),
+                                            qmp_input_get_visitor(qiv),
+                                            &error_abort));
+
+    test_dummy_create_complex(dummy);
+    qmp_input_visitor_cleanup(qiv);
+    object_unparent(OBJECT(dummy));
+    object_unref(OBJECT(dummy));
+    qobject_decref(obj);
+}
+
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
 
     module_call_init(MODULE_INIT_QOM);
+
+    qemu_add_opts(&dummy_opts);
+
     type_register_static(&dummy_info);
     type_register_static(&dummy_dev_info);
     type_register_static(&dummy_bus_info);
@@ -514,6 +827,8 @@  int main(int argc, char **argv)
     g_test_add_func("/qom/proplist/getenum", test_dummy_getenum);
     g_test_add_func("/qom/proplist/iterator", test_dummy_iterator);
     g_test_add_func("/qom/proplist/delchild", test_dummy_delchild);
+    g_test_add_func("/qom/proplist/createopts", test_dummy_createopts);
+    g_test_add_func("/qom/proplist/createqmp", test_dummy_createqmp);
 
     return g_test_run();
 }