Block layer patches
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJYEjZDAAoJEH8JsnLIjy/WSXQQALz5t/O6jnLkLMceuJvAdxzk 1w3cwcc2I2VOSubqnUXuK9KCFK0R8ifN0UmYNENbh9DCNEhDpbD16hbkO5LvnRwk IaFpt1jAP7Y9epZ3GnJ0JdAsk+PDnqj3zQW6PSJLyzXVOnyVCE7aU+fjEv3Khoa7 88ye3xBx8F9RDaQ4EBOSm55q/gEk0RHnFbgq/YurHzWg5go23VwunVBP7XALG6Bs 2jm5/iEKXdoIoJ57dADQzUf2WWMgE73CO9kd/c9iaFmd2FWOHHRsuFyj7a38bKmn N7kRDBrC3MlOPxP6zB2jBNmAa70cdQO9Ktqm7geTTb1WWSxxUHBuX1TtAsish6d7 aYVSZyNoaSdwcRSnNTnJkscNItldAUtoPvgrYCbniWVRU7YiY+yUXsQyWdmpKbTE JLy06p4mGZEuDR5RDMWZfaJbw+eNtmpiL9vMRBM+A9EzMIhuVm1hu34/SRyvcPSM fyzW5gAYsPA7E+nZT1Jkpw/f8jxxUo1vdhQpWWGijCMK5kpkumfWXZKnuSKwvRYz xcHdGE1nbLfWaBgyClZCLRMNb3CFuDEsr4NCrjwGr4xdLs9VbxXxvNqvFl8DOWR/ amVsQd6fvTfcusDxY9hY6BOlgID/dqfi7wzvxowDhsTG6ewSsqwjYLzUwADzcKn0 sAmLBX3P3sJNrf7Y9UaT =BtvM -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches # gpg: Signature made Thu 27 Oct 2016 18:15:47 BST # gpg: using RSA key 0x7F09B272C88F2FD6 # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" # Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6 * remotes/kevin/tags/for-upstream: (23 commits) iotests: Add test for NBD's blockdev-add interface iotests: Add assert_json_filename_equal() method socket_scm_helper: Accept fd directly iotests.py: Allow concurrent qemu instances iotests.py: Add qemu_nbd function qapi: Allow blockdev-add for NBD block/nbd: Use SocketAddress options block/nbd: Accept SocketAddress block/nbd: Add nbd_has_filename_options_conflict() block/nbd: Use qdict_put() block/nbd: Default port in nbd_refresh_filename() block/nbd: Reject port parameter without host block/nbd: Drop trailing "." in error messages qemu-iotests: Fix typo for NFS with IMGOPTSSYNTAX block: Remove bdrv_aio_ioctl() raw: Implement .bdrv_co_ioctl instead of .bdrv_aio_ioctl block: Introduce .bdrv_co_ioctl() driver callback block: Remove bdrv_ioctl() raw-posix: Don't use bdrv_ioctl() block: Use blk_co_ioctl() for all BB level ioctls ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
9879b75873
@ -1099,26 +1099,36 @@ BlockAIOCB *blk_aio_pwritev(BlockBackend *blk, int64_t offset,
|
||||
blk_aio_write_entry, flags, cb, opaque);
|
||||
}
|
||||
|
||||
static void blk_aio_flush_entry(void *opaque)
|
||||
{
|
||||
BlkAioEmAIOCB *acb = opaque;
|
||||
BlkRwCo *rwco = &acb->rwco;
|
||||
|
||||
rwco->ret = blk_co_flush(rwco->blk);
|
||||
blk_aio_complete(acb);
|
||||
}
|
||||
|
||||
BlockAIOCB *blk_aio_flush(BlockBackend *blk,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
if (!blk_is_available(blk)) {
|
||||
return blk_abort_aio_request(blk, cb, opaque, -ENOMEDIUM);
|
||||
}
|
||||
return blk_aio_prwv(blk, 0, 0, NULL, blk_aio_flush_entry, 0, cb, opaque);
|
||||
}
|
||||
|
||||
return bdrv_aio_flush(blk_bs(blk), cb, opaque);
|
||||
static void blk_aio_pdiscard_entry(void *opaque)
|
||||
{
|
||||
BlkAioEmAIOCB *acb = opaque;
|
||||
BlkRwCo *rwco = &acb->rwco;
|
||||
|
||||
rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, acb->bytes);
|
||||
blk_aio_complete(acb);
|
||||
}
|
||||
|
||||
BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk,
|
||||
int64_t offset, int count,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
int ret = blk_check_byte_request(blk, offset, count);
|
||||
if (ret < 0) {
|
||||
return blk_abort_aio_request(blk, cb, opaque, ret);
|
||||
}
|
||||
|
||||
return bdrv_aio_pdiscard(blk_bs(blk), offset, count, cb, opaque);
|
||||
return blk_aio_prwv(blk, offset, count, NULL, blk_aio_pdiscard_entry, 0,
|
||||
cb, opaque);
|
||||
}
|
||||
|
||||
void blk_aio_cancel(BlockAIOCB *acb)
|
||||
@ -1131,23 +1141,50 @@ void blk_aio_cancel_async(BlockAIOCB *acb)
|
||||
bdrv_aio_cancel_async(acb);
|
||||
}
|
||||
|
||||
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
|
||||
int blk_co_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
|
||||
{
|
||||
if (!blk_is_available(blk)) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
return bdrv_ioctl(blk_bs(blk), req, buf);
|
||||
return bdrv_co_ioctl(blk_bs(blk), req, buf);
|
||||
}
|
||||
|
||||
static void blk_ioctl_entry(void *opaque)
|
||||
{
|
||||
BlkRwCo *rwco = opaque;
|
||||
rwco->ret = blk_co_ioctl(rwco->blk, rwco->offset,
|
||||
rwco->qiov->iov[0].iov_base);
|
||||
}
|
||||
|
||||
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
|
||||
{
|
||||
return blk_prw(blk, req, buf, 0, blk_ioctl_entry, 0);
|
||||
}
|
||||
|
||||
static void blk_aio_ioctl_entry(void *opaque)
|
||||
{
|
||||
BlkAioEmAIOCB *acb = opaque;
|
||||
BlkRwCo *rwco = &acb->rwco;
|
||||
|
||||
rwco->ret = blk_co_ioctl(rwco->blk, rwco->offset,
|
||||
rwco->qiov->iov[0].iov_base);
|
||||
blk_aio_complete(acb);
|
||||
}
|
||||
|
||||
BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
if (!blk_is_available(blk)) {
|
||||
return blk_abort_aio_request(blk, cb, opaque, -ENOMEDIUM);
|
||||
}
|
||||
QEMUIOVector qiov;
|
||||
struct iovec iov;
|
||||
|
||||
return bdrv_aio_ioctl(blk_bs(blk), req, buf, cb, opaque);
|
||||
iov = (struct iovec) {
|
||||
.iov_base = buf,
|
||||
.iov_len = 0,
|
||||
};
|
||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
||||
|
||||
return blk_aio_prwv(blk, req, 0, &qiov, blk_aio_ioctl_entry, 0, cb, opaque);
|
||||
}
|
||||
|
||||
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int count)
|
||||
@ -1169,13 +1206,15 @@ int blk_co_flush(BlockBackend *blk)
|
||||
return bdrv_co_flush(blk_bs(blk));
|
||||
}
|
||||
|
||||
static void blk_flush_entry(void *opaque)
|
||||
{
|
||||
BlkRwCo *rwco = opaque;
|
||||
rwco->ret = blk_co_flush(rwco->blk);
|
||||
}
|
||||
|
||||
int blk_flush(BlockBackend *blk)
|
||||
{
|
||||
if (!blk_is_available(blk)) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
return bdrv_flush(blk_bs(blk));
|
||||
return blk_prw(blk, 0, NULL, 0, blk_flush_entry, 0);
|
||||
}
|
||||
|
||||
void blk_drain(BlockBackend *blk)
|
||||
@ -1555,14 +1594,15 @@ int blk_truncate(BlockBackend *blk, int64_t offset)
|
||||
return bdrv_truncate(blk_bs(blk), offset);
|
||||
}
|
||||
|
||||
static void blk_pdiscard_entry(void *opaque)
|
||||
{
|
||||
BlkRwCo *rwco = opaque;
|
||||
rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, rwco->qiov->size);
|
||||
}
|
||||
|
||||
int blk_pdiscard(BlockBackend *blk, int64_t offset, int count)
|
||||
{
|
||||
int ret = blk_check_byte_request(blk, offset, count);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_pdiscard(blk_bs(blk), offset, count);
|
||||
return blk_prw(blk, offset, NULL, count, blk_pdiscard_entry, 0);
|
||||
}
|
||||
|
||||
int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,
|
||||
|
111
block/io.c
111
block/io.c
@ -2196,35 +2196,6 @@ BlockAIOCB *bdrv_aio_flush(BlockDriverState *bs,
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
static void coroutine_fn bdrv_aio_pdiscard_co_entry(void *opaque)
|
||||
{
|
||||
BlockAIOCBCoroutine *acb = opaque;
|
||||
BlockDriverState *bs = acb->common.bs;
|
||||
|
||||
acb->req.error = bdrv_co_pdiscard(bs, acb->req.offset, acb->req.bytes);
|
||||
bdrv_co_complete(acb);
|
||||
}
|
||||
|
||||
BlockAIOCB *bdrv_aio_pdiscard(BlockDriverState *bs, int64_t offset, int count,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
Coroutine *co;
|
||||
BlockAIOCBCoroutine *acb;
|
||||
|
||||
trace_bdrv_aio_pdiscard(bs, offset, count, opaque);
|
||||
|
||||
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, bs, cb, opaque);
|
||||
acb->need_bh = true;
|
||||
acb->req.error = -EINPROGRESS;
|
||||
acb->req.offset = offset;
|
||||
acb->req.bytes = count;
|
||||
co = qemu_coroutine_create(bdrv_aio_pdiscard_co_entry, acb);
|
||||
qemu_coroutine_enter(co);
|
||||
|
||||
bdrv_co_maybe_schedule_bh(acb);
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
void *qemu_aio_get(const AIOCBInfo *aiocb_info, BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
@ -2521,7 +2492,7 @@ int bdrv_pdiscard(BlockDriverState *bs, int64_t offset, int count)
|
||||
return rwco.ret;
|
||||
}
|
||||
|
||||
static int bdrv_co_do_ioctl(BlockDriverState *bs, int req, void *buf)
|
||||
int bdrv_co_ioctl(BlockDriverState *bs, int req, void *buf)
|
||||
{
|
||||
BlockDriver *drv = bs->drv;
|
||||
BdrvTrackedRequest tracked_req;
|
||||
@ -2531,86 +2502,26 @@ static int bdrv_co_do_ioctl(BlockDriverState *bs, int req, void *buf)
|
||||
BlockAIOCB *acb;
|
||||
|
||||
tracked_request_begin(&tracked_req, bs, 0, 0, BDRV_TRACKED_IOCTL);
|
||||
if (!drv || !drv->bdrv_aio_ioctl) {
|
||||
if (!drv || (!drv->bdrv_aio_ioctl && !drv->bdrv_co_ioctl)) {
|
||||
co.ret = -ENOTSUP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
acb = drv->bdrv_aio_ioctl(bs, req, buf, bdrv_co_io_em_complete, &co);
|
||||
if (!acb) {
|
||||
co.ret = -ENOTSUP;
|
||||
goto out;
|
||||
if (drv->bdrv_co_ioctl) {
|
||||
co.ret = drv->bdrv_co_ioctl(bs, req, buf);
|
||||
} else {
|
||||
acb = drv->bdrv_aio_ioctl(bs, req, buf, bdrv_co_io_em_complete, &co);
|
||||
if (!acb) {
|
||||
co.ret = -ENOTSUP;
|
||||
goto out;
|
||||
}
|
||||
qemu_coroutine_yield();
|
||||
}
|
||||
qemu_coroutine_yield();
|
||||
out:
|
||||
tracked_request_end(&tracked_req);
|
||||
return co.ret;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
BlockDriverState *bs;
|
||||
int req;
|
||||
void *buf;
|
||||
int ret;
|
||||
} BdrvIoctlCoData;
|
||||
|
||||
static void coroutine_fn bdrv_co_ioctl_entry(void *opaque)
|
||||
{
|
||||
BdrvIoctlCoData *data = opaque;
|
||||
data->ret = bdrv_co_do_ioctl(data->bs, data->req, data->buf);
|
||||
}
|
||||
|
||||
/* needed for generic scsi interface */
|
||||
int bdrv_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
|
||||
{
|
||||
BdrvIoctlCoData data = {
|
||||
.bs = bs,
|
||||
.req = req,
|
||||
.buf = buf,
|
||||
.ret = -EINPROGRESS,
|
||||
};
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
/* Fast-path if already in coroutine context */
|
||||
bdrv_co_ioctl_entry(&data);
|
||||
} else {
|
||||
Coroutine *co = qemu_coroutine_create(bdrv_co_ioctl_entry, &data);
|
||||
|
||||
qemu_coroutine_enter(co);
|
||||
while (data.ret == -EINPROGRESS) {
|
||||
aio_poll(bdrv_get_aio_context(bs), true);
|
||||
}
|
||||
}
|
||||
return data.ret;
|
||||
}
|
||||
|
||||
static void coroutine_fn bdrv_co_aio_ioctl_entry(void *opaque)
|
||||
{
|
||||
BlockAIOCBCoroutine *acb = opaque;
|
||||
acb->req.error = bdrv_co_do_ioctl(acb->common.bs,
|
||||
acb->req.req, acb->req.buf);
|
||||
bdrv_co_complete(acb);
|
||||
}
|
||||
|
||||
BlockAIOCB *bdrv_aio_ioctl(BlockDriverState *bs,
|
||||
unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb, void *opaque)
|
||||
{
|
||||
BlockAIOCBCoroutine *acb = qemu_aio_get(&bdrv_em_co_aiocb_info,
|
||||
bs, cb, opaque);
|
||||
Coroutine *co;
|
||||
|
||||
acb->need_bh = true;
|
||||
acb->req.error = -EINPROGRESS;
|
||||
acb->req.req = req;
|
||||
acb->req.buf = buf;
|
||||
co = qemu_coroutine_create(bdrv_co_aio_ioctl_entry, acb);
|
||||
qemu_coroutine_enter(co);
|
||||
|
||||
bdrv_co_maybe_schedule_bh(acb);
|
||||
return &acb->common;
|
||||
}
|
||||
|
||||
void *qemu_blockalign(BlockDriverState *bs, size_t size)
|
||||
{
|
||||
return qemu_memalign(bdrv_opt_mem_align(bs), size);
|
||||
|
252
block/nbd.c
252
block/nbd.c
@ -32,6 +32,9 @@
|
||||
#include "qemu/uri.h"
|
||||
#include "block/block_int.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qapi-visit.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/qobject-output-visitor.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qint.h"
|
||||
@ -44,7 +47,8 @@ typedef struct BDRVNBDState {
|
||||
NbdClientSession client;
|
||||
|
||||
/* For nbd_refresh_filename() */
|
||||
char *path, *host, *port, *export, *tlscredsid;
|
||||
SocketAddress *saddr;
|
||||
char *export, *tlscredsid;
|
||||
} BDRVNBDState;
|
||||
|
||||
static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
@ -90,9 +94,13 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
qdict_put(options, "path", qstring_from_str(qp->p[0].value));
|
||||
qdict_put(options, "server.type", qstring_from_str("unix"));
|
||||
qdict_put(options, "server.data.path",
|
||||
qstring_from_str(qp->p[0].value));
|
||||
} else {
|
||||
QString *host;
|
||||
char *port_str;
|
||||
|
||||
/* nbd[+tcp]://host[:port]/export */
|
||||
if (!uri->server) {
|
||||
ret = -EINVAL;
|
||||
@ -107,12 +115,12 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
||||
host = qstring_from_str(uri->server);
|
||||
}
|
||||
|
||||
qdict_put(options, "host", host);
|
||||
if (uri->port) {
|
||||
char* port_str = g_strdup_printf("%d", uri->port);
|
||||
qdict_put(options, "port", qstring_from_str(port_str));
|
||||
g_free(port_str);
|
||||
}
|
||||
qdict_put(options, "server.type", qstring_from_str("inet"));
|
||||
qdict_put(options, "server.data.host", host);
|
||||
|
||||
port_str = g_strdup_printf("%d", uri->port ?: NBD_DEFAULT_PORT);
|
||||
qdict_put(options, "server.data.port", qstring_from_str(port_str));
|
||||
g_free(port_str);
|
||||
}
|
||||
|
||||
out:
|
||||
@ -123,6 +131,26 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool nbd_has_filename_options_conflict(QDict *options, Error **errp)
|
||||
{
|
||||
const QDictEntry *e;
|
||||
|
||||
for (e = qdict_first(options); e; e = qdict_next(options, e)) {
|
||||
if (!strcmp(e->key, "host") ||
|
||||
!strcmp(e->key, "port") ||
|
||||
!strcmp(e->key, "path") ||
|
||||
!strcmp(e->key, "export") ||
|
||||
strstart(e->key, "server.", NULL))
|
||||
{
|
||||
error_setg(errp, "Option '%s' cannot be used with a file name",
|
||||
e->key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
@ -131,12 +159,7 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
const char *host_spec;
|
||||
const char *unixpath;
|
||||
|
||||
if (qdict_haskey(options, "host")
|
||||
|| qdict_haskey(options, "port")
|
||||
|| qdict_haskey(options, "path"))
|
||||
{
|
||||
error_setg(errp, "host/port/path and a file name may not be specified "
|
||||
"at the same time");
|
||||
if (nbd_has_filename_options_conflict(options, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,7 +196,8 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
|
||||
/* are we a UNIX or TCP socket? */
|
||||
if (strstart(host_spec, "unix:", &unixpath)) {
|
||||
qdict_put(options, "path", qstring_from_str(unixpath));
|
||||
qdict_put(options, "server.type", qstring_from_str("unix"));
|
||||
qdict_put(options, "server.data.path", qstring_from_str(unixpath));
|
||||
} else {
|
||||
InetSocketAddress *addr = NULL;
|
||||
|
||||
@ -182,8 +206,9 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
||||
goto out;
|
||||
}
|
||||
|
||||
qdict_put(options, "host", qstring_from_str(addr->host));
|
||||
qdict_put(options, "port", qstring_from_str(addr->port));
|
||||
qdict_put(options, "server.type", qstring_from_str("inet"));
|
||||
qdict_put(options, "server.data.host", qstring_from_str(addr->host));
|
||||
qdict_put(options, "server.data.port", qstring_from_str(addr->port));
|
||||
qapi_free_InetSocketAddress(addr);
|
||||
}
|
||||
|
||||
@ -191,47 +216,81 @@ out:
|
||||
g_free(file);
|
||||
}
|
||||
|
||||
static SocketAddress *nbd_config(BDRVNBDState *s, QemuOpts *opts, Error **errp)
|
||||
static bool nbd_process_legacy_socket_options(QDict *output_options,
|
||||
QemuOpts *legacy_opts,
|
||||
Error **errp)
|
||||
{
|
||||
SocketAddress *saddr;
|
||||
const char *path = qemu_opt_get(legacy_opts, "path");
|
||||
const char *host = qemu_opt_get(legacy_opts, "host");
|
||||
const char *port = qemu_opt_get(legacy_opts, "port");
|
||||
const QDictEntry *e;
|
||||
|
||||
s->path = g_strdup(qemu_opt_get(opts, "path"));
|
||||
s->host = g_strdup(qemu_opt_get(opts, "host"));
|
||||
|
||||
if (!s->path == !s->host) {
|
||||
if (s->path) {
|
||||
error_setg(errp, "path and host may not be used at the same time.");
|
||||
} else {
|
||||
error_setg(errp, "one of path and host must be specified.");
|
||||
}
|
||||
return NULL;
|
||||
if (!path && !host && !port) {
|
||||
return true;
|
||||
}
|
||||
|
||||
saddr = g_new0(SocketAddress, 1);
|
||||
|
||||
if (s->path) {
|
||||
UnixSocketAddress *q_unix;
|
||||
saddr->type = SOCKET_ADDRESS_KIND_UNIX;
|
||||
q_unix = saddr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
|
||||
q_unix->path = g_strdup(s->path);
|
||||
} else {
|
||||
InetSocketAddress *inet;
|
||||
|
||||
s->port = g_strdup(qemu_opt_get(opts, "port"));
|
||||
|
||||
saddr->type = SOCKET_ADDRESS_KIND_INET;
|
||||
inet = saddr->u.inet.data = g_new0(InetSocketAddress, 1);
|
||||
inet->host = g_strdup(s->host);
|
||||
inet->port = g_strdup(s->port);
|
||||
if (!inet->port) {
|
||||
inet->port = g_strdup_printf("%d", NBD_DEFAULT_PORT);
|
||||
for (e = qdict_first(output_options); e; e = qdict_next(output_options, e))
|
||||
{
|
||||
if (strstart(e->key, "server.", NULL)) {
|
||||
error_setg(errp, "Cannot use 'server' and path/host/port at the "
|
||||
"same time");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (path && host) {
|
||||
error_setg(errp, "path and host may not be used at the same time");
|
||||
return false;
|
||||
} else if (path) {
|
||||
if (port) {
|
||||
error_setg(errp, "port may not be used without host");
|
||||
return false;
|
||||
}
|
||||
|
||||
qdict_put(output_options, "server.type", qstring_from_str("unix"));
|
||||
qdict_put(output_options, "server.data.path", qstring_from_str(path));
|
||||
} else if (host) {
|
||||
qdict_put(output_options, "server.type", qstring_from_str("inet"));
|
||||
qdict_put(output_options, "server.data.host", qstring_from_str(host));
|
||||
qdict_put(output_options, "server.data.port",
|
||||
qstring_from_str(port ?: stringify(NBD_DEFAULT_PORT)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static SocketAddress *nbd_config(BDRVNBDState *s, QDict *options, Error **errp)
|
||||
{
|
||||
SocketAddress *saddr = NULL;
|
||||
QDict *addr = NULL;
|
||||
QObject *crumpled_addr = NULL;
|
||||
Visitor *iv = NULL;
|
||||
Error *local_err = NULL;
|
||||
|
||||
qdict_extract_subqdict(options, &addr, "server.");
|
||||
if (!qdict_size(addr)) {
|
||||
error_setg(errp, "NBD server address missing");
|
||||
goto done;
|
||||
}
|
||||
|
||||
crumpled_addr = qdict_crumple(addr, errp);
|
||||
if (!crumpled_addr) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
iv = qobject_input_visitor_new(crumpled_addr, true);
|
||||
visit_type_SocketAddress(iv, NULL, &saddr, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
goto done;
|
||||
}
|
||||
|
||||
s->client.is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
|
||||
|
||||
s->export = g_strdup(qemu_opt_get(opts, "export"));
|
||||
|
||||
done:
|
||||
QDECREF(addr);
|
||||
qobject_decref(crumpled_addr);
|
||||
visit_free(iv);
|
||||
return saddr;
|
||||
}
|
||||
|
||||
@ -332,7 +391,6 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
QemuOpts *opts = NULL;
|
||||
Error *local_err = NULL;
|
||||
QIOChannelSocket *sioc = NULL;
|
||||
SocketAddress *saddr = NULL;
|
||||
QCryptoTLSCreds *tlscreds = NULL;
|
||||
const char *hostname = NULL;
|
||||
int ret = -EINVAL;
|
||||
@ -344,12 +402,19 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Pop the config into our state object. Exit if invalid. */
|
||||
saddr = nbd_config(s, opts, errp);
|
||||
if (!saddr) {
|
||||
/* Translate @host, @port, and @path to a SocketAddress */
|
||||
if (!nbd_process_legacy_socket_options(options, opts, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Pop the config into our state object. Exit if invalid. */
|
||||
s->saddr = nbd_config(s, options, errp);
|
||||
if (!s->saddr) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->export = g_strdup(qemu_opt_get(opts, "export"));
|
||||
|
||||
s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds"));
|
||||
if (s->tlscredsid) {
|
||||
tlscreds = nbd_get_tls_creds(s->tlscredsid, errp);
|
||||
@ -357,17 +422,17 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (saddr->type != SOCKET_ADDRESS_KIND_INET) {
|
||||
if (s->saddr->type != SOCKET_ADDRESS_KIND_INET) {
|
||||
error_setg(errp, "TLS only supported over IP sockets");
|
||||
goto error;
|
||||
}
|
||||
hostname = saddr->u.inet.data->host;
|
||||
hostname = s->saddr->u.inet.data->host;
|
||||
}
|
||||
|
||||
/* establish TCP connection, return error if it fails
|
||||
* TODO: Configurable retry-until-timeout behaviour.
|
||||
*/
|
||||
sioc = nbd_establish_connection(saddr, errp);
|
||||
sioc = nbd_establish_connection(s->saddr, errp);
|
||||
if (!sioc) {
|
||||
ret = -ECONNREFUSED;
|
||||
goto error;
|
||||
@ -384,13 +449,10 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
object_unref(OBJECT(tlscreds));
|
||||
}
|
||||
if (ret < 0) {
|
||||
g_free(s->path);
|
||||
g_free(s->host);
|
||||
g_free(s->port);
|
||||
qapi_free_SocketAddress(s->saddr);
|
||||
g_free(s->export);
|
||||
g_free(s->tlscredsid);
|
||||
}
|
||||
qapi_free_SocketAddress(saddr);
|
||||
qemu_opts_del(opts);
|
||||
return ret;
|
||||
}
|
||||
@ -412,9 +474,7 @@ static void nbd_close(BlockDriverState *bs)
|
||||
|
||||
nbd_client_close(bs);
|
||||
|
||||
g_free(s->path);
|
||||
g_free(s->host);
|
||||
g_free(s->port);
|
||||
qapi_free_SocketAddress(s->saddr);
|
||||
g_free(s->export);
|
||||
g_free(s->tlscredsid);
|
||||
}
|
||||
@ -441,45 +501,51 @@ static void nbd_refresh_filename(BlockDriverState *bs, QDict *options)
|
||||
{
|
||||
BDRVNBDState *s = bs->opaque;
|
||||
QDict *opts = qdict_new();
|
||||
QObject *saddr_qdict;
|
||||
Visitor *ov;
|
||||
const char *host = NULL, *port = NULL, *path = NULL;
|
||||
|
||||
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("nbd")));
|
||||
|
||||
if (s->path && s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd+unix:///%s?socket=%s", s->export, s->path);
|
||||
} else if (s->path && !s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd+unix://?socket=%s", s->path);
|
||||
} else if (!s->path && s->export && s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s:%s/%s", s->host, s->port, s->export);
|
||||
} else if (!s->path && s->export && !s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s/%s", s->host, s->export);
|
||||
} else if (!s->path && !s->export && s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s:%s", s->host, s->port);
|
||||
} else if (!s->path && !s->export && !s->port) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s", s->host);
|
||||
if (s->saddr->type == SOCKET_ADDRESS_KIND_INET) {
|
||||
const InetSocketAddress *inet = s->saddr->u.inet.data;
|
||||
if (!inet->has_ipv4 && !inet->has_ipv6 && !inet->has_to) {
|
||||
host = inet->host;
|
||||
port = inet->port;
|
||||
}
|
||||
} else if (s->saddr->type == SOCKET_ADDRESS_KIND_UNIX) {
|
||||
path = s->saddr->u.q_unix.data->path;
|
||||
}
|
||||
|
||||
if (s->path) {
|
||||
qdict_put_obj(opts, "path", QOBJECT(qstring_from_str(s->path)));
|
||||
} else if (s->port) {
|
||||
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(s->host)));
|
||||
qdict_put_obj(opts, "port", QOBJECT(qstring_from_str(s->port)));
|
||||
} else {
|
||||
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(s->host)));
|
||||
qdict_put(opts, "driver", qstring_from_str("nbd"));
|
||||
|
||||
if (path && s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd+unix:///%s?socket=%s", s->export, path);
|
||||
} else if (path && !s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd+unix://?socket=%s", path);
|
||||
} else if (host && s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s:%s/%s", host, port, s->export);
|
||||
} else if (host && !s->export) {
|
||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
"nbd://%s:%s", host, port);
|
||||
}
|
||||
|
||||
ov = qobject_output_visitor_new(&saddr_qdict);
|
||||
visit_type_SocketAddress(ov, NULL, &s->saddr, &error_abort);
|
||||
visit_complete(ov, &saddr_qdict);
|
||||
assert(qobject_type(saddr_qdict) == QTYPE_QDICT);
|
||||
|
||||
qdict_put_obj(opts, "server", saddr_qdict);
|
||||
|
||||
if (s->export) {
|
||||
qdict_put_obj(opts, "export", QOBJECT(qstring_from_str(s->export)));
|
||||
qdict_put(opts, "export", qstring_from_str(s->export));
|
||||
}
|
||||
if (s->tlscredsid) {
|
||||
qdict_put_obj(opts, "tls-creds",
|
||||
QOBJECT(qstring_from_str(s->tlscredsid)));
|
||||
qdict_put(opts, "tls-creds", qstring_from_str(s->tlscredsid));
|
||||
}
|
||||
|
||||
qdict_flatten(opts);
|
||||
bs->full_open_options = opts;
|
||||
}
|
||||
|
||||
|
@ -2069,13 +2069,23 @@ static bool hdev_is_sg(BlockDriverState *bs)
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
BDRVRawState *s = bs->opaque;
|
||||
struct stat st;
|
||||
struct sg_scsi_id scsiid;
|
||||
int sg_version;
|
||||
int ret;
|
||||
|
||||
if (stat(bs->filename, &st) >= 0 && S_ISCHR(st.st_mode) &&
|
||||
!bdrv_ioctl(bs, SG_GET_VERSION_NUM, &sg_version) &&
|
||||
!bdrv_ioctl(bs, SG_GET_SCSI_ID, &scsiid)) {
|
||||
if (stat(bs->filename, &st) < 0 || !S_ISCHR(st.st_mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = ioctl(s->fd, SG_GET_VERSION_NUM, &sg_version);
|
||||
if (ret < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return true;
|
||||
|
@ -176,12 +176,9 @@ static void raw_lock_medium(BlockDriverState *bs, bool locked)
|
||||
bdrv_lock_medium(bs->file->bs, locked);
|
||||
}
|
||||
|
||||
static BlockAIOCB *raw_aio_ioctl(BlockDriverState *bs,
|
||||
unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb,
|
||||
void *opaque)
|
||||
static int raw_co_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
|
||||
{
|
||||
return bdrv_aio_ioctl(bs->file->bs, req, buf, cb, opaque);
|
||||
return bdrv_co_ioctl(bs->file->bs, req, buf);
|
||||
}
|
||||
|
||||
static int raw_has_zero_init(BlockDriverState *bs)
|
||||
@ -261,7 +258,7 @@ BlockDriver bdrv_raw = {
|
||||
.bdrv_media_changed = &raw_media_changed,
|
||||
.bdrv_eject = &raw_eject,
|
||||
.bdrv_lock_medium = &raw_lock_medium,
|
||||
.bdrv_aio_ioctl = &raw_aio_ioctl,
|
||||
.bdrv_co_ioctl = &raw_co_ioctl,
|
||||
.create_opts = &raw_create_opts,
|
||||
.bdrv_has_zero_init = &raw_has_zero_init
|
||||
};
|
||||
|
@ -9,7 +9,6 @@ blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags
|
||||
blk_co_pwritev(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags %x"
|
||||
|
||||
# block/io.c
|
||||
bdrv_aio_pdiscard(void *bs, int64_t offset, int count, void *opaque) "bs %p offset %"PRId64" count %d opaque %p"
|
||||
bdrv_aio_flush(void *bs, void *opaque) "bs %p opaque %p"
|
||||
bdrv_aio_readv(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %p sector_num %"PRId64" nb_sectors %d opaque %p"
|
||||
bdrv_aio_writev(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %p sector_num %"PRId64" nb_sectors %d opaque %p"
|
||||
|
@ -314,17 +314,11 @@ BlockAIOCB *bdrv_aio_writev(BdrvChild *child, int64_t sector_num,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
BlockAIOCB *bdrv_aio_flush(BlockDriverState *bs,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
BlockAIOCB *bdrv_aio_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int count,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
void bdrv_aio_cancel(BlockAIOCB *acb);
|
||||
void bdrv_aio_cancel_async(BlockAIOCB *acb);
|
||||
|
||||
/* sg packet commands */
|
||||
int bdrv_ioctl(BlockDriverState *bs, unsigned long int req, void *buf);
|
||||
BlockAIOCB *bdrv_aio_ioctl(BlockDriverState *bs,
|
||||
unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int bdrv_co_ioctl(BlockDriverState *bs, int req, void *buf);
|
||||
|
||||
/* Invalidate any cached metadata used by image formats */
|
||||
void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp);
|
||||
|
@ -244,6 +244,8 @@ struct BlockDriver {
|
||||
BlockAIOCB *(*bdrv_aio_ioctl)(BlockDriverState *bs,
|
||||
unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
int coroutine_fn (*bdrv_co_ioctl)(BlockDriverState *bs,
|
||||
unsigned long int req, void *buf);
|
||||
|
||||
/* List of options for creating images, terminated by name == NULL */
|
||||
QemuOptsList *create_opts;
|
||||
|
@ -146,6 +146,7 @@ BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk, int64_t offset, int count,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
void blk_aio_cancel(BlockAIOCB *acb);
|
||||
void blk_aio_cancel_async(BlockAIOCB *acb);
|
||||
int blk_co_ioctl(BlockBackend *blk, unsigned long int req, void *buf);
|
||||
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf);
|
||||
BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
|
||||
BlockCompletionFunc *cb, void *opaque);
|
||||
|
@ -1703,15 +1703,16 @@
|
||||
#
|
||||
# @host_device, @host_cdrom: Since 2.1
|
||||
# @gluster: Since 2.7
|
||||
# @nbd: Since 2.8
|
||||
#
|
||||
# Since: 2.0
|
||||
##
|
||||
{ 'enum': 'BlockdevDriver',
|
||||
'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop',
|
||||
'dmg', 'file', 'ftp', 'ftps', 'gluster', 'host_cdrom',
|
||||
'host_device', 'http', 'https', 'luks', 'null-aio', 'null-co',
|
||||
'parallels', 'qcow', 'qcow2', 'qed', 'quorum', 'raw',
|
||||
'replication', 'tftp', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
|
||||
'host_device', 'http', 'https', 'luks', 'nbd', 'null-aio',
|
||||
'null-co', 'parallels', 'qcow', 'qcow2', 'qed', 'quorum', 'raw',
|
||||
'replication', 'tftp', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
|
||||
|
||||
##
|
||||
# @BlockdevOptionsFile
|
||||
@ -2219,6 +2220,24 @@
|
||||
{ 'struct': 'BlockdevOptionsCurl',
|
||||
'data': { 'filename': 'str' } }
|
||||
|
||||
##
|
||||
# @BlockdevOptionsNbd
|
||||
#
|
||||
# Driver specific block device options for NBD.
|
||||
#
|
||||
# @server: NBD server address
|
||||
#
|
||||
# @export: #optional export name
|
||||
#
|
||||
# @tls-creds: #optional TLS credentials ID
|
||||
#
|
||||
# Since: 2.8
|
||||
##
|
||||
{ 'struct': 'BlockdevOptionsNbd',
|
||||
'data': { 'server': 'SocketAddress',
|
||||
'*export': 'str',
|
||||
'*tls-creds': 'str' } }
|
||||
|
||||
##
|
||||
# @BlockdevOptions
|
||||
#
|
||||
@ -2264,7 +2283,7 @@
|
||||
'https': 'BlockdevOptionsCurl',
|
||||
# TODO iscsi: Wait for structured options
|
||||
'luks': 'BlockdevOptionsLUKS',
|
||||
# TODO nbd: Should take InetSocketAddress for 'host'?
|
||||
'nbd': 'BlockdevOptionsNbd',
|
||||
# TODO nfs: Wait for structured options
|
||||
'null-aio': 'BlockdevOptionsNull',
|
||||
'null-co': 'BlockdevOptionsNull',
|
||||
|
@ -222,7 +222,7 @@ Testing: -drive driver=file
|
||||
QEMU_PROG: -drive driver=file: The 'file' block driver requires a file name
|
||||
|
||||
Testing: -drive driver=nbd
|
||||
QEMU_PROG: -drive driver=nbd: one of path and host must be specified.
|
||||
QEMU_PROG: -drive driver=nbd: NBD server address missing
|
||||
|
||||
Testing: -drive driver=raw
|
||||
QEMU_PROG: -drive driver=raw: Can't use 'raw' as a block driver for the protocol level
|
||||
@ -231,7 +231,7 @@ Testing: -drive file.driver=file
|
||||
QEMU_PROG: -drive file.driver=file: The 'file' block driver requires a file name
|
||||
|
||||
Testing: -drive file.driver=nbd
|
||||
QEMU_PROG: -drive file.driver=nbd: one of path and host must be specified.
|
||||
QEMU_PROG: -drive file.driver=nbd: NBD server address missing
|
||||
|
||||
Testing: -drive file.driver=raw
|
||||
QEMU_PROG: -drive file.driver=raw: Can't use 'raw' as a block driver for the protocol level
|
||||
|
@ -316,7 +316,7 @@ Testing: -drive driver=file
|
||||
QEMU_PROG: -drive driver=file: The 'file' block driver requires a file name
|
||||
|
||||
Testing: -drive driver=nbd
|
||||
QEMU_PROG: -drive driver=nbd: one of path and host must be specified.
|
||||
QEMU_PROG: -drive driver=nbd: NBD server address missing
|
||||
|
||||
Testing: -drive driver=raw
|
||||
QEMU_PROG: -drive driver=raw: Can't use 'raw' as a block driver for the protocol level
|
||||
@ -325,7 +325,7 @@ Testing: -drive file.driver=file
|
||||
QEMU_PROG: -drive file.driver=file: The 'file' block driver requires a file name
|
||||
|
||||
Testing: -drive file.driver=nbd
|
||||
QEMU_PROG: -drive file.driver=nbd: one of path and host must be specified.
|
||||
QEMU_PROG: -drive file.driver=nbd: NBD server address missing
|
||||
|
||||
Testing: -drive file.driver=raw
|
||||
QEMU_PROG: -drive file.driver=raw: Can't use 'raw' as a block driver for the protocol level
|
||||
|
195
tests/qemu-iotests/147
Executable file
195
tests/qemu-iotests/147
Executable file
@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Test case for NBD's blockdev-add interface
|
||||
#
|
||||
# Copyright (C) 2016 Red Hat, Inc.
|
||||
#
|
||||
# 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 socket
|
||||
import stat
|
||||
import time
|
||||
import iotests
|
||||
from iotests import cachemode, imgfmt, qemu_img, qemu_nbd
|
||||
|
||||
NBD_PORT = 10811
|
||||
|
||||
test_img = os.path.join(iotests.test_dir, 'test.img')
|
||||
unix_socket = os.path.join(iotests.test_dir, 'nbd.socket')
|
||||
|
||||
class NBDBlockdevAddBase(iotests.QMPTestCase):
|
||||
def blockdev_add_options(self, address, export=None):
|
||||
options = { 'node-name': 'nbd-blockdev',
|
||||
'driver': 'raw',
|
||||
'file': {
|
||||
'driver': 'nbd',
|
||||
'server': address
|
||||
} }
|
||||
if export is not None:
|
||||
options['file']['export'] = export
|
||||
return options
|
||||
|
||||
def client_test(self, filename, address, export=None):
|
||||
bao = self.blockdev_add_options(address, export)
|
||||
result = self.vm.qmp('blockdev-add', **bao)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
result = self.vm.qmp('query-named-block-nodes')
|
||||
for node in result['return']:
|
||||
if node['node-name'] == 'nbd-blockdev':
|
||||
if isinstance(filename, str):
|
||||
self.assert_qmp(node, 'image/filename', filename)
|
||||
else:
|
||||
self.assert_json_filename_equal(node['image']['filename'],
|
||||
filename)
|
||||
break
|
||||
|
||||
result = self.vm.qmp('x-blockdev-del', node_name='nbd-blockdev')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
|
||||
class QemuNBD(NBDBlockdevAddBase):
|
||||
def setUp(self):
|
||||
qemu_img('create', '-f', iotests.imgfmt, test_img, '64k')
|
||||
self.vm = iotests.VM()
|
||||
self.vm.launch()
|
||||
|
||||
def tearDown(self):
|
||||
self.vm.shutdown()
|
||||
os.remove(test_img)
|
||||
try:
|
||||
os.remove(unix_socket)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _server_up(self, *args):
|
||||
self.assertEqual(qemu_nbd('-f', imgfmt, test_img, *args), 0)
|
||||
|
||||
def test_inet(self):
|
||||
self._server_up('-p', str(NBD_PORT))
|
||||
address = { 'type': 'inet',
|
||||
'data': {
|
||||
'host': 'localhost',
|
||||
'port': str(NBD_PORT)
|
||||
} }
|
||||
self.client_test('nbd://localhost:%i' % NBD_PORT, address)
|
||||
|
||||
def test_unix(self):
|
||||
self._server_up('-k', unix_socket)
|
||||
address = { 'type': 'unix',
|
||||
'data': { 'path': unix_socket } }
|
||||
self.client_test('nbd+unix://?socket=' + unix_socket, address)
|
||||
|
||||
|
||||
class BuiltinNBD(NBDBlockdevAddBase):
|
||||
def setUp(self):
|
||||
qemu_img('create', '-f', iotests.imgfmt, test_img, '64k')
|
||||
self.vm = iotests.VM()
|
||||
self.vm.launch()
|
||||
self.server = iotests.VM('.server')
|
||||
self.server.add_drive_raw('if=none,id=nbd-export,' +
|
||||
'file=%s,' % test_img +
|
||||
'format=%s,' % imgfmt +
|
||||
'cache=%s' % cachemode)
|
||||
self.server.launch()
|
||||
|
||||
def tearDown(self):
|
||||
self.vm.shutdown()
|
||||
self.server.shutdown()
|
||||
os.remove(test_img)
|
||||
try:
|
||||
os.remove(unix_socket)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _server_up(self, address):
|
||||
result = self.server.qmp('nbd-server-start', addr=address)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
result = self.server.qmp('nbd-server-add', device='nbd-export')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
def _server_down(self):
|
||||
result = self.server.qmp('nbd-server-stop')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
def test_inet(self):
|
||||
address = { 'type': 'inet',
|
||||
'data': {
|
||||
'host': 'localhost',
|
||||
'port': str(NBD_PORT)
|
||||
} }
|
||||
self._server_up(address)
|
||||
self.client_test('nbd://localhost:%i/nbd-export' % NBD_PORT,
|
||||
address, 'nbd-export')
|
||||
self._server_down()
|
||||
|
||||
def test_inet6(self):
|
||||
address = { 'type': 'inet',
|
||||
'data': {
|
||||
'host': '::1',
|
||||
'port': str(NBD_PORT),
|
||||
'ipv4': False,
|
||||
'ipv6': True
|
||||
} }
|
||||
filename = { 'driver': 'raw',
|
||||
'file': {
|
||||
'driver': 'nbd',
|
||||
'export': 'nbd-export',
|
||||
'server': address
|
||||
} }
|
||||
self._server_up(address)
|
||||
self.client_test(filename, address, 'nbd-export')
|
||||
self._server_down()
|
||||
|
||||
def test_unix(self):
|
||||
address = { 'type': 'unix',
|
||||
'data': { 'path': unix_socket } }
|
||||
self._server_up(address)
|
||||
self.client_test('nbd+unix:///nbd-export?socket=' + unix_socket,
|
||||
address, 'nbd-export')
|
||||
self._server_down()
|
||||
|
||||
def test_fd(self):
|
||||
self._server_up({ 'type': 'unix',
|
||||
'data': { 'path': unix_socket } })
|
||||
|
||||
sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sockfd.connect(unix_socket)
|
||||
|
||||
result = self.vm.send_fd_scm(str(sockfd.fileno()))
|
||||
self.assertEqual(result, 0, 'Failed to send socket FD')
|
||||
|
||||
result = self.vm.qmp('getfd', fdname='nbd-fifo')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
address = { 'type': 'fd',
|
||||
'data': { 'str': 'nbd-fifo' } }
|
||||
filename = { 'driver': 'raw',
|
||||
'file': {
|
||||
'driver': 'nbd',
|
||||
'export': 'nbd-export',
|
||||
'server': address
|
||||
} }
|
||||
self.client_test(filename, address, 'nbd-export')
|
||||
|
||||
self._server_down()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Need to support image creation
|
||||
iotests.main(supported_fmts=['vpc', 'parallels', 'qcow', 'vdi', 'qcow2',
|
||||
'vmdk', 'raw', 'vhdx', 'qed'])
|
5
tests/qemu-iotests/147.out
Normal file
5
tests/qemu-iotests/147.out
Normal file
@ -0,0 +1,5 @@
|
||||
......
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests
|
||||
|
||||
OK
|
@ -69,7 +69,7 @@ if [ "$IMGOPTSSYNTAX" = "true" ]; then
|
||||
TEST_IMG="$DRIVER,file.driver=ssh,file.host=127.0.0.1,file.path=$TEST_IMG_FILE"
|
||||
elif [ "$IMGPROTO" = "nfs" ]; then
|
||||
TEST_DIR="$DRIVER,file.driver=nfs,file.filename=nfs://127.0.0.1/$TEST_DIR"
|
||||
TEST_IMG=$TEST_DIR_OPTS/t.$IMGFMT
|
||||
TEST_IMG=$TEST_DIR/t.$IMGFMT
|
||||
elif [ "$IMGPROTO" = "archipelago" ]; then
|
||||
TEST_IMG="$DRIVER,file.driver=archipelago,file.volume=:at.$IMGFMT"
|
||||
else
|
||||
|
@ -149,6 +149,7 @@
|
||||
144 rw auto quick
|
||||
145 auto quick
|
||||
146 auto quick
|
||||
147 auto
|
||||
148 rw auto quick
|
||||
149 rw auto sudo
|
||||
150 rw auto quick
|
||||
|
@ -39,6 +39,10 @@ qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
|
||||
if os.environ.get('QEMU_IO_OPTIONS'):
|
||||
qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
|
||||
|
||||
qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
|
||||
if os.environ.get('QEMU_NBD_OPTIONS'):
|
||||
qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
|
||||
|
||||
qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
|
||||
qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
|
||||
|
||||
@ -87,6 +91,10 @@ def qemu_io(*args):
|
||||
sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
|
||||
return subp.communicate()[0]
|
||||
|
||||
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 compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
|
||||
'''Return True if two image files are identical'''
|
||||
return qemu_img('compare', '-f', fmt1,
|
||||
@ -132,8 +140,10 @@ def log(msg, filters=[]):
|
||||
class VM(qtest.QEMUQtestMachine):
|
||||
'''A QEMU VM'''
|
||||
|
||||
def __init__(self):
|
||||
super(VM, self).__init__(qemu_prog, qemu_opts, test_dir=test_dir,
|
||||
def __init__(self, path_suffix=''):
|
||||
name = "qemu%s-%d" % (path_suffix, os.getpid())
|
||||
super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
|
||||
test_dir=test_dir,
|
||||
socket_scm_helper=socket_scm_helper)
|
||||
if debug:
|
||||
self._debug = True
|
||||
@ -212,6 +222,19 @@ class QMPTestCase(unittest.TestCase):
|
||||
self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
|
||||
return d
|
||||
|
||||
def flatten_qmp_object(self, obj, output=None, basestr=''):
|
||||
if output is None:
|
||||
output = dict()
|
||||
if isinstance(obj, list):
|
||||
for i in range(len(obj)):
|
||||
self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
|
||||
elif isinstance(obj, dict):
|
||||
for key in obj:
|
||||
self.flatten_qmp_object(obj[key], output, basestr + key + '.')
|
||||
else:
|
||||
output[basestr[:-1]] = obj # Strip trailing '.'
|
||||
return output
|
||||
|
||||
def assert_qmp_absent(self, d, path):
|
||||
try:
|
||||
result = self.dictpath(d, path)
|
||||
@ -242,6 +265,13 @@ class QMPTestCase(unittest.TestCase):
|
||||
self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
|
||||
(node_name, file_name, result))
|
||||
|
||||
def assert_json_filename_equal(self, json_filename, reference):
|
||||
'''Asserts that the given filename is a json: filename and that its
|
||||
content is equal to the given reference object'''
|
||||
self.assertEqual(json_filename[:5], 'json:')
|
||||
self.assertEqual(self.flatten_qmp_object(json.loads(json_filename[5:])),
|
||||
self.flatten_qmp_object(reference))
|
||||
|
||||
def cancel_and_wait(self, drive='drive0', force=False, resume=False):
|
||||
'''Cancel a block job and wait for it to finish, returning the event'''
|
||||
result = self.vm.qmp('block-job-cancel', device=drive, force=force)
|
||||
|
@ -60,7 +60,7 @@ static int send_fd(int fd, int fd_to_send)
|
||||
}
|
||||
|
||||
/* Convert string to fd number. */
|
||||
static int get_fd_num(const char *fd_str)
|
||||
static int get_fd_num(const char *fd_str, bool silent)
|
||||
{
|
||||
int sock;
|
||||
char *err;
|
||||
@ -68,12 +68,16 @@ static int get_fd_num(const char *fd_str)
|
||||
errno = 0;
|
||||
sock = strtol(fd_str, &err, 10);
|
||||
if (errno) {
|
||||
fprintf(stderr, "Failed in strtol for socket fd, reason: %s\n",
|
||||
strerror(errno));
|
||||
if (!silent) {
|
||||
fprintf(stderr, "Failed in strtol for socket fd, reason: %s\n",
|
||||
strerror(errno));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (!*fd_str || *err || sock < 0) {
|
||||
fprintf(stderr, "bad numerical value for socket fd '%s'\n", fd_str);
|
||||
if (!silent) {
|
||||
fprintf(stderr, "bad numerical value for socket fd '%s'\n", fd_str);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -104,18 +108,21 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
|
||||
|
||||
sock = get_fd_num(argv[1]);
|
||||
sock = get_fd_num(argv[1], false);
|
||||
if (sock < 0) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Now only open a file in readonly mode for test purpose. If more precise
|
||||
control is needed, use python script in file operation, which is
|
||||
supposed to fork and exec this program. */
|
||||
fd = open(argv[2], O_RDONLY);
|
||||
fd = get_fd_num(argv[2], true);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Failed to open file '%s'\n", argv[2]);
|
||||
return EXIT_FAILURE;
|
||||
/* Now only open a file in readonly mode for test purpose. If more
|
||||
precise control is needed, use python script in file operation, which
|
||||
is supposed to fork and exec this program. */
|
||||
fd = open(argv[2], O_RDONLY);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Failed to open file '%s'\n", argv[2]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
ret = send_fd(sock, fd);
|
||||
|
Loading…
Reference in New Issue
Block a user