Skip to content

Commit

Permalink
block: add 'null' block job
Browse files Browse the repository at this point in the history
The null block job is meant to be a simple block job, useful as a
tutorial.

The job reads through all sectors in the disk, and obeys the 'speed'
parameter for block jobs.  The data return from the reads is ignored.
  • Loading branch information
codyprime committed Aug 20, 2015
1 parent 074a992 commit 013b0f9
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 1 deletion.
1 change: 1 addition & 0 deletions block/Makefile.objs
Expand Up @@ -26,6 +26,7 @@ block-obj-y += write-threshold.o
common-obj-y += stream.o
common-obj-y += commit.o
common-obj-y += backup.o
common-obj-y += job-null.o

iscsi.o-cflags := $(LIBISCSI_CFLAGS)
iscsi.o-libs := $(LIBISCSI_LIBS)
Expand Down
136 changes: 136 additions & 0 deletions block/job-null.c
@@ -0,0 +1,136 @@
/*
* Null Block Job Driver
*
* Copyright Red Hat, Inc. 2015
*
* Authors:
* Jeff Cody <jcody@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/

#include "trace.h"
#include "block/block_int.h"
#include "block/blockjob.h"
#include "qapi/qmp/qerror.h"
#include "qemu/ratelimit.h"

#define NULL_BUFFER_SIZE (512 * 1024) /* in bytes */

#define SLICE_TIME 100000000ULL /* ns */

typedef struct NullBlockJob {
BlockJob common;
RateLimit limit;
} NullBlockJob;


typedef struct {
int ret;
} NullCompleteData;

static void null_complete(BlockJob *job, void *opaque)
{
NullBlockJob *s = container_of(job, NullBlockJob, common);
NullCompleteData *data = opaque;
int ret = data->ret;

if (!block_job_is_cancelled(&s->common) && ret == 0) {
/* success */
}

/* do whatever final steps needed */

block_job_completed(&s->common, ret);
g_free(data);
}

static void coroutine_fn null_run(void *opaque)
{
NullBlockJob *s = opaque;
NullCompleteData *data;
int64_t sector_num, end;
int ret = 0;
int n = 1024;
void *buf = NULL;

ret = s->common.len = bdrv_getlength(s->common.bs);

if (s->common.len < 0) {
goto out;
}

end = s->common.len >> BDRV_SECTOR_BITS;
buf = qemu_blockalign(s->common.bs, NULL_BUFFER_SIZE);

for (sector_num = 0; sector_num < end; sector_num += n) {
uint64_t delay_ns = 0;

wait:
/* Note that even when no rate limit is applied we need to yield
* with no pending I/O here so that bdrv_drain_all() returns.
*/
block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
if (block_job_is_cancelled(&s->common)) {
break;
}

if (s->common.speed) {
delay_ns = ratelimit_calculate_delay(&s->limit, n);
if (delay_ns > 0) {
goto wait;
}
}

ret = bdrv_read(s->common.bs, sector_num, buf, n);
if (ret < 0) {
goto out;
}
/* Publish progress */
s->common.offset += n * BDRV_SECTOR_SIZE;
}

ret = 0;

out:
qemu_vfree(buf);

data = g_malloc(sizeof(*data));
data->ret = ret;
block_job_defer_to_main_loop(&s->common, null_complete, data);
}


static void null_set_speed(BlockJob *job, int64_t speed, Error **errp)
{
NullBlockJob *s = container_of(job, NullBlockJob, common);

if (speed < 0) {
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
return;
}
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
}

static const BlockJobDriver null_job_driver = {
.instance_size = sizeof(NullBlockJob),
.job_type = BLOCK_JOB_TYPE_NULL,
.set_speed = null_set_speed,
};

void null_start(BlockDriverState *bs, int64_t speed, BlockCompletionFunc *cb,
void *opaque, Error **errp)
{
NullBlockJob *s;

s = block_job_create(&null_job_driver, bs, speed, cb, opaque, errp);
if (!s) {
return;
}

s->common.co = qemu_coroutine_create(null_run);

qemu_coroutine_enter(s->common.co, s);
}
42 changes: 42 additions & 0 deletions blockdev.c
Expand Up @@ -2349,6 +2349,48 @@ void qmp_block_stream(const char *device,
aio_context_release(aio_context);
}

void qmp_block_null(const char *device,
bool has_speed, int64_t speed,
Error **errp)
{
BlockBackend *blk;
BlockDriverState *bs;
AioContext *aio_context;
Error *local_err = NULL;

if (!has_speed) {
speed = 0;
}

/* important note:
* libvirt relies on the devicenotfound error class in order to probe for
* live commit feature versions; for this to work, we must make sure to
* perform the device lookup before any generic errors that may occur in a
* scenario in which all optional arguments are omitted. */
blk = blk_by_name(device);
if (!blk) {
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
"device '%s' not found", device);
return;
}
bs = blk_bs(blk);

aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);

if (local_err != NULL) {
error_propagate(errp, local_err);
goto out;
}

null_start(bs, speed, block_job_cb, bs, &local_err);

out:
aio_context_release(aio_context);
}



void qmp_block_commit(const char *device,
bool has_base, const char *base,
bool has_top, const char *top,
Expand Down
2 changes: 2 additions & 0 deletions include/block/block_int.h
Expand Up @@ -544,6 +544,8 @@ void bdrv_remove_aio_context_notifier(BlockDriverState *bs,
int is_windows_drive(const char *filename);
#endif

void null_start(BlockDriverState *bs, int64_t speed, BlockCompletionFunc *cb,
void *opaque, Error **errp);
/**
* stream_start:
* @bs: Block device to operate on.
Expand Down
5 changes: 4 additions & 1 deletion qapi/block-core.json
Expand Up @@ -559,7 +559,7 @@
# Since: 1.7
##
{ 'enum': 'BlockJobType',
'data': ['commit', 'stream', 'mirror', 'backup'] }
'data': ['commit', 'stream', 'mirror', 'backup', 'null'] }

##
# @BlockJobInfo:
Expand Down Expand Up @@ -885,6 +885,9 @@
'data': { 'device': 'str', '*base': 'str', '*top': 'str',
'*backing-file': 'str', '*speed': 'int' } }

{ 'command': 'block-null',
'data': { 'device': 'str', '*speed': 'int' } }

##
# @drive-backup
#
Expand Down
5 changes: 5 additions & 0 deletions qmp-commands.hx
Expand Up @@ -1044,6 +1044,11 @@ Example:

EQMP

{
.name = "block-null",
.args_type = "device:B,speed:o?",
.mhandler.cmd_new = qmp_marshal_input_block_null,
},
{
.name = "block-commit",
.args_type = "device:B,base:s?,top:s?,backing-file:s?,speed:o?",
Expand Down

0 comments on commit 013b0f9

Please sign in to comment.