From patchwork Tue Aug 15 07:45:13 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Manos Pitsidianakis X-Patchwork-Id: 9901205 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id D13ED6028A for ; Tue, 15 Aug 2017 07:47:36 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BBD8A27DCD for ; Tue, 15 Aug 2017 07:47:36 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id AEEB8287CB; Tue, 15 Aug 2017 07:47:36 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 66D8927DCD for ; Tue, 15 Aug 2017 07:47:34 +0000 (UTC) Received: from localhost ([::1]:44459 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dhWZZ-00034T-ML for patchwork-qemu-devel@patchwork.kernel.org; Tue, 15 Aug 2017 03:47:33 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:38645) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dhWYU-0002py-O9 for qemu-devel@nongnu.org; Tue, 15 Aug 2017 03:46:30 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1dhWYQ-0006RM-PZ for qemu-devel@nongnu.org; Tue, 15 Aug 2017 03:46:26 -0400 Received: from smtp1.ntua.gr ([2001:648:2000:de::183]:52596) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dhWYH-0006M2-F4; Tue, 15 Aug 2017 03:46:14 -0400 Received: from mail.ntua.gr (internet4-5-144-200-220.pat.nym.cosmote.net [5.144.200.220]) (authenticated bits=0) by smtp1.ntua.gr (8.15.2/8.15.2) with ESMTPSA id v7F7jMZo059293 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 15 Aug 2017 10:45:23 +0300 (EEST) (envelope-from el13635@mail.ntua.gr) X-Authentication-Warning: smtp1.ntua.gr: Host internet4-5-144-200-220.pat.nym.cosmote.net [5.144.200.220] claimed to be mail.ntua.gr From: Manos Pitsidianakis To: qemu-devel Date: Tue, 15 Aug 2017 10:45:13 +0300 Message-Id: <20170815074513.9055-1-el13635@mail.ntua.gr> X-Mailer: git-send-email 2.11.0 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2001:648:2000:de::183 Subject: [Qemu-devel] [PATCH RFC] block: add block-insert-node QMP command X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , Alberto Garcia , Stefan Hajnoczi , qemu-block Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP block-insert-node and its pair command block-remove-node provide runtime insertion and removal of filter nodes. block-insert-node takes a (parent, child) and (node, child) pair of edges and unrefs the (parent, child) BdrvChild relationship and creates a new (parent, node) child with the same BdrvChildRole. This is a different approach than x-blockdev-change which uses the driver methods bdrv_add_child() and bdrv_del_child(), Signed-off-by: Manos Pitsidianakis --- block.c | 192 ++++++++ blockdev.c | 44 ++ include/block/block.h | 6 + qapi/block-core.json | 60 +++ tests/qemu-iotests/193 | 241 ++++++++++ tests/qemu-iotests/193.out | 1116 ++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/group | 1 + 7 files changed, 1660 insertions(+) create mode 100755 tests/qemu-iotests/193 create mode 100644 tests/qemu-iotests/193.out diff --git a/block.c b/block.c index 81bd51b670..f874aabbfb 100644 --- a/block.c +++ b/block.c @@ -930,6 +930,9 @@ static void bdrv_backing_attach(BdrvChild *c) parent->backing_blocker); bdrv_op_unblock(backing_hd, BLOCK_OP_TYPE_STREAM, parent->backing_blocker); + /* Unblock filter node insertion */ + bdrv_op_unblock(backing_hd, BLOCK_OP_TYPE_EDGE_MODIFICATION, + parent->backing_blocker); /* * We do backup in 3 ways: * 1. drive backup @@ -5036,3 +5039,192 @@ BlockDriverState *bdrv_get_first_explicit(BlockDriverState *bs) } return bs; } + + +static inline BdrvChild *bdrv_find_child(BlockDriverState *parent_bs, + const char *child_name) +{ + BdrvChild *child; + assert(child_name); + + QLIST_FOREACH(child, &parent_bs->children, next) { + if (child->bs && !g_strcmp0(child->bs->node_name, child_name)) { + return child; + } + } + + return NULL; +} + +static int check_node_edge(const char *parent, const char *child, Error **errp) +{ + BlockDriverState *parent_bs, *child_bs; + parent_bs = bdrv_find_node(parent); + if (!parent_bs) { + error_setg(errp, "'%s' not a node name", parent); + return 1; + } + child_bs = bdrv_find_node(child); + if (!child_bs) { + error_setg(errp, "'%s' not a node name", child); + return 1; + } + if (!bdrv_find_child(parent_bs, child)) { + error_setg(errp, "'%s' not a child of '%s'", child, parent); + return 1; + } + if (bdrv_op_is_blocked(parent_bs, BLOCK_OP_TYPE_EDGE_MODIFICATION, errp) || + bdrv_op_is_blocked(child_bs, BLOCK_OP_TYPE_EDGE_MODIFICATION, errp)) { + return 1; + } + return 0; +} + +void bdrv_insert_node(const char *parent, const char *child, + const char *node, Error **errp) +{ + BlockBackend *blk; + BlockDriverState *parent_bs, *node_bs, *child_bs; + BdrvChild *c; + const BdrvChildRole *role; + + if (check_node_edge(node, child, errp)) { + return; + } + node_bs = bdrv_find_node(node); + child_bs = bdrv_find_node(child); + blk = blk_by_name(parent); + if (blk) { + /* insert 'node' as root bs of 'parent' device */ + if (!blk_bs(blk)) { + error_setg(errp, "Device '%s' has no medium", parent); + return; + } + if (blk_bs(blk) != child_bs) { + error_setg(errp, "'%s' not a child of device '%s'", child, parent); + return; + } + bdrv_drained_begin(child_bs); + blk_remove_bs(blk); + blk_insert_bs(blk, node_bs, errp); + if (!blk_bs(blk)) { + blk_insert_bs(blk, child_bs, &error_abort); + } + bdrv_drained_end(child_bs); + return; + } + + /* insert 'node' as child bs of 'parent' node */ + if (check_node_edge(parent, child, errp)) { + return; + } + parent_bs = bdrv_find_node(parent); + c = bdrv_find_child(parent_bs, child); + role = c->role; + assert(role == &child_file || role == &child_backing); + + bdrv_ref(node_bs); + + bdrv_drained_begin(parent_bs); + bdrv_unref_child(parent_bs, c); + if (role == &child_file) { + parent_bs->file = bdrv_attach_child(parent_bs, node_bs, "file", + &child_file, errp); + if (!parent_bs->file) { + parent_bs->file = bdrv_attach_child(parent_bs, child_bs, "file", + &child_file, &error_abort); + goto out; + } + } else if (role == &child_backing) { + parent_bs->backing = bdrv_attach_child(parent_bs, node_bs, "backing", + &child_backing, errp); + if (!parent_bs->backing) { + parent_bs->backing = bdrv_attach_child(parent_bs, child_bs, + "backing", &child_backing, + &error_abort); + goto out; + } + } + bdrv_refresh_filename(parent_bs); + bdrv_refresh_limits(parent_bs, NULL); + +out: + bdrv_drained_end(parent_bs); +} + +void bdrv_remove_node(const char *parent, const char *child, + const char *node, Error **errp) +{ + BlockBackend *blk; + BlockDriverState *node_bs, *parent_bs, *child_bs; + BdrvChild *c; + const BdrvChildRole *role; + + if (check_node_edge(node, child, errp)) { + return; + } + node_bs = bdrv_find_node(node); + child_bs = bdrv_find_node(child); + blk = blk_by_name(parent); + if (blk) { + /* remove 'node' as root bs of 'parent' device */ + if (!blk_bs(blk)) { + error_setg(errp, "Device '%s' has no medium", parent); + return; + } + if (blk_bs(blk) != node_bs) { + error_setg(errp, "'%s' not a child of device '%s'", node, parent); + return; + } + bdrv_drained_begin(node_bs); + blk_remove_bs(blk); + blk_insert_bs(blk, child_bs, errp); + if (!blk_bs(blk)) { + blk_insert_bs(blk, node_bs, &error_abort); + } + bdrv_drained_end(node_bs); + return; + } + + if (check_node_edge(parent, node, errp)) { + return; + } + parent_bs = bdrv_find_node(parent); + c = bdrv_find_child(node_bs, child); + role = c->role; + assert(role == &child_file || role == &child_backing); + + + bdrv_ref(child_bs); + bdrv_ref(node_bs); + + bdrv_drained_begin(parent_bs); + bdrv_unref_child(parent_bs, bdrv_find_child(parent_bs, node)); + if (role == &child_file) { + parent_bs->file = bdrv_attach_child(parent_bs, child_bs, "file", + &child_file, errp); + if (!parent_bs->file) { + parent_bs->file = bdrv_attach_child(parent_bs, node_bs, "file", + &child_file, &error_abort); + node_bs->file = bdrv_attach_child(node_bs, child_bs, "file", + &child_file, &error_abort); + goto out; + } + } else if (role == &child_backing) { + parent_bs->backing = bdrv_attach_child(parent_bs, child_bs, "backing", + &child_backing, errp); + if (!parent_bs->backing) { + parent_bs->backing = bdrv_attach_child(parent_bs, node_bs, + "backing", &child_backing, + &error_abort); + node_bs->backing = bdrv_attach_child(node_bs, child_bs, "backing", + &child_backing, &error_abort); + goto out; + } + } + bdrv_refresh_filename(parent_bs); + bdrv_refresh_limits(parent_bs, NULL); + bdrv_unref(node_bs); +out: + bdrv_drained_end(parent_bs); +} diff --git a/blockdev.c b/blockdev.c index 8e2fc6e64c..5195ec1b61 100644 --- a/blockdev.c +++ b/blockdev.c @@ -4238,3 +4238,47 @@ QemuOptsList qemu_drive_opts = { { /* end of list */ } }, }; + +void qmp_block_insert_node(const char *parent, const char *child, + const char *node, Error **errp) +{ + BlockDriverState *bs = bdrv_find_node(node); + if (!bs) { + error_setg(errp, "Node '%s' not found", node); + return; + } + if (!bs->monitor_list.tqe_prev) { + error_setg(errp, "Node '%s' is not owned by the monitor", + bs->node_name); + return; + } + if (!bs->drv->is_filter) { + error_setg(errp, "Block format '%s' used by node '%s' does not support" + "insertion", bs->drv->format_name, bs->node_name); + return; + } + + bdrv_insert_node(parent, child, node, errp); +} + +void qmp_block_remove_node(const char *parent, const char *child, + const char *node, Error **errp) +{ + BlockDriverState *bs = bdrv_find_node(node); + if (!bs) { + error_setg(errp, "Node '%s' not found", node); + return; + } + if (!bs->monitor_list.tqe_prev) { + error_setg(errp, "Node %s is not owned by the monitor", + bs->node_name); + return; + } + if (!bs->drv->is_filter) { + error_setg(errp, "Block format '%s' used by node '%s' does not support" + "removal", bs->drv->format_name, bs->node_name); + return; + } + + bdrv_remove_node(parent, child, node, errp); +} diff --git a/include/block/block.h b/include/block/block.h index 744b50e734..9e1120af1b 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -191,6 +191,8 @@ typedef enum BlockOpType { BLOCK_OP_TYPE_RESIZE, BLOCK_OP_TYPE_STREAM, BLOCK_OP_TYPE_REPLACE, + BLOCK_OP_TYPE_EDGE_MODIFICATION, /* block user modification of graph edges + including this node */ BLOCK_OP_TYPE_MAX, } BlockOpType; @@ -627,4 +629,8 @@ void bdrv_del_child(BlockDriverState *parent, BdrvChild *child, Error **errp); bool bdrv_can_store_new_dirty_bitmap(BlockDriverState *bs, const char *name, uint32_t granularity, Error **errp); +void bdrv_insert_node(const char *parent, const char *child, + const char *node, Error **errp); +void bdrv_remove_node(const char *parent, const char *child, + const char *node, Error **errp); #endif diff --git a/qapi/block-core.json b/qapi/block-core.json index 4d6ba1baef..16e19cb648 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -3947,3 +3947,63 @@ 'data' : { 'parent': 'str', '*child': 'str', '*node': 'str' } } + +## +# @block-insert-node: +# +# Insert a filter node between a specific edge in the block driver state graph. +# @parent: the name of the parent node or device +# @node: the name of the node to insert under parent +# @child: the name of the child of both node and parent +# +# Example: +# Insert and remove a throttle filter on top of a device chain, between the +# device 'ide0-hd0' and node 'node-A' +# +# -> {'execute': 'object-add', +# "arguments": { +# "qom-type": "throttle-group", +# "id": "group0", +# "props" : { "limits": { "iops-total": 300 } } +# } +# } +# <- { 'return': {} } +# -> {'execute': 'blockdev-add', +# 'arguments': { +# 'driver': 'throttle', +# 'node-name': 'throttle0', +# 'throttle-group': 'group0', +# 'file': 'node-A' +# } +# } +# <- { 'return': {} } +# -> { 'execute': 'block-insert-node', +# 'arguments': { 'parent': 'ide0-hd0', 'child': 'node-A', 'node': 'throttle0' } +# } +# <- { 'return': {} } +# -> { 'execute': 'block-remove-node', +# 'arguments': { 'parent': 'ide0-hd0', 'child': 'node-A', 'node': 'throttle0' } +# } +# <- { 'return': {} } +# -> { 'execute': 'blockdev-del', +# 'arguments': { 'node-name': 'throttle0' } +# } +# <- { 'return': {} } +# +## +{ 'command': 'block-insert-node', + 'data': { 'parent': 'str', + 'child': 'str', + 'node': 'str'} } +## +# @block-remove-node: +# +# Remove a filter node between two other nodes in the block driver state graph. +# @parent: the name of the parent node or device +# @node: the name of the node to remove from parent +# @child: the name of the child of node which will go under parent +## +{ 'command': 'block-remove-node', + 'data': { 'parent': 'str', + 'child': 'str', + 'node': 'str'} } diff --git a/tests/qemu-iotests/193 b/tests/qemu-iotests/193 new file mode 100755 index 0000000000..2936eba95e --- /dev/null +++ b/tests/qemu-iotests/193 @@ -0,0 +1,241 @@ +#!/bin/bash +# +# Test block-insert-node & block-remove-node commands +# +# Copyright (C) 2017 Manos Pitsidianakis +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# creator +owner="Manos Pitsidianakis" + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img + rm -rf "$TEST_IMG".s + rm -rf "$TEST_IMG".e + +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 +_supported_proto file +_supported_os Linux + +function do_run_qemu() +{ + echo Testing: "$@" | _filter_imgfmt + $QEMU -nographic -qmp-pretty stdio -serial none "$@" + echo +} + +function run_qemu() +{ + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_qmp\ + | _filter_qemu_io | _filter_generated_node_ids +} + +_make_test_img 64M +test_throttle=$($QEMU_IMG --help|grep throttle) +[ "$test_throttle" = "" ] && _supported_fmt throttle + +echo +echo "== inserting node ==" + +run_qemu <