aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2019-01-31 19:26:09 +0000
committerPeter Maydell <peter.maydell@linaro.org>2019-01-31 19:26:09 +0000
commitcfe6c547690b06fbce54a6d0f7b05dd7f18e36ea (patch)
tree103fb713da1c3ba222cb025f7b47899fb339596f
parente8977901b79fb678f288dd372261b640bbeccd0d (diff)
parent908b30164bbffad7430d551b2a03a8fbcaa631ef (diff)
Merge remote-tracking branch 'remotes/xanclic/tags/pull-block-2019-01-31' into staging
Block patches: - New debugging QMP command to explore block graphs - Converted DPRINTF()s to trace events - Fixed qemu-io's use of getopt() for systems with optreset - Minor NVMe emulation fixes - An iotest fix # gpg: Signature made Thu 31 Jan 2019 00:51:46 GMT # gpg: using RSA key F407DB0061D5CF40 # gpg: Good signature from "Max Reitz <mreitz@redhat.com>" [full] # Primary key fingerprint: 91BE B60A 30DB 3E88 57D1 1829 F407 DB00 61D5 CF40 * remotes/xanclic/tags/pull-block-2019-01-31: iotests: Allow 147 to be run concurrently iotests: Bind qemu-nbd to localhost in 147 iotests.py: Add qemu_nbd_pipe() nvme: use pci_dev directly in nvme_realize nvme: ensure the num_queues is not zero nvme: use TYPE_NVME instead of constant string qemu-io: Add generic function for reinitializing optind. block/sheepdog: Convert from DPRINTF() macro to trace events block/file-posix: Convert from DPRINTF() macro to trace events block/curl: Convert from DPRINTF() macro to trace events block/ssh: Convert from DPRINTF() macro to trace events scripts: add render_block_graph function for QEMUMachine qapi: add x-debug-query-block-graph Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--block.c148
-rw-r--r--block/block-backend.c5
-rw-r--r--block/curl.c29
-rw-r--r--block/file-posix.c25
-rw-r--r--block/sheepdog.c47
-rw-r--r--block/ssh.c46
-rw-r--r--block/trace-events47
-rw-r--r--blockdev.c5
-rwxr-xr-xconfigure14
-rw-r--r--hw/block/nvme.c15
-rw-r--r--include/block/block.h1
-rw-r--r--include/qemu/osdep.h16
-rw-r--r--include/sysemu/block-backend.h2
-rw-r--r--qapi/block-core.json108
-rw-r--r--qemu-img.c2
-rw-r--r--qemu-io-cmds.c2
-rwxr-xr-xscripts/render_block_graph.py120
-rwxr-xr-xtests/qemu-iotests/14798
-rw-r--r--tests/qemu-iotests/iotests.py14
19 files changed, 608 insertions, 136 deletions
diff --git a/block.c b/block.c
index 4f5ff2cc12..e795a7c5de 100644
--- a/block.c
+++ b/block.c
@@ -4119,6 +4119,154 @@ BlockDeviceInfoList *bdrv_named_nodes_list(Error **errp)
return list;
}
+#define QAPI_LIST_ADD(list, element) do { \
+ typeof(list) _tmp = g_new(typeof(*(list)), 1); \
+ _tmp->value = (element); \
+ _tmp->next = (list); \
+ (list) = _tmp; \
+} while (0)
+
+typedef struct XDbgBlockGraphConstructor {
+ XDbgBlockGraph *graph;
+ GHashTable *graph_nodes;
+} XDbgBlockGraphConstructor;
+
+static XDbgBlockGraphConstructor *xdbg_graph_new(void)
+{
+ XDbgBlockGraphConstructor *gr = g_new(XDbgBlockGraphConstructor, 1);
+
+ gr->graph = g_new0(XDbgBlockGraph, 1);
+ gr->graph_nodes = g_hash_table_new(NULL, NULL);
+
+ return gr;
+}
+
+static XDbgBlockGraph *xdbg_graph_finalize(XDbgBlockGraphConstructor *gr)
+{
+ XDbgBlockGraph *graph = gr->graph;
+
+ g_hash_table_destroy(gr->graph_nodes);
+ g_free(gr);
+
+ return graph;
+}
+
+static uintptr_t xdbg_graph_node_num(XDbgBlockGraphConstructor *gr, void *node)
+{
+ uintptr_t ret = (uintptr_t)g_hash_table_lookup(gr->graph_nodes, node);
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ /*
+ * Start counting from 1, not 0, because 0 interferes with not-found (NULL)
+ * answer of g_hash_table_lookup.
+ */
+ ret = g_hash_table_size(gr->graph_nodes) + 1;
+ g_hash_table_insert(gr->graph_nodes, node, (void *)ret);
+
+ return ret;
+}
+
+static void xdbg_graph_add_node(XDbgBlockGraphConstructor *gr, void *node,
+ XDbgBlockGraphNodeType type, const char *name)
+{
+ XDbgBlockGraphNode *n;
+
+ n = g_new0(XDbgBlockGraphNode, 1);
+
+ n->id = xdbg_graph_node_num(gr, node);
+ n->type = type;
+ n->name = g_strdup(name);
+
+ QAPI_LIST_ADD(gr->graph->nodes, n);
+}
+
+static void xdbg_graph_add_edge(XDbgBlockGraphConstructor *gr, void *parent,
+ const BdrvChild *child)
+{
+ typedef struct {
+ unsigned int flag;
+ BlockPermission num;
+ } PermissionMap;
+
+ static const PermissionMap permissions[] = {
+ { BLK_PERM_CONSISTENT_READ, BLOCK_PERMISSION_CONSISTENT_READ },
+ { BLK_PERM_WRITE, BLOCK_PERMISSION_WRITE },
+ { BLK_PERM_WRITE_UNCHANGED, BLOCK_PERMISSION_WRITE_UNCHANGED },
+ { BLK_PERM_RESIZE, BLOCK_PERMISSION_RESIZE },
+ { BLK_PERM_GRAPH_MOD, BLOCK_PERMISSION_GRAPH_MOD },
+ { 0, 0 }
+ };
+ const PermissionMap *p;
+ XDbgBlockGraphEdge *edge;
+
+ QEMU_BUILD_BUG_ON(1UL << (ARRAY_SIZE(permissions) - 1) != BLK_PERM_ALL + 1);
+
+ edge = g_new0(XDbgBlockGraphEdge, 1);
+
+ edge->parent = xdbg_graph_node_num(gr, parent);
+ edge->child = xdbg_graph_node_num(gr, child->bs);
+ edge->name = g_strdup(child->name);
+
+ for (p = permissions; p->flag; p++) {
+ if (p->flag & child->perm) {
+ QAPI_LIST_ADD(edge->perm, p->num);
+ }
+ if (p->flag & child->shared_perm) {
+ QAPI_LIST_ADD(edge->shared_perm, p->num);
+ }
+ }
+
+ QAPI_LIST_ADD(gr->graph->edges, edge);
+}
+
+
+XDbgBlockGraph *bdrv_get_xdbg_block_graph(Error **errp)
+{
+ BlockBackend *blk;
+ BlockJob *job;
+ BlockDriverState *bs;
+ BdrvChild *child;
+ XDbgBlockGraphConstructor *gr = xdbg_graph_new();
+
+ for (blk = blk_all_next(NULL); blk; blk = blk_all_next(blk)) {
+ char *allocated_name = NULL;
+ const char *name = blk_name(blk);
+
+ if (!*name) {
+ name = allocated_name = blk_get_attached_dev_id(blk);
+ }
+ xdbg_graph_add_node(gr, blk, X_DBG_BLOCK_GRAPH_NODE_TYPE_BLOCK_BACKEND,
+ name);
+ g_free(allocated_name);
+ if (blk_root(blk)) {
+ xdbg_graph_add_edge(gr, blk, blk_root(blk));
+ }
+ }
+
+ for (job = block_job_next(NULL); job; job = block_job_next(job)) {
+ GSList *el;
+
+ xdbg_graph_add_node(gr, job, X_DBG_BLOCK_GRAPH_NODE_TYPE_BLOCK_JOB,
+ job->job.id);
+ for (el = job->nodes; el; el = el->next) {
+ xdbg_graph_add_edge(gr, job, (BdrvChild *)el->data);
+ }
+ }
+
+ QTAILQ_FOREACH(bs, &graph_bdrv_states, node_list) {
+ xdbg_graph_add_node(gr, bs, X_DBG_BLOCK_GRAPH_NODE_TYPE_BLOCK_DRIVER,
+ bs->node_name);
+ QLIST_FOREACH(child, &bs->children, next) {
+ xdbg_graph_add_edge(gr, bs, child);
+ }
+ }
+
+ return xdbg_graph_finalize(gr);
+}
+
BlockDriverState *bdrv_lookup_bs(const char *device,
const char *node_name,
Error **errp)
diff --git a/block/block-backend.c b/block/block-backend.c
index 60d37a0c3d..cf05abd89d 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -2249,3 +2249,8 @@ int coroutine_fn blk_co_copy_range(BlockBackend *blk_in, int64_t off_in,
blk_out->root, off_out,
bytes, read_flags, write_flags);
}
+
+const BdrvChild *blk_root(BlockBackend *blk)
+{
+ return blk->root;
+}
diff --git a/block/curl.c b/block/curl.c
index db5d2bd8ef..b7ac265d3a 100644
--- a/block/curl.c
+++ b/block/curl.c
@@ -32,22 +32,10 @@
#include "crypto/secret.h"
#include <curl/curl.h>
#include "qemu/cutils.h"
+#include "trace.h"
-// #define DEBUG_CURL
// #define DEBUG_VERBOSE
-#ifdef DEBUG_CURL
-#define DEBUG_CURL_PRINT 1
-#else
-#define DEBUG_CURL_PRINT 0
-#endif
-#define DPRINTF(fmt, ...) \
- do { \
- if (DEBUG_CURL_PRINT) { \
- fprintf(stderr, fmt, ## __VA_ARGS__); \
- } \
- } while (0)
-
#if LIBCURL_VERSION_NUM >= 0x071000
/* The multi interface timer callback was introduced in 7.16.0 */
#define NEED_CURL_TIMER_CALLBACK
@@ -154,7 +142,7 @@ static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
{
BDRVCURLState *s = opaque;
- DPRINTF("CURL: timer callback timeout_ms %ld\n", timeout_ms);
+ trace_curl_timer_cb(timeout_ms);
if (timeout_ms == -1) {
timer_del(&s->timer);
} else {
@@ -193,7 +181,7 @@ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
}
socket = NULL;
- DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, (int)fd);
+ trace_curl_sock_cb(action, (int)fd);
switch (action) {
case CURL_POLL_IN:
aio_set_fd_handler(s->aio_context, fd, false,
@@ -238,7 +226,7 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
size_t realsize = size * nmemb;
int i;
- DPRINTF("CURL: Just reading %zd bytes\n", realsize);
+ trace_curl_read_cb(realsize);
if (!s || !s->orig_buf) {
goto read_end;
@@ -777,7 +765,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
}
}
- DPRINTF("CURL: Opening %s\n", file);
+ trace_curl_open(file);
qemu_co_queue_init(&s->free_state_waitq);
s->aio_context = bdrv_get_aio_context(bs);
s->url = g_strdup(file);
@@ -830,7 +818,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
"Server does not support 'range' (byte ranges).");
goto out;
}
- DPRINTF("CURL: Size = %" PRIu64 "\n", s->len);
+ trace_curl_open_size(s->len);
qemu_mutex_lock(&s->mutex);
curl_clean_state(state);
@@ -908,8 +896,7 @@ static void curl_setup_preadv(BlockDriverState *bs, CURLAIOCB *acb)
state->acb[0] = acb;
snprintf(state->range, 127, "%" PRIu64 "-%" PRIu64, start, end);
- DPRINTF("CURL (AIO): Reading %" PRIu64 " at %" PRIu64 " (%s)\n",
- acb->bytes, start, state->range);
+ trace_curl_setup_preadv(acb->bytes, start, state->range);
curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range);
curl_multi_add_handle(s->multi, state->curl);
@@ -943,7 +930,7 @@ static void curl_close(BlockDriverState *bs)
{
BDRVCURLState *s = bs->opaque;
- DPRINTF("CURL: Close\n");
+ trace_curl_close();
curl_detach_aio_context(bs);
qemu_mutex_destroy(&s->mutex);
diff --git a/block/file-posix.c b/block/file-posix.c
index 8aee7a3fb8..ba6ab62a38 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -102,19 +102,7 @@
#include <xfs/xfs.h>
#endif
-//#define DEBUG_BLOCK
-
-#ifdef DEBUG_BLOCK
-# define DEBUG_BLOCK_PRINT 1
-#else
-# define DEBUG_BLOCK_PRINT 0
-#endif
-#define DPRINTF(fmt, ...) \
-do { \
- if (DEBUG_BLOCK_PRINT) { \
- printf(fmt, ## __VA_ARGS__); \
- } \
-} while (0)
+#include "trace.h"
/* OS X does not have O_DSYNC */
#ifndef O_DSYNC
@@ -1411,7 +1399,7 @@ static int xfs_write_zeroes(BDRVRawState *s, int64_t offset, uint64_t bytes)
if (xfsctl(NULL, s->fd, XFS_IOC_ZERO_RANGE, &fl) < 0) {
err = errno;
- DPRINTF("cannot write zero range (%s)\n", strerror(errno));
+ trace_file_xfs_write_zeroes(strerror(errno));
return -err;
}
@@ -1430,7 +1418,7 @@ static int xfs_discard(BDRVRawState *s, int64_t offset, uint64_t bytes)
if (xfsctl(NULL, s->fd, XFS_IOC_UNRESVSP64, &fl) < 0) {
err = errno;
- DPRINTF("cannot punch hole (%s)\n", strerror(errno));
+ trace_file_xfs_discard(strerror(errno));
return -err;
}
@@ -2819,7 +2807,7 @@ static char *FindEjectableOpticalMedia(io_iterator_t *mediaIterator)
/* If a match was found, leave the loop */
if (*mediaIterator != 0) {
- DPRINTF("Matching using %s\n", matching_array[index]);
+ trace_file_FindEjectableOpticalMedia(matching_array[index]);
mediaType = g_strdup(matching_array[index]);
break;
}
@@ -2879,7 +2867,7 @@ static bool setup_cdrom(char *bsd_path, Error **errp)
if (partition_found == false) {
error_setg(errp, "Failed to find a working partition on disc");
} else {
- DPRINTF("Using %s as optical disc\n", test_partition);
+ trace_file_setup_cdrom(test_partition);
pstrcpy(bsd_path, MAXPATHLEN, test_partition);
}
return partition_found;
@@ -2974,8 +2962,7 @@ static bool hdev_is_sg(BlockDriverState *bs)
ret = ioctl(s->fd, SG_GET_SCSI_ID, &scsiid);
if (ret >= 0) {
- DPRINTF("SG device found: type=%d, version=%d\n",
- scsiid.scsi_type, sg_version);
+ trace_file_hdev_is_sg(scsiid.scsi_type, sg_version);
return true;
}
diff --git a/block/sheepdog.c b/block/sheepdog.c
index ed14f7afbe..b916ba07bf 100644
--- a/block/sheepdog.c
+++ b/block/sheepdog.c
@@ -28,6 +28,7 @@
#include "sysemu/block-backend.h"
#include "qemu/bitops.h"
#include "qemu/cutils.h"
+#include "trace.h"
#define SD_PROTO_VER 0x01
@@ -299,19 +300,6 @@ static inline size_t count_data_objs(const struct SheepdogInode *inode)
(1UL << inode->block_size_shift));
}
-#undef DPRINTF
-#ifdef DEBUG_SDOG
-#define DEBUG_SDOG_PRINT 1
-#else
-#define DEBUG_SDOG_PRINT 0
-#endif
-#define DPRINTF(fmt, args...) \
- do { \
- if (DEBUG_SDOG_PRINT) { \
- fprintf(stderr, "%s %d: " fmt, __func__, __LINE__, ##args); \
- } \
- } while (0)
-
typedef struct SheepdogAIOCB SheepdogAIOCB;
typedef struct BDRVSheepdogState BDRVSheepdogState;
@@ -750,7 +738,7 @@ static coroutine_fn void reconnect_to_sdog(void *opaque)
Error *local_err = NULL;
s->fd = get_sheep_fd(s, &local_err);
if (s->fd < 0) {
- DPRINTF("Wait for connection to be established\n");
+ trace_sheepdog_reconnect_to_sdog();
error_report_err(local_err);
qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 1000000000ULL);
}
@@ -847,7 +835,7 @@ static void coroutine_fn aio_read_response(void *opaque)
break;
case AIOCB_FLUSH_CACHE:
if (rsp.result == SD_RES_INVALID_PARMS) {
- DPRINTF("disable cache since the server doesn't support it\n");
+ trace_sheepdog_aio_read_response();
s->cache_flags = SD_FLAG_CMD_DIRECT;
rsp.result = SD_RES_SUCCESS;
}
@@ -1639,7 +1627,7 @@ static int sd_open(BlockDriverState *bs, QDict *options, int flags,
s->discard_supported = true;
if (snap_id || tag[0]) {
- DPRINTF("%" PRIx32 " snapshot inode was open.\n", vid);
+ trace_sheepdog_open(vid);
s->is_snapshot = true;
}
@@ -2252,7 +2240,7 @@ static void sd_close(BlockDriverState *bs)
unsigned int wlen, rlen = 0;
int fd, ret;
- DPRINTF("%s\n", s->name);
+ trace_sheepdog_close(s->name);
fd = connect_to_sdog(s, &local_err);
if (fd < 0) {
@@ -2429,7 +2417,7 @@ static int sd_create_branch(BDRVSheepdogState *s)
char *buf;
bool deleted;
- DPRINTF("%" PRIx32 " is snapshot.\n", s->inode.vdi_id);
+ trace_sheepdog_create_branch_snapshot(s->inode.vdi_id);
buf = g_malloc(SD_INODE_SIZE);
@@ -2445,7 +2433,7 @@ static int sd_create_branch(BDRVSheepdogState *s)
goto out;
}
- DPRINTF("%" PRIx32 " is created.\n", vid);
+ trace_sheepdog_create_branch_created(vid);
fd = connect_to_sdog(s, &local_err);
if (fd < 0) {
@@ -2467,7 +2455,7 @@ static int sd_create_branch(BDRVSheepdogState *s)
s->is_snapshot = false;
ret = 0;
- DPRINTF("%" PRIx32 " was newly created.\n", s->inode.vdi_id);
+ trace_sheepdog_create_branch_new(s->inode.vdi_id);
out:
g_free(buf);
@@ -2561,11 +2549,11 @@ static void coroutine_fn sd_co_rw_vector(SheepdogAIOCB *acb)
}
if (create) {
- DPRINTF("update ino (%" PRIu32 ") %" PRIu64 " %" PRIu64 " %ld\n",
- inode->vdi_id, oid,
- vid_to_data_oid(inode->data_vdi_id[idx], idx), idx);
+ trace_sheepdog_co_rw_vector_update(inode->vdi_id, oid,
+ vid_to_data_oid(inode->data_vdi_id[idx], idx),
+ idx);
oid = vid_to_data_oid(inode->vdi_id, idx);
- DPRINTF("new oid %" PRIx64 "\n", oid);
+ trace_sheepdog_co_rw_vector_new(oid);
}
aio_req = alloc_aio_req(s, acb, oid, len, offset, flags, create,
@@ -2670,9 +2658,8 @@ static int sd_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
SheepdogInode *inode;
unsigned int datalen;
- DPRINTF("sn_info: name %s id_str %s s: name %s vm_state_size %" PRId64 " "
- "is_snapshot %d\n", sn_info->name, sn_info->id_str,
- s->name, sn_info->vm_state_size, s->is_snapshot);
+ trace_sheepdog_snapshot_create_info(sn_info->name, sn_info->id_str, s->name,
+ sn_info->vm_state_size, s->is_snapshot);
if (s->is_snapshot) {
error_report("You can't create a snapshot of a snapshot VDI, "
@@ -2681,7 +2668,7 @@ static int sd_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
return -EINVAL;
}
- DPRINTF("%s %s\n", sn_info->name, sn_info->id_str);
+ trace_sheepdog_snapshot_create(sn_info->name, sn_info->id_str);
s->inode.vm_state_size = sn_info->vm_state_size;
s->inode.vm_clock_nsec = sn_info->vm_clock_nsec;
@@ -2726,8 +2713,8 @@ static int sd_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
}
memcpy(&s->inode, inode, datalen);
- DPRINTF("s->inode: name %s snap_id %x oid %x\n",
- s->inode.name, s->inode.snap_id, s->inode.vdi_id);
+ trace_sheepdog_snapshot_create_inode(s->inode.name, s->inode.snap_id,
+ s->inode.vdi_id);
cleanup:
g_free(inode);
diff --git a/block/ssh.c b/block/ssh.c
index 7fbc27abdf..bbc513e095 100644
--- a/block/ssh.c
+++ b/block/ssh.c
@@ -41,27 +41,17 @@
#include "qapi/qmp/qstring.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
+#include "trace.h"
-/* DEBUG_SSH=1 enables the DPRINTF (debugging printf) statements in
- * this block driver code.
- *
+/*
* TRACE_LIBSSH2=<bitmask> enables tracing in libssh2 itself. Note
* that this requires that libssh2 was specially compiled with the
* `./configure --enable-debug' option, so most likely you will have
* to compile it yourself. The meaning of <bitmask> is described
* here: http://www.libssh2.org/libssh2_trace.html
*/
-#define DEBUG_SSH 0
#define TRACE_LIBSSH2 0 /* or try: LIBSSH2_TRACE_SFTP */
-#define DPRINTF(fmt, ...) \
- do { \
- if (DEBUG_SSH) { \
- fprintf(stderr, "ssh: %-15s " fmt "\n", \
- __func__, ##__VA_ARGS__); \
- } \
- } while (0)
-
typedef struct BDRVSSHState {
/* Coroutine. */
CoMutex lock;
@@ -336,7 +326,7 @@ static int check_host_key_knownhosts(BDRVSSHState *s,
switch (r) {
case LIBSSH2_KNOWNHOST_CHECK_MATCH:
/* OK */
- DPRINTF("host key OK: %s", found->key);
+ trace_ssh_check_host_key_knownhosts(found->key);
break;
case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
ret = -EINVAL;
@@ -721,8 +711,7 @@ static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
}
/* Open the remote file. */
- DPRINTF("opening file %s flags=0x%x creat_mode=0%o",
- opts->path, ssh_flags, creat_mode);
+ trace_ssh_connect_to_ssh(opts->path, ssh_flags, creat_mode);
s->sftp_handle = libssh2_sftp_open(s->sftp, opts->path, ssh_flags,
creat_mode);
if (!s->sftp_handle) {
@@ -890,7 +879,7 @@ static int coroutine_fn ssh_co_create_opts(const char *filename, QemuOpts *opts,
/* Get desired file size. */
ssh_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
BDRV_SECTOR_SIZE);
- DPRINTF("total_size=%" PRIi64, ssh_opts->size);
+ trace_ssh_co_create_opts(ssh_opts->size);
uri_options = qdict_new();
ret = parse_uri(filename, uri_options, errp);
@@ -946,7 +935,7 @@ static void restart_coroutine(void *opaque)
BDRVSSHState *s = bs->opaque;
AioContext *ctx = bdrv_get_aio_context(bs);
- DPRINTF("co=%p", restart->co);
+ trace_ssh_restart_coroutine(restart->co);
aio_set_fd_handler(ctx, s->sock, false, NULL, NULL, NULL, NULL);
aio_co_wake(restart->co);
@@ -974,13 +963,12 @@ static coroutine_fn void co_yield(BDRVSSHState *s, BlockDriverState *bs)
wr_handler = restart_coroutine;
}
- DPRINTF("s->sock=%d rd_handler=%p wr_handler=%p", s->sock,
- rd_handler, wr_handler);
+ trace_ssh_co_yield(s->sock, rd_handler, wr_handler);
aio_set_fd_handler(bdrv_get_aio_context(bs), s->sock,
false, rd_handler, wr_handler, NULL, &restart);
qemu_coroutine_yield();
- DPRINTF("s->sock=%d - back", s->sock);
+ trace_ssh_co_yield_back(s->sock);
}
/* SFTP has a function `libssh2_sftp_seek64' which seeks to a position
@@ -1003,7 +991,7 @@ static void ssh_seek(BDRVSSHState *s, int64_t offset, int flags)
bool force = (flags & SSH_SEEK_FORCE) != 0;
if (force || op_read != s->offset_op_read || offset != s->offset) {
- DPRINTF("seeking to offset=%" PRIi64, offset);
+ trace_ssh_seek(offset);
libssh2_sftp_seek64(s->sftp_handle, offset);
s->offset = offset;
s->offset_op_read = op_read;
@@ -1019,7 +1007,7 @@ static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
char *buf, *end_of_vec;
struct iovec *i;
- DPRINTF("offset=%" PRIi64 " size=%zu", offset, size);
+ trace_ssh_read(offset, size);
ssh_seek(s, offset, SSH_SEEK_READ);
@@ -1038,9 +1026,9 @@ static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
*/
for (got = 0; got < size; ) {
again:
- DPRINTF("sftp_read buf=%p size=%zu", buf, end_of_vec - buf);
+ trace_ssh_read_buf(buf, end_of_vec - buf);
r = libssh2_sftp_read(s->sftp_handle, buf, end_of_vec - buf);
- DPRINTF("sftp_read returned %zd", r);
+ trace_ssh_read_return(r);
if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
co_yield(s, bs);
@@ -1094,7 +1082,7 @@ static int ssh_write(BDRVSSHState *s, BlockDriverState *bs,
char *buf, *end_of_vec;
struct iovec *i;
- DPRINTF("offset=%" PRIi64 " size=%zu", offset, size);
+ trace_ssh_write(offset, size);
ssh_seek(s, offset, SSH_SEEK_WRITE);
@@ -1108,9 +1096,9 @@ static int ssh_write(BDRVSSHState *s, BlockDriverState *bs,
for (written = 0; written < size; ) {
again:
- DPRINTF("sftp_write buf=%p size=%zu", buf, end_of_vec - buf);
+ trace_ssh_write_buf(buf, end_of_vec - buf);
r = libssh2_sftp_write(s->sftp_handle, buf, end_of_vec - buf);
- DPRINTF("sftp_write returned %zd", r);
+ trace_ssh_write_return(r);
if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
co_yield(s, bs);
@@ -1187,7 +1175,7 @@ static coroutine_fn int ssh_flush(BDRVSSHState *s, BlockDriverState *bs)
{
int r;
- DPRINTF("fsync");
+ trace_ssh_flush();
again:
r = libssh2_sftp_fsync(s->sftp_handle);
if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
@@ -1238,7 +1226,7 @@ static int64_t ssh_getlength(BlockDriverState *bs)
/* Note we cannot make a libssh2 call here. */
length = (int64_t) s->attrs.filesize;
- DPRINTF("length=%" PRIi64, length);
+ trace_ssh_getlength(length);
return length;
}
diff --git a/block/trace-events b/block/trace-events
index 693c14c443..70056eafd2 100644
--- a/block/trace-events
+++ b/block/trace-events
@@ -160,3 +160,50 @@ iscsi_xcopy(void *src_lun, uint64_t src_off, void *dst_lun, uint64_t dst_off, ui
# block/nbd-client.c
nbd_read_reply_entry_fail(int ret, const char *err) "ret = %d, err: %s"
nbd_co_request_fail(uint64_t from, uint32_t len, uint64_t handle, uint16_t flags, uint16_t type, const char *name, int ret, const char *err) "Request failed { .from = %" PRIu64", .len = %" PRIu32 ", .handle = %" PRIu64 ", .flags = 0x%" PRIx16 ", .type = %" PRIu16 " (%s) } ret = %d, err: %s"
+
+# block/ssh.c
+ssh_restart_coroutine(void *co) "co=%p"
+ssh_flush(void) "fsync"
+ssh_check_host_key_knownhosts(const char *key) "host key OK: %s"
+ssh_connect_to_ssh(char *path, int flags, int mode) "opening file %s flags=0x%x creat_mode=0%o"
+ssh_co_yield(int sock, void *rd_handler, void *wr_handler) "s->sock=%d rd_handler=%p wr_handler=%p"
+ssh_co_yield_back(int sock) "s->sock=%d - back"
+ssh_getlength(int64_t length) "length=%" PRIi64
+ssh_co_create_opts(uint64_t size) "total_size=%" PRIu64
+ssh_read(int64_t offset, size_t size) "offset=%" PRIi64 " size=%zu"
+ssh_read_buf(void *buf, size_t size) "sftp_read buf=%p size=%zu"
+ssh_read_return(ssize_t ret) "sftp_read returned %zd"
+ssh_write(int64_t offset, size_t size) "offset=%" PRIi64 " size=%zu"
+ssh_write_buf(void *buf, size_t size) "sftp_write buf=%p size=%zu"
+ssh_write_return(ssize_t ret) "sftp_write returned %zd"
+ssh_seek(int64_t offset) "seeking to offset=%" PRIi64
+
+# block/curl.c
+curl_timer_cb(long timeout_ms) "timer callback timeout_ms %ld"
+curl_sock_cb(int action, int fd) "sock action %d on fd %d"
+curl_read_cb(size_t realsize) "just reading %zu bytes"
+curl_open(const char *file) "opening %s"
+curl_open_size(uint64_t size) "size = %" PRIu64
+curl_setup_preadv(uint64_t bytes, uint64_t start, const char *range) "reading %" PRIu64 " at %" PRIu64 " (%s)"
+curl_close(void) "close"
+
+# block/file-posix.c
+file_xfs_write_zeroes(const char *error) "cannot write zero range (%s)"
+file_xfs_discard(const char *error) "cannot punch hole (%s)"
+file_FindEjectableOpticalMedia(const char *media) "Matching using %s"
+file_setup_cdrom(const char *partition) "Using %s as optical disc"
+file_hdev_is_sg(int type, int version) "SG device found: type=%d, version=%d"
+
+# block/sheepdog.c
+sheepdog_reconnect_to_sdog(void) "Wait for connection to be established"
+sheepdog_aio_read_response(void) "disable cache since the server doesn't support it"
+sheepdog_open(uint32_t vid) "0x%" PRIx32 " snapshot inode was open"
+sheepdog_close(const char *name) "%s"
+sheepdog_create_branch_snapshot(uint32_t vdi) "0x%" PRIx32 " is snapshot"
+sheepdog_create_branch_created(uint32_t vdi) "0x%" PRIx32 " is created"
+sheepdog_create_branch_new(uint32_t vdi) "0x%" PRIx32 " was newly created"
+sheepdog_co_rw_vector_update(uint32_t vdi, uint64_t oid, uint64_t data, long idx) "update ino (%" PRIu32 ") %" PRIu64 " %" PRIu64 " %ld"
+sheepdog_co_rw_vector_new(uint64_t oid) "new oid 0x%" PRIx64
+sheepdog_snapshot_create_info(const char *sn_name, const char *id, const char *name, int64_t size, int is_snapshot) "sn_info: name %s id_str %s s: name %s vm_state_size %" PRId64 " " "is_snapshot %d"
+sheepdog_snapshot_create(const char *sn_name, const char *id) "%s %s"
+sheepdog_snapshot_create_inode(const char *name, uint32_t snap, uint32_t vdi) "s->inode: name %s snap_id 0x%" PRIx32 " vdi 0x%" PRIx32
diff --git a/blockdev.c b/blockdev.c
index a8fa8748a9..fb18e9c975 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3582,6 +3582,11 @@ BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
return bdrv_named_nodes_list(errp);
}
+XDbgBlockGraph *qmp_x_debug_query_block_graph(Error **errp)
+{
+ return bdrv_get_xdbg_block_graph(errp);
+}
+
BlockJob *do_blockdev_backup(BlockdevBackup *backup, JobTxn *txn,
Error **errp)
{
diff --git a/configure b/configure
index b18281c61f..831d26d4ae 100755
--- a/configure
+++ b/configure
@@ -4269,6 +4269,17 @@ if compile_prog "" "" ; then
signalfd=yes
fi
+# check if optreset global is declared by <getopt.h>
+optreset="no"
+cat > $TMPC << EOF
+#include <getopt.h>
+int main(void) { return optreset; }
+EOF
+
+if compile_prog "" "" ; then
+ optreset=yes
+fi
+
# check if eventfd is supported
eventfd=no
cat > $TMPC << EOF
@@ -6643,6 +6654,9 @@ fi
if test "$signalfd" = "yes" ; then
echo "CONFIG_SIGNALFD=y" >> $config_host_mak
fi
+if test "$optreset" = "yes" ; then
+ echo "HAVE_OPTRESET=y" >> $config_host_mak
+fi
if test "$tcg" = "yes"; then
echo "CONFIG_TCG=y" >> $config_host_mak
if test "$tcg_interpreter" = "yes" ; then
diff --git a/hw/block/nvme.c b/hw/block/nvme.c
index 7c8c63e8f5..8325b5e88a 100644
--- a/hw/block/nvme.c
+++ b/hw/block/nvme.c
@@ -1208,6 +1208,11 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
int64_t bs_size;
uint8_t *pci_conf;
+ if (!n->num_queues) {
+ error_setg(errp, "num_queues can't be zero");
+ return;
+ }
+
if (!n->conf.blk) {
error_setg(errp, "drive property not set");
return;
@@ -1233,7 +1238,7 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
pci_conf[PCI_INTERRUPT_PIN] = 1;
pci_config_set_prog_interface(pci_dev->config, 0x2);
pci_config_set_class(pci_dev->config, PCI_CLASS_STORAGE_EXPRESS);
- pcie_endpoint_cap_init(&n->parent_obj, 0x80);
+ pcie_endpoint_cap_init(pci_dev, 0x80);
n->num_namespaces = 1;
n->reg_size = pow2ceil(0x1004 + 2 * (n->num_queues + 1) * 4);
@@ -1245,10 +1250,10 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
memory_region_init_io(&n->iomem, OBJECT(n), &nvme_mmio_ops, n,
"nvme", n->reg_size);
- pci_register_bar(&n->parent_obj, 0,
+ pci_register_bar(pci_dev, 0,
PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64,
&n->iomem);
- msix_init_exclusive_bar(&n->parent_obj, n->num_queues, 4, NULL);
+ msix_init_exclusive_bar(pci_dev, n->num_queues, 4, NULL);
id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID));
id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID));
@@ -1303,7 +1308,7 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
n->cmbuf = g_malloc0(NVME_CMBSZ_GETSIZE(n->bar.cmbsz));
memory_region_init_io(&n->ctrl_mem, OBJECT(n), &nvme_cmb_ops, n,
"nvme-cmb", NVME_CMBSZ_GETSIZE(n->bar.cmbsz));
- pci_register_bar(&n->parent_obj, NVME_CMBLOC_BIR(n->bar.cmbloc),
+ pci_register_bar(pci_dev, NVME_CMBLOC_BIR(n->bar.cmbloc),
PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64 |
PCI_BASE_ADDRESS_MEM_PREFETCH, &n->ctrl_mem);
@@ -1381,7 +1386,7 @@ static void nvme_instance_init(Object *obj)
}
static const TypeInfo nvme_info = {
- .name = "nvme",
+ .name = TYPE_NVME,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(NvmeCtrl),
.class_init = nvme_class_init,
diff --git a/include/block/block.h b/include/block/block.h
index f70a843b72..57233cf2c0 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -448,6 +448,7 @@ void bdrv_eject(BlockDriverState *bs, bool eject_flag);
const char *bdrv_get_format_name(BlockDriverState *bs);
BlockDriverState *bdrv_find_node(const char *node_name);
BlockDeviceInfoList *bdrv_named_nodes_list(Error **errp);
+XDbgBlockGraph *bdrv_get_xdbg_block_graph(Error **errp);
BlockDriverState *bdrv_lookup_bs(const char *device,
const char *node_name,
Error **errp);
diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h
index 80df7253db..840af09cb0 100644
--- a/include/qemu/osdep.h
+++ b/include/qemu/osdep.h
@@ -109,6 +109,7 @@ extern int daemon(int, int);
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
+#include <getopt.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <assert.h>
@@ -604,4 +605,19 @@ extern int qemu_icache_linesize_log;
extern int qemu_dcache_linesize;
extern int qemu_dcache_linesize_log;
+/*
+ * After using getopt or getopt_long, if you need to parse another set
+ * of options, then you must reset optind. Unfortunately the way to
+ * do this varies between implementations of getopt.
+ */
+static inline void qemu_reset_optind(void)
+{
+#ifdef HAVE_OPTRESET
+ optind = 1;
+ optreset = 1;
+#else
+ optind = 0;
+#endif
+}
+
#endif
diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
index c96bcdee14..c8f816a200 100644
--- a/include/sysemu/block-backend.h
+++ b/include/sysemu/block-backend.h
@@ -237,4 +237,6 @@ int coroutine_fn blk_co_copy_range(BlockBackend *blk_in, int64_t off_in,
int bytes, BdrvRequestFlags read_flags,
BdrvRequestFlags write_flags);
+const BdrvChild *blk_root(BlockBackend *blk);
+
#endif
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 91685be6c2..cde1b53708 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1666,6 +1666,114 @@
{ 'command': 'query-named-block-nodes', 'returns': [ 'BlockDeviceInfo' ] }
##
+# @XDbgBlockGraphNodeType:
+#
+# @block-backend: corresponds to BlockBackend
+#
+# @block-job: corresonds to BlockJob
+#
+# @block-driver: corresponds to BlockDriverState
+#
+# Since: 4.0
+##
+{ 'enum': 'XDbgBlockGraphNodeType',
+ 'data': [ 'block-backend', 'block-job', 'block-driver' ] }
+
+##
+# @XDbgBlockGraphNode:
+#
+# @id: Block graph node identifier. This @id is generated only for
+# x-debug-query-block-graph and does not relate to any other identifiers in
+# Qemu.
+#
+# @type: Type of graph node. Can be one of block-backend, block-job or
+# block-driver-state.
+#
+# @name: Human readable name of the node. Corresponds to node-name for
+# block-driver-state nodes; is not guaranteed to be unique in the whole
+# graph (with block-jobs and block-backends).
+#
+# Since: 4.0
+##
+{ 'struct': 'XDbgBlockGraphNode',
+ 'data': { 'id': 'uint64', 'type': 'XDbgBlockGraphNodeType', 'name': 'str' } }
+
+##
+# @BlockPermission:
+#
+# Enum of base block permissions.
+#
+# @consistent-read: A user that has the "permission" of consistent reads is
+# guaranteed that their view of the contents of the block
+# device is complete and self-consistent, representing the
+# contents of a disk at a specific point.
+# For most block devices (including their backing files) this
+# is true, but the property cannot be maintained in a few
+# situations like for intermediate nodes of a commit block
+# job.
+#
+# @write: This permission is required to change the visible disk contents.
+#
+# @write-unchanged: This permission (which is weaker than BLK_PERM_WRITE) is
+# both enough and required for writes to the block node when
+# the caller promises that the visible disk content doesn't
+# change.
+# As the BLK_PERM_WRITE permission is strictly stronger,
+# either is sufficient to perform an unchanging write.
+#
+# @resize: This permission is required to change the size of a block node.
+#
+# @graph-mod: This permission is required to change the node that this
+# BdrvChild points to.
+#
+# Since: 4.0
+##
+ { 'enum': 'BlockPermission',
+ 'data': [ 'consistent-read', 'write', 'write-unchanged', 'resize',
+ 'graph-mod' ] }
+##
+# @XDbgBlockGraphEdge:
+#
+# Block Graph edge description for x-debug-query-block-graph.
+#
+# @parent: parent id
+#
+# @child: child id
+#
+# @name: name of the relation (examples are 'file' and 'backing')
+#
+# @perm: granted permissions for the parent operating on the child
+#
+# @shared-perm: permissions that can still be granted to other users of the
+# child while it is still attached to this parent
+#
+# Since: 4.0
+##
+{ 'struct': 'XDbgBlockGraphEdge',
+ 'data': { 'parent': 'uint64', 'child': 'uint64',
+ 'name': 'str', 'perm': [ 'BlockPermission' ],
+ 'shared-perm': [ 'BlockPermission' ] } }
+
+##
+# @XDbgBlockGraph:
+#
+# Block Graph - list of nodes and list of edges.
+#
+# Since: 4.0
+##
+{ 'struct': 'XDbgBlockGraph',
+ 'data': { 'nodes': ['XDbgBlockGraphNode'], 'edges': ['XDbgBlockGraphEdge'] } }
+
+##
+# @x-debug-query-block-graph:
+#
+# Get the block graph.
+#
+# Since: 4.0
+##
+{ 'command': 'x-debug-query-block-graph', 'returns': 'XDbgBlockGraph' }
+
+##
# @drive-mirror:
#
# Start mirroring a block device's writes to a new destination. target
diff --git a/qemu-img.c b/qemu-img.c
index ad04f59565..25288c4d18 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -4962,7 +4962,7 @@ int main(int argc, char **argv)
return 0;
}
argv += optind;
- optind = 0;
+ qemu_reset_optind();
if (!trace_init_backends()) {
exit(1);
diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index 2c39124036..ee8f56e46a 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -114,7 +114,7 @@ static int command(BlockBackend *blk, const cmdinfo_t *ct, int argc,
}
}
- optind = 0;
+ qemu_reset_optind();
return ct->cfunc(blk, argc, argv);
}
diff --git a/scripts/render_block_graph.py b/scripts/render_block_graph.py
new file mode 100755
index 0000000000..ed7e581b4f
--- /dev/null
+++ b/scripts/render_block_graph.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+#
+# Render Qemu Block Graph
+#
+# Copyright (c) 2018 Virtuozzo International GmbH. All rights reserved.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import sys
+import subprocess
+import json
+from graphviz import Digraph
+from qemu import MonitorResponseError
+
+
+def perm(arr):
+ s = 'w' if 'write' in arr else '_'
+ s += 'r' if 'consistent-read' in arr else '_'
+ s += 'u' if 'write-unchanged' in arr else '_'
+ s += 'g' if 'graph-mod' in arr else '_'
+ s += 's' if 'resize' in arr else '_'
+ return s
+
+
+def render_block_graph(qmp, filename, format='png'):
+ '''
+ Render graph in text (dot) representation into "@filename" and
+ representation in @format into "@filename.@format"
+ '''
+
+ bds_nodes = qmp.command('query-named-block-nodes')
+ bds_nodes = {n['node-name']: n for n in bds_nodes}
+
+ job_nodes = qmp.command('query-block-jobs')
+ job_nodes = {n['device']: n for n in job_nodes}
+
+ block_graph = qmp.command('x-debug-query-block-graph')
+
+ graph = Digraph(comment='Block Nodes Graph')
+ graph.format = format
+ graph.node('permission symbols:\l'
+ ' w - Write\l'
+ ' r - consistent-Read\l'
+ ' u - write - Unchanged\l'
+ ' g - Graph-mod\l'
+ ' s - reSize\l'
+ 'edge label scheme:\l'
+ ' <child type>\l'
+ ' <perm>\l'
+ ' <shared_perm>\l', shape='none')
+
+ for n in block_graph['nodes']:
+ if n['type'] == 'block-driver':
+ info = bds_nodes[n['name']]
+ label = n['name'] + ' [' + info['drv'] + ']'
+ if info['drv'] == 'file':
+ label += '\n' + os.path.basename(info['file'])
+ shape = 'ellipse'
+ elif n['type'] == 'block-job':
+ info = job_nodes[n['name']]
+ label = info['type'] + ' job (' + n['name'] + ')'
+ shape = 'box'
+ else:
+ assert n['type'] == 'block-backend'
+ label = n['name'] if n['name'] else 'unnamed blk'
+ shape = 'box'
+
+ graph.node(str(n['id']), label, shape=shape)
+
+ for e in block_graph['edges']:
+ label = '%s\l%s\l%s\l' % (e['name'], perm(e['perm']),
+ perm(e['shared-perm']))
+ graph.edge(str(e['parent']), str(e['child']), label=label)
+
+ graph.render(filename)
+
+
+class LibvirtGuest():
+ def __init__(self, name):
+ self.name = name
+
+ def command(self, cmd):
+ # only supports qmp commands without parameters
+ m = {'execute': cmd}
+ ar = ['virsh', 'qemu-monitor-command', self.name, json.dumps(m)]
+
+ reply = json.loads(subprocess.check_output(ar))
+
+ if 'error' in reply:
+ raise MonitorResponseError(reply)
+
+ return reply['return']
+
+
+if __name__ == '__main__':
+ obj = sys.argv[1]
+ out = sys.argv[2]
+
+ if os.path.exists(obj):
+ # assume unix socket
+ qmp = QEMUMonitorProtocol(obj)
+ qmp.connect()
+ else:
+ # assume libvirt guest name
+ qmp = LibvirtGuest(obj)
+
+ render_block_graph(qmp, out)
diff --git a/tests/qemu-iotests/147 b/tests/qemu-iotests/147
index 05b374b7d3..82513279b0 100755
--- a/tests/qemu-iotests/147
+++ b/tests/qemu-iotests/147
@@ -19,13 +19,17 @@
#
import os
+import random
import socket
import stat
import time
import iotests
-from iotests import cachemode, imgfmt, qemu_img, qemu_nbd
+from iotests import cachemode, imgfmt, qemu_img, qemu_nbd, qemu_nbd_pipe
-NBD_PORT = 10811
+NBD_PORT_START = 32768
+NBD_PORT_END = NBD_PORT_START + 1024
+NBD_IPV6_PORT_START = NBD_PORT_END
+NBD_IPV6_PORT_END = NBD_IPV6_PORT_START + 1024
test_img = os.path.join(iotests.test_dir, 'test.img')
unix_socket = os.path.join(iotests.test_dir, 'nbd.socket')
@@ -88,17 +92,29 @@ class QemuNBD(NBDBlockdevAddBase):
except OSError:
pass
+ def _try_server_up(self, *args):
+ status, msg = qemu_nbd_pipe('-f', imgfmt, test_img, *args)
+ if status == 0:
+ return True
+ if 'Address already in use' in msg:
+ return False
+ self.fail(msg)
+
def _server_up(self, *args):
- self.assertEqual(qemu_nbd('-f', imgfmt, test_img, *args), 0)
+ self.assertTrue(self._try_server_up(*args))
def test_inet(self):
- self._server_up('-p', str(NBD_PORT))
+ while True:
+ nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END)
+ if self._try_server_up('-b', 'localhost', '-p', str(nbd_port)):
+ break
+
address = { 'type': 'inet',
'data': {
'host': 'localhost',
- 'port': str(NBD_PORT)
+ 'port': str(nbd_port)
} }
- self.client_test('nbd://localhost:%i' % NBD_PORT,
+ self.client_test('nbd://localhost:%i' % nbd_port,
flatten_sock_addr(address))
def test_unix(self):
@@ -130,8 +146,13 @@ class BuiltinNBD(NBDBlockdevAddBase):
except OSError:
pass
- def _server_up(self, address, export_name=None, export_name2=None):
+ # Returns False on EADDRINUSE; fails an assertion on other errors.
+ # Returns True on success.
+ def _try_server_up(self, address, export_name=None, export_name2=None):
result = self.server.qmp('nbd-server-start', addr=address)
+ if 'error' in result and \
+ 'Address already in use' in result['error']['desc']:
+ return False
self.assert_qmp(result, 'return', {})
if export_name is None:
@@ -146,20 +167,28 @@ class BuiltinNBD(NBDBlockdevAddBase):
name=export_name2)
self.assert_qmp(result, 'return', {})
+ return True
+
+ def _server_up(self, address, export_name=None, export_name2=None):
+ self.assertTrue(self._try_server_up(address, export_name, export_name2))
def _server_down(self):
result = self.server.qmp('nbd-server-stop')
self.assert_qmp(result, 'return', {})
def do_test_inet(self, export_name=None):
- address = { 'type': 'inet',
- 'data': {
- 'host': 'localhost',
- 'port': str(NBD_PORT)
- } }
- self._server_up(address, export_name)
+ while True:
+ nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END)
+ address = { 'type': 'inet',
+ 'data': {
+ 'host': 'localhost',
+ 'port': str(nbd_port)
+ } }
+ if self._try_server_up(address, export_name):
+ break
+
export_name = export_name or 'nbd-export'
- self.client_test('nbd://localhost:%i/%s' % (NBD_PORT, export_name),
+ self.client_test('nbd://localhost:%i/%s' % (nbd_port, export_name),
flatten_sock_addr(address), export_name)
self._server_down()
@@ -173,15 +202,19 @@ class BuiltinNBD(NBDBlockdevAddBase):
self.do_test_inet('shadow')
def test_inet_two_exports(self):
- address = { 'type': 'inet',
- 'data': {
- 'host': 'localhost',
- 'port': str(NBD_PORT)
- } }
- self._server_up(address, 'exp1', 'exp2')
- self.client_test('nbd://localhost:%i/%s' % (NBD_PORT, 'exp1'),
+ while True:
+ nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END)
+ address = { 'type': 'inet',
+ 'data': {
+ 'host': 'localhost',
+ 'port': str(nbd_port)
+ } }
+ if self._try_server_up(address, 'exp1', 'exp2'):
+ break
+
+ self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp1'),
flatten_sock_addr(address), 'exp1', 'node1', False)
- self.client_test('nbd://localhost:%i/%s' % (NBD_PORT, 'exp2'),
+ self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp2'),
flatten_sock_addr(address), 'exp2', 'node2', False)
result = self.vm.qmp('blockdev-del', node_name='node1')
self.assert_qmp(result, 'return', {})
@@ -197,20 +230,25 @@ class BuiltinNBD(NBDBlockdevAddBase):
except socket.gaierror:
# IPv6 not available, skip
return
- address = { 'type': 'inet',
- 'data': {
- 'host': '::1',
- 'port': str(NBD_PORT),
- 'ipv4': False,
- 'ipv6': True
- } }
+
+ while True:
+ nbd_port = random.randrange(NBD_IPV6_PORT_START, NBD_IPV6_PORT_END)
+ address = { 'type': 'inet',
+ 'data': {
+ 'host': '::1',
+ 'port': str(nbd_port),
+ 'ipv4': False,
+ 'ipv6': True
+ } }
+ if self._try_server_up(address):
+ break
+
filename = { 'driver': 'raw',
'file': {
'driver': 'nbd',
'export': 'nbd-export',
'server': flatten_sock_addr(address)
} }
- self._server_up(address)
self.client_test(filename, flatten_sock_addr(address), 'nbd-export')
self._server_down()
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index cbedfaf1df..009c614ef7 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -201,6 +201,20 @@ def qemu_nbd(*args):
'''Run qemu-nbd in daemon mode and return the parent's exit code'''
return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
+def qemu_nbd_pipe(*args):
+ '''Run qemu-nbd in daemon mode and return both the parent's exit code
+ and its output'''
+ subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ exitcode = subp.wait()
+ if exitcode < 0:
+ sys.stderr.write('qemu-nbd received signal %i: %s\n' %
+ (-exitcode,
+ ' '.join(qemu_nbd_args + ['--fork'] + list(args))))
+ return exitcode, subp.communicate()[0]
+
def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
'''Return True if two image files are identical'''
return qemu_img('compare', '-f', fmt1,