diff mbox

[v3,03/10] qom: support arbitrary non-scalar properties with -object

Message ID 1457636396-24983-3-git-send-email-berrange@redhat.com
State New
Headers show

Commit Message

Daniel P. Berrangé March 10, 2016, 6:59 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

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

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 hmp.c                      |  18 +--
 qom/object_interfaces.c    |  20 ++-
 tests/check-qom-proplist.c | 295 ++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 313 insertions(+), 20 deletions(-)

Comments

Eric Blake March 21, 2016, 11:27 p.m. UTC | #1
On 03/10/2016 11:59 AM, Daniel P. Berrange wrote:
> 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.

Zoltán Kővágó tried earlier with his GSoC patches for the audio
subsystem last year, but those got stalled waiting for qapi enhancements
to go in.  But I think your approach is indeed a bit nicer (rather than
making the warty OptsVisitor even wartier, just avoid it).

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

Indeed, nice replacement.

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

Maybe mention that the indentation is not actually present in the real
command lines typed.

> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  hmp.c                      |  18 +--
>  qom/object_interfaces.c    |  20 ++-
>  tests/check-qom-proplist.c | 295 ++++++++++++++++++++++++++++++++++++++++++++-
>  3 files changed, 313 insertions(+), 20 deletions(-)
> 

> @@ -120,6 +120,7 @@ Object *user_creatable_add_type(const char *type, const char *id,
>      obj = object_new(type);
>      if (qdict) {
>          for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
> +
>              object_property_set(obj, v, e->key, &local_err);
>              if (local_err) {
>                  goto out;

Spurious hunk?
Markus Armbruster March 22, 2016, 9:07 a.m. UTC | #2
Eric Blake <eblake@redhat.com> writes:

> On 03/10/2016 11:59 AM, Daniel P. Berrange wrote:
>> 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.
>
> Zoltán Kővágó tried earlier with his GSoC patches for the audio
> subsystem last year, but those got stalled waiting for qapi enhancements
> to go in.

Yet another series stalled on the big QAPI rework.  Hitting a GSoC
student that way is really unfortunate.

>            But I think your approach is indeed a bit nicer (rather than
> making the warty OptsVisitor even wartier, just avoid it).

QemuOpts defines an important part of the command line language, namely
(most of) the syntax of many option arguments.  It parses them into a
set of (name, value) pairs.

"Most of", because additional syntax may hide in the parameter value.

Parameter values are typed, except when they aren't.  Types are limited
to string, bool, uint64_t number (accepts negative numbers and casts
them) and uint64_t size (rejects negative numbers, accepts suffixes).

OptsVisitor adds special list syntax.  It's used with untyped values.

Bypassing OptsVisitor risks adding different special syntax.  Doesn't
mean it's a bad idea, only that we need to keep close watch on what it
does to the language.  See below.

I merely scratched the surprising amount of complexity that has accrued
over time.  If you doubt me, study how the merge_lists flag works, and
how it interacts with the several ways we do defaults.

The gap between QemuOpts' and QAPI's type system is obvious.  But it's
not just a gap, it's also contraditions: QAPI does only *signed*
integers.

Nevertheless, the path from command line to QAPI is through QemuOpts for
now.

Aside: for historical reasons, we have a few QMP commands that detour
through QemuOpts.  Mistakes, do not add more.

Ceterum censeo QemuOpts esse delendam.  And I say that as author of 35
out of 117 commits touching qemu-option.c.

>> 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.
>
> Indeed, nice replacement.
>
>> 
>> 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

Okay, this adds a bare minimum of syntax, and it's not even new: we use
similar syntax for block stuff already.

>> 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
>
> Maybe mention that the indentation is not actually present in the real
> command lines typed.
>
>> 
>> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
>> ---
>>  hmp.c                      |  18 +--
>>  qom/object_interfaces.c    |  20 ++-
>>  tests/check-qom-proplist.c | 295 ++++++++++++++++++++++++++++++++++++++++++++-
>>  3 files changed, 313 insertions(+), 20 deletions(-)
>> 
>
>> @@ -120,6 +120,7 @@ Object *user_creatable_add_type(const char *type, const char *id,
>>      obj = object_new(type);
>>      if (qdict) {
>>          for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
>> +
>>              object_property_set(obj, v, e->key, &local_err);
>>              if (local_err) {
>>                  goto out;
>
> Spurious hunk?
Daniel P. Berrangé March 22, 2016, 10:34 a.m. UTC | #3
On Tue, Mar 22, 2016 at 10:07:42AM +0100, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
> > On 03/10/2016 11:59 AM, Daniel P. Berrange wrote:
> >> 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.
> >
> > Zoltán Kővágó tried earlier with his GSoC patches for the audio
> > subsystem last year, but those got stalled waiting for qapi enhancements
> > to go in.
> 
> Yet another series stalled on the big QAPI rework.  Hitting a GSoC
> student that way is really unfortunate.
> 
> >            But I think your approach is indeed a bit nicer (rather than
> > making the warty OptsVisitor even wartier, just avoid it).
> 
> QemuOpts defines an important part of the command line language, namely
> (most of) the syntax of many option arguments.  It parses them into a
> set of (name, value) pairs.
> 
> "Most of", because additional syntax may hide in the parameter value.
> 
> Parameter values are typed, except when they aren't.  Types are limited
> to string, bool, uint64_t number (accepts negative numbers and casts
> them) and uint64_t size (rejects negative numbers, accepts suffixes).
> 
> OptsVisitor adds special list syntax.  It's used with untyped values.
> 
> Bypassing OptsVisitor risks adding different special syntax.  Doesn't
> mean it's a bad idea, only that we need to keep close watch on what it
> does to the language.  See below.

FWIW, when I first started attacking this problem I actually went down
the path of extending the OptsVisitor to cope with arbitrarily nested
structs + lists. I quickly discovered that special list syntax supported
by the OptsVisitor. I tried to hack support for nested structs + lists
on top of that, but ultimately the way the special list syntax is designed
makes that an impossible problem without breaking back compat, or having
OptsVisitor support two completely different lists syntaxes at the same
time with ambiguous parsing results. None the less I did implement that
all and it was a huge amount of work. I then took a look at QMPInputVisitor
and realized that if we could converts a QemuOpts into a QDict, we could
just reuse the QMPInputVisitor.

In the specific case of -object the fact that QMPInputVisitor does not
support the special list syntax from OptsVisitor is not a problem because
we don't have any existing user defined object type that uses list props
yet. So if we want to change -object from OptsVisitor to QMPInputVisitor
the sooner we change it the better - it is only a matter of time before
something comes along that depends on the existing special list syntax
and then we'll be locked into OptsVisitor's non-extensible approach.

> >> 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
> 
> Okay, this adds a bare minimum of syntax, and it's not even new: we use
> similar syntax for block stuff already.

Yes, indeed that usage by the block layer is what motivated me to ditch
OptsVisitor and take the approach of converting QemuOpts to QDict and
then using QMPInputVisitor.

Regards,
Daniel
Daniel P. Berrangé March 22, 2016, 3:51 p.m. UTC | #4
On Mon, Mar 21, 2016 at 05:27:24PM -0600, Eric Blake wrote:
> On 03/10/2016 11:59 AM, Daniel P. Berrange wrote:
> > 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.
> 
> Zoltán Kővágó tried earlier with his GSoC patches for the audio
> subsystem last year, but those got stalled waiting for qapi enhancements
> to go in.  But I think your approach is indeed a bit nicer (rather than
> making the warty OptsVisitor even wartier, just avoid it).

My first attempt did indeed modify OptsVisitor, but I quickly
abandoned it since it ended up being quite complex code to
make it fit in with the pre-existing hack to supports lists
of scalars in OptsVisitor.  The QmpInputVisitor approach is
cleaner and simpler overall


> > 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
> > 
> > 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
> 
> Maybe mention that the indentation is not actually present in the real
> command lines typed.

Heh, yeah

> > @@ -120,6 +120,7 @@ Object *user_creatable_add_type(const char *type, const char *id,
> >      obj = object_new(type);
> >      if (qdict) {
> >          for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
> > +
> >              object_property_set(obj, v, e->key, &local_err);
> >              if (local_err) {
> >                  goto out;
> 
> Spurious hunk?

Indeed

Regards,
Daniel
diff mbox

Patch

diff --git a/hmp.c b/hmp.c
index 5b6084a..7a98726 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"
@@ -1673,20 +1673,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_full((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/qom/object_interfaces.c b/qom/object_interfaces.c
index c2f6e29..9c41730 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -1,9 +1,9 @@ 
 #include "qemu/osdep.h"
 #include "qom/object_interfaces.h"
 #include "qemu/module.h"
+#include "qemu/option.h"
 #include "qapi-visit.h"
-#include "qapi/qmp-output-visitor.h"
-#include "qapi/opts-visitor.h"
+#include "qapi/qmp-input-visitor.h"
 
 void user_creatable_complete(Object *obj, Error **errp)
 {
@@ -120,6 +120,7 @@  Object *user_creatable_add_type(const char *type, const char *id,
     obj = object_new(type);
     if (qdict) {
         for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
+
             object_property_set(obj, v, e->key, &local_err);
             if (local_err) {
                 goto out;
@@ -151,15 +152,22 @@  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);
+    pobj = qdict_crumple(pdict, true, errp);
+    if (!pobj) {
+        goto cleanup;
+    }
+    qiv = qmp_input_visitor_new_full(pobj, true, true);
 
-    obj = user_creatable_add(pdict, opts_get_visitor(ov), errp);
-    opts_visitor_cleanup(ov);
+    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 a2bb556..b19bfb0 100644
--- a/tests/check-qom-proplist.c
+++ b/tests/check-qom-proplist.c
@@ -23,6 +23,14 @@ 
 
 #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"
@@ -30,6 +38,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)
 
@@ -50,12 +63,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 {
@@ -117,6 +153,135 @@  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, &err);
+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, &err);
+out:
+    error_propagate(errp, err);
+}
+
+static void visit_type_DummyAddrList(Visitor *v, const char *name,
+                                     DummyAddrList **obj, Error **errp)
+{
+    Error *err = NULL;
+    GenericList *i, **prev;
+
+    visit_start_list(v, name, &err);
+    if (err) {
+        goto out;
+    }
+
+    for (prev = (GenericList **)obj;
+         !err && (i = visit_next_list(v, prev, sizeof(**obj))) != NULL;
+         prev = &i) {
+        DummyAddrList *native_i = (DummyAddrList *)i;
+        visit_type_DummyAddr(v, NULL, &native_i->value, &err);
+    }
+
+    visit_end_list(v);
+out:
+    error_propagate(errp, err);
+}
+
+
+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)
 {
@@ -126,9 +291,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,
@@ -143,12 +315,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);
 }
@@ -162,6 +358,10 @@  static const TypeInfo dummy_info = {
     .instance_finalize = dummy_finalize,
     .class_size = sizeof(DummyObjectClass),
     .class_init = dummy_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
 };
 
 
@@ -457,7 +657,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))) {
@@ -470,6 +672,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();
@@ -479,6 +687,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));
 }
@@ -497,11 +708,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_strict(obj);
+    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);
@@ -513,6 +804,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();
 }