diff --git a/MAINTAINERS b/MAINTAINERS index 4be087b88e..ddbf0fc1e3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2732,6 +2732,13 @@ F: util/uuid.c F: include/qemu/uuid.h F: tests/test-uuid.c +Yank feature +M: Lukas Straub +S: Odd fixes +F: util/yank.c +F: include/qemu/yank.h +F: qapi/yank.json + COLO Framework M: zhanghailiang S: Maintained diff --git a/include/qemu/yank.h b/include/qemu/yank.h new file mode 100644 index 0000000000..5b93c70cbf --- /dev/null +++ b/include/qemu/yank.h @@ -0,0 +1,97 @@ +/* + * QEMU yank feature + * + * Copyright (c) Lukas Straub + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef YANK_H +#define YANK_H + +#include "qapi/qapi-types-yank.h" + +typedef void (YankFn)(void *opaque); + +/** + * yank_register_instance: Register a new instance. + * + * This registers a new instance for yanking. Must be called before any yank + * function is registered for this instance. + * + * This function is thread-safe. + * + * @instance: The instance. + * @errp: Error object. + * + * Returns true on success or false if an error occured. + */ +bool yank_register_instance(const YankInstance *instance, Error **errp); + +/** + * yank_unregister_instance: Unregister a instance. + * + * This unregisters a instance. Must be called only after every yank function + * of the instance has been unregistered. + * + * This function is thread-safe. + * + * @instance: The instance. + */ +void yank_unregister_instance(const YankInstance *instance); + +/** + * yank_register_function: Register a yank function + * + * This registers a yank function. All limitations of qmp oob commands apply + * to the yank function as well. See docs/devel/qapi-code-gen.txt under + * "An OOB-capable command handler must satisfy the following conditions". + * + * This function is thread-safe. + * + * @instance: The instance. + * @func: The yank function. + * @opaque: Will be passed to the yank function. + */ +void yank_register_function(const YankInstance *instance, + YankFn *func, + void *opaque); + +/** + * yank_unregister_function: Unregister a yank function + * + * This unregisters a yank function. + * + * This function is thread-safe. + * + * @instance: The instance. + * @func: func that was passed to yank_register_function. + * @opaque: opaque that was passed to yank_register_function. + */ +void yank_unregister_function(const YankInstance *instance, + YankFn *func, + void *opaque); + +/** + * yank_generic_iochannel: Generic yank function for iochannel + * + * This is a generic yank function which will call qio_channel_shutdown on the + * provided QIOChannel. + * + * @opaque: QIOChannel to shutdown + */ +void yank_generic_iochannel(void *opaque); + +#define BLOCKDEV_YANK_INSTANCE(the_node_name) (&(YankInstance) { \ + .type = YANK_INSTANCE_TYPE_BLOCK_NODE, \ + .u.block_node.node_name = (the_node_name) }) + +#define CHARDEV_YANK_INSTANCE(the_id) (&(YankInstance) { \ + .type = YANK_INSTANCE_TYPE_CHARDEV, \ + .u.chardev.id = (the_id) }) + +#define MIGRATION_YANK_INSTANCE (&(YankInstance) { \ + .type = YANK_INSTANCE_TYPE_MIGRATION }) + +#endif diff --git a/qapi/meson.build b/qapi/meson.build index 0e98146f1f..ab68e7900e 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -47,6 +47,7 @@ qapi_all_modules = [ 'trace', 'transaction', 'ui', + 'yank', ] qapi_storage_daemon_modules = [ diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index 0b444b76d2..3441c9a9ae 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -86,6 +86,7 @@ { 'include': 'machine.json' } { 'include': 'machine-target.json' } { 'include': 'replay.json' } +{ 'include': 'yank.json' } { 'include': 'misc.json' } { 'include': 'misc-target.json' } { 'include': 'audio.json' } diff --git a/qapi/yank.json b/qapi/yank.json new file mode 100644 index 0000000000..167a775594 --- /dev/null +++ b/qapi/yank.json @@ -0,0 +1,119 @@ +# -*- Mode: Python -*- +# vim: filetype=python +# + +## +# = Yank feature +## + +## +# @YankInstanceType: +# +# An enumeration of yank instance types. See @YankInstance for more +# information. +# +# Since: 6.0 +## +{ 'enum': 'YankInstanceType', + 'data': [ 'block-node', 'chardev', 'migration' ] } + +## +# @YankInstanceBlockNode: +# +# Specifies which block graph node to yank. See @YankInstance for more +# information. +# +# @node-name: the name of the block graph node +# +# Since: 6.0 +## +{ 'struct': 'YankInstanceBlockNode', + 'data': { 'node-name': 'str' } } + +## +# @YankInstanceChardev: +# +# Specifies which character device to yank. See @YankInstance for more +# information. +# +# @id: the chardev's ID +# +# Since: 6.0 +## +{ 'struct': 'YankInstanceChardev', + 'data': { 'id': 'str' } } + +## +# @YankInstance: +# +# A yank instance can be yanked with the @yank qmp command to recover from a +# hanging QEMU. +# +# Currently implemented yank instances: +# - nbd block device: +# Yanking it will shut down the connection to the nbd server without +# attempting to reconnect. +# - socket chardev: +# Yanking it will shut down the connected socket. +# - migration: +# Yanking it will shut down all migration connections. Unlike +# @migrate_cancel, it will not notify the migration process, so migration +# will go into @failed state, instead of @cancelled state. @yank should be +# used to recover from hangs. +# +# Since: 6.0 +## +{ 'union': 'YankInstance', + 'base': { 'type': 'YankInstanceType' }, + 'discriminator': 'type', + 'data': { + 'block-node': 'YankInstanceBlockNode', + 'chardev': 'YankInstanceChardev' } } + +## +# @yank: +# +# Try to recover from hanging QEMU by yanking the specified instances. See +# @YankInstance for more information. +# +# Takes a list of @YankInstance as argument. +# +# Returns: - Nothing on success +# - @DeviceNotFound error, if any of the YankInstances doesn't exist +# +# Example: +# +# -> { "execute": "yank", +# "arguments": { +# "instances": [ +# { "type": "block-node", +# "node-name": "nbd0" } +# ] } } +# <- { "return": {} } +# +# Since: 6.0 +## +{ 'command': 'yank', + 'data': { 'instances': ['YankInstance'] }, + 'allow-oob': true } + +## +# @query-yank: +# +# Query yank instances. See @YankInstance for more information. +# +# Returns: list of @YankInstance +# +# Example: +# +# -> { "execute": "query-yank" } +# <- { "return": [ +# { "type": "block-node", +# "node-name": "nbd0" } +# ] } +# +# Since: 6.0 +## +{ 'command': 'query-yank', + 'returns': ['YankInstance'], + 'allow-oob': true } diff --git a/util/meson.build b/util/meson.build index a3dfc0f966..540a605b78 100644 --- a/util/meson.build +++ b/util/meson.build @@ -50,6 +50,7 @@ endif if have_system util_ss.add(when: 'CONFIG_GIO', if_true: [files('dbus.c'), gio]) + util_ss.add(files('yank.c')) endif if have_block diff --git a/util/yank.c b/util/yank.c new file mode 100644 index 0000000000..fc08f65209 --- /dev/null +++ b/util/yank.c @@ -0,0 +1,207 @@ +/* + * QEMU yank feature + * + * Copyright (c) Lukas Straub + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/thread.h" +#include "qemu/queue.h" +#include "qemu/lockable.h" +#include "qapi/qapi-commands-yank.h" +#include "qapi/qapi-visit-yank.h" +#include "qapi/clone-visitor.h" +#include "io/channel.h" +#include "qemu/yank.h" + +struct YankFuncAndParam { + YankFn *func; + void *opaque; + QLIST_ENTRY(YankFuncAndParam) next; +}; + +struct YankInstanceEntry { + YankInstance *instance; + QLIST_HEAD(, YankFuncAndParam) yankfns; + QLIST_ENTRY(YankInstanceEntry) next; +}; + +typedef struct YankFuncAndParam YankFuncAndParam; +typedef struct YankInstanceEntry YankInstanceEntry; + +/* + * This lock protects the yank_instance_list below. Because it's taken by + * OOB-capable commands, it must be "fast", i.e. it may only be held for a + * bounded, short time. See docs/devel/qapi-code-gen.txt for additional + * information. + */ +static QemuMutex yank_lock; + +static QLIST_HEAD(, YankInstanceEntry) yank_instance_list + = QLIST_HEAD_INITIALIZER(yank_instance_list); + +static bool yank_instance_equal(const YankInstance *a, const YankInstance *b) +{ + if (a->type != b->type) { + return false; + } + + switch (a->type) { + case YANK_INSTANCE_TYPE_BLOCK_NODE: + return g_str_equal(a->u.block_node.node_name, + b->u.block_node.node_name); + + case YANK_INSTANCE_TYPE_CHARDEV: + return g_str_equal(a->u.chardev.id, b->u.chardev.id); + + case YANK_INSTANCE_TYPE_MIGRATION: + return true; + + default: + abort(); + } +} + +static YankInstanceEntry *yank_find_entry(const YankInstance *instance) +{ + YankInstanceEntry *entry; + + QLIST_FOREACH(entry, &yank_instance_list, next) { + if (yank_instance_equal(entry->instance, instance)) { + return entry; + } + } + return NULL; +} + +bool yank_register_instance(const YankInstance *instance, Error **errp) +{ + YankInstanceEntry *entry; + + QEMU_LOCK_GUARD(&yank_lock); + + if (yank_find_entry(instance)) { + error_setg(errp, "duplicate yank instance"); + return false; + } + + entry = g_new0(YankInstanceEntry, 1); + entry->instance = QAPI_CLONE(YankInstance, instance); + QLIST_INIT(&entry->yankfns); + QLIST_INSERT_HEAD(&yank_instance_list, entry, next); + + return true; +} + +void yank_unregister_instance(const YankInstance *instance) +{ + YankInstanceEntry *entry; + + QEMU_LOCK_GUARD(&yank_lock); + entry = yank_find_entry(instance); + assert(entry); + + assert(QLIST_EMPTY(&entry->yankfns)); + QLIST_REMOVE(entry, next); + qapi_free_YankInstance(entry->instance); + g_free(entry); +} + +void yank_register_function(const YankInstance *instance, + YankFn *func, + void *opaque) +{ + YankInstanceEntry *entry; + YankFuncAndParam *func_entry; + + QEMU_LOCK_GUARD(&yank_lock); + entry = yank_find_entry(instance); + assert(entry); + + func_entry = g_new0(YankFuncAndParam, 1); + func_entry->func = func; + func_entry->opaque = opaque; + + QLIST_INSERT_HEAD(&entry->yankfns, func_entry, next); +} + +void yank_unregister_function(const YankInstance *instance, + YankFn *func, + void *opaque) +{ + YankInstanceEntry *entry; + YankFuncAndParam *func_entry; + + QEMU_LOCK_GUARD(&yank_lock); + entry = yank_find_entry(instance); + assert(entry); + + QLIST_FOREACH(func_entry, &entry->yankfns, next) { + if (func_entry->func == func && func_entry->opaque == opaque) { + QLIST_REMOVE(func_entry, next); + g_free(func_entry); + return; + } + } + + abort(); +} + +void yank_generic_iochannel(void *opaque) +{ + QIOChannel *ioc = QIO_CHANNEL(opaque); + + qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL); +} + +void qmp_yank(YankInstanceList *instances, + Error **errp) +{ + YankInstanceList *tail; + YankInstanceEntry *entry; + YankFuncAndParam *func_entry; + + QEMU_LOCK_GUARD(&yank_lock); + for (tail = instances; tail; tail = tail->next) { + entry = yank_find_entry(tail->value); + if (!entry) { + error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, "Instance not found"); + return; + } + } + for (tail = instances; tail; tail = tail->next) { + entry = yank_find_entry(tail->value); + assert(entry); + QLIST_FOREACH(func_entry, &entry->yankfns, next) { + func_entry->func(func_entry->opaque); + } + } +} + +YankInstanceList *qmp_query_yank(Error **errp) +{ + YankInstanceEntry *entry; + YankInstanceList *ret; + + ret = NULL; + + QEMU_LOCK_GUARD(&yank_lock); + QLIST_FOREACH(entry, &yank_instance_list, next) { + YankInstanceList *new_entry; + new_entry = g_new0(YankInstanceList, 1); + new_entry->value = QAPI_CLONE(YankInstance, entry->instance); + new_entry->next = ret; + ret = new_entry; + } + + return ret; +} + +static void __attribute__((__constructor__)) yank_init(void) +{ + qemu_mutex_init(&yank_lock); +}