Features/QAPI/Signals
Problem
The signal/slot paradigm maps pretty well to the structure of an English sentence. Consider the sentence:
Jane jumped over the fence.
The sentence has two parts, the noun phrase, "Jane", and the verb phrase, "jumped over the fence". "jumped" is the verb and "over the fence" is enhances the verb with additional information.
In signal/slots, the signal is the verb. The noun phrase provides a method for connecting to a signal. You pass that connection method a slot. The slot is invoked with the noun, and the remainder of the verb phrase excluding the verb itself.
This maps well to object oriented programming since objects are usually nouns. Signals are a common to an entire class of nouns (any person can jump).
In QMP today, we don't have a notion of objects since JSON only encodes primative types. In order to properly implement signal/slots, we need to introduce a protocol level concept of objects.
Implicit Objects
The interfaces that QEMU supports today actually mostly operate on objects of some form. In QMP, we encode these objects into a primative value so it can be marshalled via JSON. Consider the block_passwd command:
{ 'command': 'block_passwd', 'data': {'device': 'str', 'password': 'str'} }
The 'device' argument is really a BlockDriverState in QEMU. QMP uses bdrv_find in order to lookup a BlockDriverState based on its symbolic name.
This relationship is not visible at the protocol level though. The schema does not differientiate the type of 'device' from any other string parameter.
In order to leverage this information in the protocol, we need to make this relationship explicit in the schema instead of implicit.
Handles
A handle is a way to encode a complex object in a simple way. This improves the wire protocol by making it simplier while also allowing the server to enforce that the contents of a complex object can be trusted even when referenced by an untrusted source.
The following is the schema definition of a handle representing block devices:
{ 'handle': 'Blockdev', 'data': 'str' }
This lets an arbitrarily complex Blockdev object be represented in QMP by just a string. This also allows us to be 100% backwards compatible while still benefiting from stronger typing. For instance, we can redefine the block_passwd command to be:
{ 'command': 'block_passwd', 'data': {'device': 'Blockdev', 'password': 'str'} }
QAPI Code Generation
The goal of handle support in QAPI is to use the strongest typing possible. In the case of block_passwd, that means a function signature of:
void qmp_block_passwd(Blockdev *device, const char *password, Error **errp);
In order for the server to decide this, for each handle type, a function that can lookup the complex object given the simple representation is required. In the case of Blockdev, the following signature would be generated:
typedef struct Blockdev { char * info; struct Blockdev * next; } Blockdev; Blockdev *qapi_find_blockdev(const char * info, Error **errp);
Blockdev's can only ever be created through qapi_find_blockdev which means that this interface can allocate a larger structure and then rely on using container_of() to get the more complex object. For instance:
typedef struct BlockdevWrapper { Blockdev blockdev; BlockDriverState * bs; } BlockdevWrapper;
This provides a reliable way for functions that take a Blockdev to get the underlying BlockDriverState.
Here's a more complete example:
typedef struct BlockdevWrapper { Blockdev blockdev; BlockDriverState *bs; } BlockdevWrapper; Blockdev *qapi_find_blockdev(const char * info, Error **errp) { BlockDriverState *bs; BlockdevWrapper *bw; bs = bdrv_find(info); if (bs == NULL) { error_set(errp, QERR_INVALID_HANDLE_VALUE, "Blockdev"); return NULL; } bw = qemu_mallocz(sizeof(*bw)); bw->blockdev.info = qemu_strdup(info); bw->bs = bs; return &bw->blockdev; } static BlockDriverState *blockdev_to_bs(Blockdev *device) { return container_of(device, BlockdevWrapper, blockdev)->bs; } void qmp_blockdev_passwd(Blockdev *device, const char *password, Error **errp) { BlockDriverState *bs = blockdev_to_bs(device); // ... }
Maintaining a Self-Describing RPC
A big advantage of JSON-RPC is that it is a self-describing RPC. This makes it trivial to use with a dynamic language because the RPC can be decoded in a meaningful way without knowledge of the RPC schema.
The transparent encoding of handles violates this rule. In order to make up for this, we will introduce a new QMP capability enabled by the set-explicit-handles command. When issued before qmp_capabilities is executed, the command will cause handles to be encoded with type information. That is, Blockdev will be encoded as:
{ '__handle__': 'Blockdev', 'data': 'ide0-hd0' }
This is refered to as Canonical Form. If this capability is not enabled, handleds are encoded in Non-Canonical Form, or in the case of blockdev:
'ide0-hd0'
Supporting Signals
Once we have a mechanism to identify objects in QMP, we can now implement sophisticated support for signals. To define a signal in the QMP schema, we add the following entry:
{ 'signal': 'IO_ERROR', 'owner': 'Blockdev', 'data': { 'operation': 'str', 'action': 'str' } }
Within qapi-types.h, this will define the following:
typedef void (BlockdevIoErrorFunc)(Blockdev *, void * opaque, char * operation, char * action); typedef struct BlockdevIoError { QmpSignal *signal; BlockdevIoErrorFunc *fn; } BlockdevIoError;
Within QEMU, the following function needs to be implemented:
BlockdevIoError *qapi_get_blockdev_io_error(Blockdev *obj, Error **errp);
This is exposed through libqmp as:
BlockdevIoError *libqmp_get_blockdev_io_error(QmpSession *sess, Blockdev *obj, Error **errp);
To connect to a signal and disconnect, the following functions are used:
int conn; Blockdev *obj = ...; BlockdevIoError *signal; signal = libqmp_get_blockdev_io_error(sess, obj, NULL); conn = qapi_signal_connect(signal, my_handler, &my_state); // do some work qapi_signal_disconnect(signal, conn);
Signals and QMP
In QMP, signals are supported as a first-class concept in the protocol. Signals are referenced by name. Here is an example of an exchange to connect to a signal:
> { 'execute': 'query-block' } < { 'returns': [ { 'device': { '__handle__': 'Blockdev', 'data': 'ide0-hd0' }, 'type': 'hd', 'removable': false, 'locked': false } ] } > { 'connect': 'IO_ERROR', 'owner': { '__handle__': 'Blockdev', 'data': 'ide0-hd0' }, 'user_data': 'libqmp-session-handle-23423' } < { 'returns': 24 }
Connecting to a signal at the QMP level requires the signal name, and a handle to the object that owns the signal. The handle MUST be specified in Canonical Form. Signals cannot be used unless the session is using explicit handle typing.
Disconnecting from a signal just requires the connection handle and the object.
> { 'disconnect': 24, owner: { '__handle__': 'Blockdev', 'data': 'ide0-hd0' } } < { 'returns': {} }
When a signal is emitted, it includes the user_data included with the signal.
< { 'signal': 'IO_ERROR', 'owner': { '__handle__': 'Blockdev', 'data': 'ide0-hd0' }, 'user_data': 'libqmp-session-handle-23423', 'data': { 'action': 'stop', 'operation': 'read' } }