ae23c9
From f0ec16e0035297f89177beaeb55995fae4131926 Mon Sep 17 00:00:00 2001
ae23c9
From: Kevin Wolf <kwolf@redhat.com>
ae23c9
Date: Tue, 26 Jun 2018 09:48:04 +0200
ae23c9
Subject: [PATCH 096/268] job: Move state transitions to Job
ae23c9
ae23c9
RH-Author: Kevin Wolf <kwolf@redhat.com>
ae23c9
Message-id: <20180626094856.6924-22-kwolf@redhat.com>
ae23c9
Patchwork-id: 81093
ae23c9
O-Subject: [RHV-7.6 qemu-kvm-rhev PATCH v2 21/73] job: Move state transitions to Job
ae23c9
Bugzilla: 1513543
ae23c9
RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
ae23c9
RH-Acked-by: Max Reitz <mreitz@redhat.com>
ae23c9
RH-Acked-by: Fam Zheng <famz@redhat.com>
ae23c9
ae23c9
This moves BlockJob.status and the closely related functions
ae23c9
(block_)job_state_transition() and (block_)job_apply_verb to Job. The
ae23c9
two QAPI enums are renamed to JobStatus and JobVerb.
ae23c9
ae23c9
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
ae23c9
Reviewed-by: Max Reitz <mreitz@redhat.com>
ae23c9
Reviewed-by: John Snow <jsnow@redhat.com>
ae23c9
Reviewed-by: Eric Blake <eblake@redhat.com>
ae23c9
(cherry picked from commit a50c2ab858fe613fb805e53b4f6b970ab936706d)
ae23c9
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
ae23c9
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
ae23c9
---
ae23c9
 block/trace-events       |   2 -
ae23c9
 blockjob.c               | 102 +++++++++++------------------------------------
ae23c9
 include/block/blockjob.h |   3 --
ae23c9
 include/qemu/job.h       |  13 ++++++
ae23c9
 job.c                    |  56 ++++++++++++++++++++++++++
ae23c9
 qapi/block-core.json     |  16 ++++----
ae23c9
 tests/test-blockjob.c    |  39 +++++++++---------
ae23c9
 trace-events             |   4 ++
ae23c9
 8 files changed, 123 insertions(+), 112 deletions(-)
ae23c9
ae23c9
diff --git a/block/trace-events b/block/trace-events
ae23c9
index f8c50b4..93b9279 100644
ae23c9
--- a/block/trace-events
ae23c9
+++ b/block/trace-events
ae23c9
@@ -6,8 +6,6 @@ bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d"
ae23c9
 
ae23c9
 # blockjob.c
ae23c9
 block_job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d"
ae23c9
-block_job_state_transition(void *job,  int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)"
ae23c9
-block_job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)"
ae23c9
 
ae23c9
 # block/block-backend.c
ae23c9
 blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
ae23c9
diff --git a/blockjob.c b/blockjob.c
ae23c9
index c69b2e7..0fba01e 100644
ae23c9
--- a/blockjob.c
ae23c9
+++ b/blockjob.c
ae23c9
@@ -41,61 +41,6 @@
ae23c9
  * block_job_enter. */
ae23c9
 static QemuMutex block_job_mutex;
ae23c9
 
ae23c9
-/* BlockJob State Transition Table */
ae23c9
-bool BlockJobSTT[BLOCK_JOB_STATUS__MAX][BLOCK_JOB_STATUS__MAX] = {
ae23c9
-                                          /* U, C, R, P, Y, S, W, D, X, E, N */
ae23c9
-    /* U: */ [BLOCK_JOB_STATUS_UNDEFINED] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
ae23c9
-    /* C: */ [BLOCK_JOB_STATUS_CREATED]   = {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1},
ae23c9
-    /* R: */ [BLOCK_JOB_STATUS_RUNNING]   = {0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0},
ae23c9
-    /* P: */ [BLOCK_JOB_STATUS_PAUSED]    = {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
ae23c9
-    /* Y: */ [BLOCK_JOB_STATUS_READY]     = {0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0},
ae23c9
-    /* S: */ [BLOCK_JOB_STATUS_STANDBY]   = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
ae23c9
-    /* W: */ [BLOCK_JOB_STATUS_WAITING]   = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},
ae23c9
-    /* D: */ [BLOCK_JOB_STATUS_PENDING]   = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
ae23c9
-    /* X: */ [BLOCK_JOB_STATUS_ABORTING]  = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
ae23c9
-    /* E: */ [BLOCK_JOB_STATUS_CONCLUDED] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
ae23c9
-    /* N: */ [BLOCK_JOB_STATUS_NULL]      = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
ae23c9
-};
ae23c9
-
ae23c9
-bool BlockJobVerbTable[BLOCK_JOB_VERB__MAX][BLOCK_JOB_STATUS__MAX] = {
ae23c9
-                                          /* U, C, R, P, Y, S, W, D, X, E, N */
ae23c9
-    [BLOCK_JOB_VERB_CANCEL]               = {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
ae23c9
-    [BLOCK_JOB_VERB_PAUSE]                = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
ae23c9
-    [BLOCK_JOB_VERB_RESUME]               = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
ae23c9
-    [BLOCK_JOB_VERB_SET_SPEED]            = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
ae23c9
-    [BLOCK_JOB_VERB_COMPLETE]             = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
ae23c9
-    [BLOCK_JOB_VERB_FINALIZE]             = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
ae23c9
-    [BLOCK_JOB_VERB_DISMISS]              = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
ae23c9
-};
ae23c9
-
ae23c9
-static void block_job_state_transition(BlockJob *job, BlockJobStatus s1)
ae23c9
-{
ae23c9
-    BlockJobStatus s0 = job->status;
ae23c9
-    assert(s1 >= 0 && s1 <= BLOCK_JOB_STATUS__MAX);
ae23c9
-    trace_block_job_state_transition(job, job->ret, BlockJobSTT[s0][s1] ?
ae23c9
-                                     "allowed" : "disallowed",
ae23c9
-                                     BlockJobStatus_str(s0),
ae23c9
-                                     BlockJobStatus_str(s1));
ae23c9
-    assert(BlockJobSTT[s0][s1]);
ae23c9
-    job->status = s1;
ae23c9
-}
ae23c9
-
ae23c9
-static int block_job_apply_verb(BlockJob *job, BlockJobVerb bv, Error **errp)
ae23c9
-{
ae23c9
-    assert(bv >= 0 && bv <= BLOCK_JOB_VERB__MAX);
ae23c9
-    trace_block_job_apply_verb(job, BlockJobStatus_str(job->status),
ae23c9
-                               BlockJobVerb_str(bv),
ae23c9
-                               BlockJobVerbTable[bv][job->status] ?
ae23c9
-                               "allowed" : "prohibited");
ae23c9
-    if (BlockJobVerbTable[bv][job->status]) {
ae23c9
-        return 0;
ae23c9
-    }
ae23c9
-    error_setg(errp, "Job '%s' in state '%s' cannot accept command verb '%s'",
ae23c9
-               job->job.id, BlockJobStatus_str(job->status),
ae23c9
-               BlockJobVerb_str(bv));
ae23c9
-    return -EPERM;
ae23c9
-}
ae23c9
-
ae23c9
 static void block_job_lock(void)
ae23c9
 {
ae23c9
     qemu_mutex_lock(&block_job_mutex);
ae23c9
@@ -257,7 +202,7 @@ static void block_job_detach_aio_context(void *opaque);
ae23c9
 void block_job_unref(BlockJob *job)
ae23c9
 {
ae23c9
     if (--job->refcnt == 0) {
ae23c9
-        assert(job->status == BLOCK_JOB_STATUS_NULL);
ae23c9
+        assert(job->job.status == JOB_STATUS_NULL);
ae23c9
         assert(!job->txn);
ae23c9
         BlockDriverState *bs = blk_bs(job->blk);
ae23c9
         bs->job = NULL;
ae23c9
@@ -409,7 +354,7 @@ void block_job_start(BlockJob *job)
ae23c9
     job->pause_count--;
ae23c9
     job->busy = true;
ae23c9
     job->paused = false;
ae23c9
-    block_job_state_transition(job, BLOCK_JOB_STATUS_RUNNING);
ae23c9
+    job_state_transition(&job->job, JOB_STATUS_RUNNING);
ae23c9
     bdrv_coroutine_enter(blk_bs(job->blk), job->co);
ae23c9
 }
ae23c9
 
ae23c9
@@ -421,7 +366,7 @@ static void block_job_decommission(BlockJob *job)
ae23c9
     job->paused = false;
ae23c9
     job->deferred_to_main_loop = true;
ae23c9
     block_job_txn_del_job(job);
ae23c9
-    block_job_state_transition(job, BLOCK_JOB_STATUS_NULL);
ae23c9
+    job_state_transition(&job->job, JOB_STATUS_NULL);
ae23c9
     block_job_unref(job);
ae23c9
 }
ae23c9
 
ae23c9
@@ -432,7 +377,7 @@ static void block_job_do_dismiss(BlockJob *job)
ae23c9
 
ae23c9
 static void block_job_conclude(BlockJob *job)
ae23c9
 {
ae23c9
-    block_job_state_transition(job, BLOCK_JOB_STATUS_CONCLUDED);
ae23c9
+    job_state_transition(&job->job, JOB_STATUS_CONCLUDED);
ae23c9
     if (job->auto_dismiss || !block_job_started(job)) {
ae23c9
         block_job_do_dismiss(job);
ae23c9
     }
ae23c9
@@ -444,7 +389,7 @@ static void block_job_update_rc(BlockJob *job)
ae23c9
         job->ret = -ECANCELED;
ae23c9
     }
ae23c9
     if (job->ret) {
ae23c9
-        block_job_state_transition(job, BLOCK_JOB_STATUS_ABORTING);
ae23c9
+        job_state_transition(&job->job, JOB_STATUS_ABORTING);
ae23c9
     }
ae23c9
 }
ae23c9
 
ae23c9
@@ -652,7 +597,7 @@ static void block_job_completed_txn_success(BlockJob *job)
ae23c9
     BlockJobTxn *txn = job->txn;
ae23c9
     BlockJob *other_job;
ae23c9
 
ae23c9
-    block_job_state_transition(job, BLOCK_JOB_STATUS_WAITING);
ae23c9
+    job_state_transition(&job->job, JOB_STATUS_WAITING);
ae23c9
 
ae23c9
     /*
ae23c9
      * Successful completion, see if there are other running jobs in this
ae23c9
@@ -677,7 +622,7 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
ae23c9
 {
ae23c9
     int64_t old_speed = job->speed;
ae23c9
 
ae23c9
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_SET_SPEED, errp)) {
ae23c9
+    if (job_apply_verb(&job->job, JOB_VERB_SET_SPEED, errp)) {
ae23c9
         return;
ae23c9
     }
ae23c9
     if (speed < 0) {
ae23c9
@@ -709,7 +654,7 @@ void block_job_complete(BlockJob *job, Error **errp)
ae23c9
 {
ae23c9
     /* Should not be reachable via external interface for internal jobs */
ae23c9
     assert(job->job.id);
ae23c9
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_COMPLETE, errp)) {
ae23c9
+    if (job_apply_verb(&job->job, JOB_VERB_COMPLETE, errp)) {
ae23c9
         return;
ae23c9
     }
ae23c9
     if (job->pause_count || job->cancelled || !job->driver->complete) {
ae23c9
@@ -724,7 +669,7 @@ void block_job_complete(BlockJob *job, Error **errp)
ae23c9
 void block_job_finalize(BlockJob *job, Error **errp)
ae23c9
 {
ae23c9
     assert(job && job->job.id);
ae23c9
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
ae23c9
+    if (job_apply_verb(&job->job, JOB_VERB_FINALIZE, errp)) {
ae23c9
         return;
ae23c9
     }
ae23c9
     block_job_do_finalize(job);
ae23c9
@@ -735,7 +680,7 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
ae23c9
     BlockJob *job = *jobptr;
ae23c9
     /* similarly to _complete, this is QMP-interface only. */
ae23c9
     assert(job->job.id);
ae23c9
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_DISMISS, errp)) {
ae23c9
+    if (job_apply_verb(&job->job, JOB_VERB_DISMISS, errp)) {
ae23c9
         return;
ae23c9
     }
ae23c9
 
ae23c9
@@ -745,7 +690,7 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
ae23c9
 
ae23c9
 void block_job_user_pause(BlockJob *job, Error **errp)
ae23c9
 {
ae23c9
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_PAUSE, errp)) {
ae23c9
+    if (job_apply_verb(&job->job, JOB_VERB_PAUSE, errp)) {
ae23c9
         return;
ae23c9
     }
ae23c9
     if (job->user_paused) {
ae23c9
@@ -768,7 +713,7 @@ void block_job_user_resume(BlockJob *job, Error **errp)
ae23c9
         error_setg(errp, "Can't resume a job that was not paused");
ae23c9
         return;
ae23c9
     }
ae23c9
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_RESUME, errp)) {
ae23c9
+    if (job_apply_verb(&job->job, JOB_VERB_RESUME, errp)) {
ae23c9
         return;
ae23c9
     }
ae23c9
     block_job_iostatus_reset(job);
ae23c9
@@ -778,7 +723,7 @@ void block_job_user_resume(BlockJob *job, Error **errp)
ae23c9
 
ae23c9
 void block_job_cancel(BlockJob *job, bool force)
ae23c9
 {
ae23c9
-    if (job->status == BLOCK_JOB_STATUS_CONCLUDED) {
ae23c9
+    if (job->job.status == JOB_STATUS_CONCLUDED) {
ae23c9
         block_job_do_dismiss(job);
ae23c9
         return;
ae23c9
     }
ae23c9
@@ -794,7 +739,7 @@ void block_job_cancel(BlockJob *job, bool force)
ae23c9
 
ae23c9
 void block_job_user_cancel(BlockJob *job, bool force, Error **errp)
ae23c9
 {
ae23c9
-    if (block_job_apply_verb(job, BLOCK_JOB_VERB_CANCEL, errp)) {
ae23c9
+    if (job_apply_verb(&job->job, JOB_VERB_CANCEL, errp)) {
ae23c9
         return;
ae23c9
     }
ae23c9
     block_job_cancel(job, force);
ae23c9
@@ -859,7 +804,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
ae23c9
     info->speed     = job->speed;
ae23c9
     info->io_status = job->iostatus;
ae23c9
     info->ready     = job->ready;
ae23c9
-    info->status    = job->status;
ae23c9
+    info->status    = job->job.status;
ae23c9
     info->auto_finalize = job->auto_finalize;
ae23c9
     info->auto_dismiss  = job->auto_dismiss;
ae23c9
     info->has_error = job->ret != 0;
ae23c9
@@ -907,7 +852,7 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
ae23c9
 
ae23c9
 static int block_job_event_pending(BlockJob *job)
ae23c9
 {
ae23c9
-    block_job_state_transition(job, BLOCK_JOB_STATUS_PENDING);
ae23c9
+    job_state_transition(&job->job, JOB_STATUS_PENDING);
ae23c9
     if (!job->auto_finalize && !block_job_is_internal(job)) {
ae23c9
         qapi_event_send_block_job_pending(job_type(&job->job),
ae23c9
                                           job->job.id,
ae23c9
@@ -975,7 +920,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
ae23c9
     job->refcnt        = 1;
ae23c9
     job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE);
ae23c9
     job->auto_dismiss  = !(flags & BLOCK_JOB_MANUAL_DISMISS);
ae23c9
-    block_job_state_transition(job, BLOCK_JOB_STATUS_CREATED);
ae23c9
     aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
ae23c9
                    QEMU_CLOCK_REALTIME, SCALE_NS,
ae23c9
                    block_job_sleep_timer_cb, job);
ae23c9
@@ -1017,7 +961,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
ae23c9
 
ae23c9
 void block_job_early_fail(BlockJob *job)
ae23c9
 {
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_CREATED);
ae23c9
+    assert(job->job.status == JOB_STATUS_CREATED);
ae23c9
     block_job_decommission(job);
ae23c9
 }
ae23c9
 
ae23c9
@@ -1077,14 +1021,14 @@ void coroutine_fn block_job_pause_point(BlockJob *job)
ae23c9
     }
ae23c9
 
ae23c9
     if (block_job_should_pause(job) && !block_job_is_cancelled(job)) {
ae23c9
-        BlockJobStatus status = job->status;
ae23c9
-        block_job_state_transition(job, status == BLOCK_JOB_STATUS_READY ? \
ae23c9
-                                   BLOCK_JOB_STATUS_STANDBY :           \
ae23c9
-                                   BLOCK_JOB_STATUS_PAUSED);
ae23c9
+        JobStatus status = job->job.status;
ae23c9
+        job_state_transition(&job->job, status == JOB_STATUS_READY
ae23c9
+                                        ? JOB_STATUS_STANDBY
ae23c9
+                                        : JOB_STATUS_PAUSED);
ae23c9
         job->paused = true;
ae23c9
         block_job_do_yield(job, -1);
ae23c9
         job->paused = false;
ae23c9
-        block_job_state_transition(job, status);
ae23c9
+        job_state_transition(&job->job, status);
ae23c9
     }
ae23c9
 
ae23c9
     if (job->driver->resume) {
ae23c9
@@ -1176,7 +1120,7 @@ void block_job_iostatus_reset(BlockJob *job)
ae23c9
 
ae23c9
 void block_job_event_ready(BlockJob *job)
ae23c9
 {
ae23c9
-    block_job_state_transition(job, BLOCK_JOB_STATUS_READY);
ae23c9
+    job_state_transition(&job->job, JOB_STATUS_READY);
ae23c9
     job->ready = true;
ae23c9
 
ae23c9
     if (block_job_is_internal(job)) {
ae23c9
diff --git a/include/block/blockjob.h b/include/block/blockjob.h
ae23c9
index 10bd9f7..01cdee6 100644
ae23c9
--- a/include/block/blockjob.h
ae23c9
+++ b/include/block/blockjob.h
ae23c9
@@ -147,9 +147,6 @@ typedef struct BlockJob {
ae23c9
      */
ae23c9
     QEMUTimer sleep_timer;
ae23c9
 
ae23c9
-    /** Current state; See @BlockJobStatus for details. */
ae23c9
-    BlockJobStatus status;
ae23c9
-
ae23c9
     /** True if this job should automatically finalize itself */
ae23c9
     bool auto_finalize;
ae23c9
 
ae23c9
diff --git a/include/qemu/job.h b/include/qemu/job.h
ae23c9
index bae2b09..0b78778 100644
ae23c9
--- a/include/qemu/job.h
ae23c9
+++ b/include/qemu/job.h
ae23c9
@@ -41,6 +41,9 @@ typedef struct Job {
ae23c9
     /** The type of this job. */
ae23c9
     const JobDriver *driver;
ae23c9
 
ae23c9
+    /** Current state; See @JobStatus for details. */
ae23c9
+    JobStatus status;
ae23c9
+
ae23c9
     /** Element of the list of jobs */
ae23c9
     QLIST_ENTRY(Job) job_list;
ae23c9
 } Job;
ae23c9
@@ -90,4 +93,14 @@ Job *job_next(Job *job);
ae23c9
  */
ae23c9
 Job *job_get(const char *id);
ae23c9
 
ae23c9
+/**
ae23c9
+ * Check whether the verb @verb can be applied to @job in its current state.
ae23c9
+ * Returns 0 if the verb can be applied; otherwise errp is set and -EPERM
ae23c9
+ * returned.
ae23c9
+ */
ae23c9
+int job_apply_verb(Job *job, JobVerb verb, Error **errp);
ae23c9
+
ae23c9
+/* TODO To be removed from the public interface */
ae23c9
+void job_state_transition(Job *job, JobStatus s1);
ae23c9
+
ae23c9
 #endif
ae23c9
diff --git a/job.c b/job.c
ae23c9
index e57303c..b049a32 100644
ae23c9
--- a/job.c
ae23c9
+++ b/job.c
ae23c9
@@ -28,9 +28,63 @@
ae23c9
 #include "qapi/error.h"
ae23c9
 #include "qemu/job.h"
ae23c9
 #include "qemu/id.h"
ae23c9
+#include "trace-root.h"
ae23c9
 
ae23c9
 static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
ae23c9
 
ae23c9
+/* Job State Transition Table */
ae23c9
+bool JobSTT[JOB_STATUS__MAX][JOB_STATUS__MAX] = {
ae23c9
+                                    /* U, C, R, P, Y, S, W, D, X, E, N */
ae23c9
+    /* U: */ [JOB_STATUS_UNDEFINED] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
ae23c9
+    /* C: */ [JOB_STATUS_CREATED]   = {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1},
ae23c9
+    /* R: */ [JOB_STATUS_RUNNING]   = {0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0},
ae23c9
+    /* P: */ [JOB_STATUS_PAUSED]    = {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
ae23c9
+    /* Y: */ [JOB_STATUS_READY]     = {0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0},
ae23c9
+    /* S: */ [JOB_STATUS_STANDBY]   = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
ae23c9
+    /* W: */ [JOB_STATUS_WAITING]   = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},
ae23c9
+    /* D: */ [JOB_STATUS_PENDING]   = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
ae23c9
+    /* X: */ [JOB_STATUS_ABORTING]  = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
ae23c9
+    /* E: */ [JOB_STATUS_CONCLUDED] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
ae23c9
+    /* N: */ [JOB_STATUS_NULL]      = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
ae23c9
+};
ae23c9
+
ae23c9
+bool JobVerbTable[JOB_VERB__MAX][JOB_STATUS__MAX] = {
ae23c9
+                                    /* U, C, R, P, Y, S, W, D, X, E, N */
ae23c9
+    [JOB_VERB_CANCEL]               = {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
ae23c9
+    [JOB_VERB_PAUSE]                = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
ae23c9
+    [JOB_VERB_RESUME]               = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
ae23c9
+    [JOB_VERB_SET_SPEED]            = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
ae23c9
+    [JOB_VERB_COMPLETE]             = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
ae23c9
+    [JOB_VERB_FINALIZE]             = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
ae23c9
+    [JOB_VERB_DISMISS]              = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
ae23c9
+};
ae23c9
+
ae23c9
+/* TODO Make static once the whole state machine is in job.c */
ae23c9
+void job_state_transition(Job *job, JobStatus s1)
ae23c9
+{
ae23c9
+    JobStatus s0 = job->status;
ae23c9
+    assert(s1 >= 0 && s1 <= JOB_STATUS__MAX);
ae23c9
+    trace_job_state_transition(job, /* TODO re-enable: job->ret */ 0,
ae23c9
+                               JobSTT[s0][s1] ? "allowed" : "disallowed",
ae23c9
+                               JobStatus_str(s0), JobStatus_str(s1));
ae23c9
+    assert(JobSTT[s0][s1]);
ae23c9
+    job->status = s1;
ae23c9
+}
ae23c9
+
ae23c9
+int job_apply_verb(Job *job, JobVerb verb, Error **errp)
ae23c9
+{
ae23c9
+    JobStatus s0 = job->status;
ae23c9
+    assert(verb >= 0 && verb <= JOB_VERB__MAX);
ae23c9
+    trace_job_apply_verb(job, JobStatus_str(s0), JobVerb_str(verb),
ae23c9
+                         JobVerbTable[verb][s0] ? "allowed" : "prohibited");
ae23c9
+    if (JobVerbTable[verb][s0]) {
ae23c9
+        return 0;
ae23c9
+    }
ae23c9
+    error_setg(errp, "Job '%s' in state '%s' cannot accept command verb '%s'",
ae23c9
+               job->id, JobStatus_str(s0), JobVerb_str(verb));
ae23c9
+    return -EPERM;
ae23c9
+}
ae23c9
+
ae23c9
 JobType job_type(const Job *job)
ae23c9
 {
ae23c9
     return job->driver->job_type;
ae23c9
@@ -81,6 +135,8 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
ae23c9
     job->driver        = driver;
ae23c9
     job->id            = g_strdup(job_id);
ae23c9
 
ae23c9
+    job_state_transition(job, JOB_STATUS_CREATED);
ae23c9
+
ae23c9
     QLIST_INSERT_HEAD(&jobs, job, job_list);
ae23c9
 
ae23c9
     return job;
ae23c9
diff --git a/qapi/block-core.json b/qapi/block-core.json
ae23c9
index cab0697..f2ed7a8 100644
ae23c9
--- a/qapi/block-core.json
ae23c9
+++ b/qapi/block-core.json
ae23c9
@@ -1068,9 +1068,9 @@
ae23c9
   'data': ['commit', 'stream', 'mirror', 'backup'] }
ae23c9
 
ae23c9
 ##
ae23c9
-# @BlockJobVerb:
ae23c9
+# @JobVerb:
ae23c9
 #
ae23c9
-# Represents command verbs that can be applied to a blockjob.
ae23c9
+# Represents command verbs that can be applied to a job.
ae23c9
 #
ae23c9
 # @cancel: see @block-job-cancel
ae23c9
 #
ae23c9
@@ -1088,14 +1088,14 @@
ae23c9
 #
ae23c9
 # Since: 2.12
ae23c9
 ##
ae23c9
-{ 'enum': 'BlockJobVerb',
ae23c9
+{ 'enum': 'JobVerb',
ae23c9
   'data': ['cancel', 'pause', 'resume', 'set-speed', 'complete', 'dismiss',
ae23c9
            'finalize' ] }
ae23c9
 
ae23c9
 ##
ae23c9
-# @BlockJobStatus:
ae23c9
+# @JobStatus:
ae23c9
 #
ae23c9
-# Indicates the present state of a given blockjob in its lifetime.
ae23c9
+# Indicates the present state of a given job in its lifetime.
ae23c9
 #
ae23c9
 # @undefined: Erroneous, default state. Should not ever be visible.
ae23c9
 #
ae23c9
@@ -1134,7 +1134,7 @@
ae23c9
 #
ae23c9
 # Since: 2.12
ae23c9
 ##
ae23c9
-{ 'enum': 'BlockJobStatus',
ae23c9
+{ 'enum': 'JobStatus',
ae23c9
   'data': ['undefined', 'created', 'running', 'paused', 'ready', 'standby',
ae23c9
            'waiting', 'pending', 'aborting', 'concluded', 'null' ] }
ae23c9
 
ae23c9
@@ -1184,7 +1184,7 @@
ae23c9
   'data': {'type': 'str', 'device': 'str', 'len': 'int',
ae23c9
            'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int',
ae23c9
            'io-status': 'BlockDeviceIoStatus', 'ready': 'bool',
ae23c9
-           'status': 'BlockJobStatus',
ae23c9
+           'status': 'JobStatus',
ae23c9
            'auto-finalize': 'bool', 'auto-dismiss': 'bool',
ae23c9
            '*error': 'str' } }
ae23c9
 
ae23c9
@@ -2416,7 +2416,7 @@
ae23c9
 # QEMU 2.12+ job lifetime management semantics.
ae23c9
 #
ae23c9
 # This command will refuse to operate on any job that has not yet reached
ae23c9
-# its terminal state, BLOCK_JOB_STATUS_CONCLUDED. For jobs that make use of
ae23c9
+# its terminal state, JOB_STATUS_CONCLUDED. For jobs that make use of the
ae23c9
 # BLOCK_JOB_READY event, block-job-cancel or block-job-complete will still need
ae23c9
 # to be used as appropriate.
ae23c9
 #
ae23c9
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
ae23c9
index b820261..6ccd585 100644
ae23c9
--- a/tests/test-blockjob.c
ae23c9
+++ b/tests/test-blockjob.c
ae23c9
@@ -211,7 +211,7 @@ static CancelJob *create_common(BlockJob **pjob)
ae23c9
     job = mk_job(blk, "Steve", &test_cancel_driver, true,
ae23c9
                  BLOCK_JOB_MANUAL_FINALIZE | BLOCK_JOB_MANUAL_DISMISS);
ae23c9
     block_job_ref(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_CREATED);
ae23c9
+    assert(job->job.status == JOB_STATUS_CREATED);
ae23c9
     s = container_of(job, CancelJob, common);
ae23c9
     s->blk = blk;
ae23c9
 
ae23c9
@@ -223,15 +223,14 @@ static void cancel_common(CancelJob *s)
ae23c9
 {
ae23c9
     BlockJob *job = &s->common;
ae23c9
     BlockBackend *blk = s->blk;
ae23c9
-    BlockJobStatus sts = job->status;
ae23c9
+    JobStatus sts = job->job.status;
ae23c9
 
ae23c9
     block_job_cancel_sync(job);
ae23c9
-    if ((sts != BLOCK_JOB_STATUS_CREATED) &&
ae23c9
-        (sts != BLOCK_JOB_STATUS_CONCLUDED)) {
ae23c9
+    if (sts != JOB_STATUS_CREATED && sts != JOB_STATUS_CONCLUDED) {
ae23c9
         BlockJob *dummy = job;
ae23c9
         block_job_dismiss(&dummy, &error_abort);
ae23c9
     }
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_NULL);
ae23c9
+    assert(job->job.status == JOB_STATUS_NULL);
ae23c9
     block_job_unref(job);
ae23c9
     destroy_blk(blk);
ae23c9
 }
ae23c9
@@ -253,7 +252,7 @@ static void test_cancel_running(void)
ae23c9
     s = create_common(&job;;
ae23c9
 
ae23c9
     block_job_start(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
ae23c9
+    assert(job->job.status == JOB_STATUS_RUNNING);
ae23c9
 
ae23c9
     cancel_common(s);
ae23c9
 }
ae23c9
@@ -266,11 +265,11 @@ static void test_cancel_paused(void)
ae23c9
     s = create_common(&job;;
ae23c9
 
ae23c9
     block_job_start(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
ae23c9
+    assert(job->job.status == JOB_STATUS_RUNNING);
ae23c9
 
ae23c9
     block_job_user_pause(job, &error_abort);
ae23c9
     block_job_enter(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_PAUSED);
ae23c9
+    assert(job->job.status == JOB_STATUS_PAUSED);
ae23c9
 
ae23c9
     cancel_common(s);
ae23c9
 }
ae23c9
@@ -283,11 +282,11 @@ static void test_cancel_ready(void)
ae23c9
     s = create_common(&job;;
ae23c9
 
ae23c9
     block_job_start(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
ae23c9
+    assert(job->job.status == JOB_STATUS_RUNNING);
ae23c9
 
ae23c9
     s->should_converge = true;
ae23c9
     block_job_enter(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_READY);
ae23c9
+    assert(job->job.status == JOB_STATUS_READY);
ae23c9
 
ae23c9
     cancel_common(s);
ae23c9
 }
ae23c9
@@ -300,15 +299,15 @@ static void test_cancel_standby(void)
ae23c9
     s = create_common(&job;;
ae23c9
 
ae23c9
     block_job_start(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
ae23c9
+    assert(job->job.status == JOB_STATUS_RUNNING);
ae23c9
 
ae23c9
     s->should_converge = true;
ae23c9
     block_job_enter(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_READY);
ae23c9
+    assert(job->job.status == JOB_STATUS_READY);
ae23c9
 
ae23c9
     block_job_user_pause(job, &error_abort);
ae23c9
     block_job_enter(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_STANDBY);
ae23c9
+    assert(job->job.status == JOB_STATUS_STANDBY);
ae23c9
 
ae23c9
     cancel_common(s);
ae23c9
 }
ae23c9
@@ -321,18 +320,18 @@ static void test_cancel_pending(void)
ae23c9
     s = create_common(&job;;
ae23c9
 
ae23c9
     block_job_start(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
ae23c9
+    assert(job->job.status == JOB_STATUS_RUNNING);
ae23c9
 
ae23c9
     s->should_converge = true;
ae23c9
     block_job_enter(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_READY);
ae23c9
+    assert(job->job.status == JOB_STATUS_READY);
ae23c9
 
ae23c9
     block_job_complete(job, &error_abort);
ae23c9
     block_job_enter(job);
ae23c9
     while (!s->completed) {
ae23c9
         aio_poll(qemu_get_aio_context(), true);
ae23c9
     }
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_PENDING);
ae23c9
+    assert(job->job.status == JOB_STATUS_PENDING);
ae23c9
 
ae23c9
     cancel_common(s);
ae23c9
 }
ae23c9
@@ -345,21 +344,21 @@ static void test_cancel_concluded(void)
ae23c9
     s = create_common(&job;;
ae23c9
 
ae23c9
     block_job_start(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_RUNNING);
ae23c9
+    assert(job->job.status == JOB_STATUS_RUNNING);
ae23c9
 
ae23c9
     s->should_converge = true;
ae23c9
     block_job_enter(job);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_READY);
ae23c9
+    assert(job->job.status == JOB_STATUS_READY);
ae23c9
 
ae23c9
     block_job_complete(job, &error_abort);
ae23c9
     block_job_enter(job);
ae23c9
     while (!s->completed) {
ae23c9
         aio_poll(qemu_get_aio_context(), true);
ae23c9
     }
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_PENDING);
ae23c9
+    assert(job->job.status == JOB_STATUS_PENDING);
ae23c9
 
ae23c9
     block_job_finalize(job, &error_abort);
ae23c9
-    assert(job->status == BLOCK_JOB_STATUS_CONCLUDED);
ae23c9
+    assert(job->job.status == JOB_STATUS_CONCLUDED);
ae23c9
 
ae23c9
     cancel_common(s);
ae23c9
 }
ae23c9
diff --git a/trace-events b/trace-events
ae23c9
index ed71f44..2507e13 100644
ae23c9
--- a/trace-events
ae23c9
+++ b/trace-events
ae23c9
@@ -104,6 +104,10 @@ gdbstub_err_invalid_rle(void) "got invalid RLE sequence"
ae23c9
 gdbstub_err_checksum_invalid(uint8_t ch) "got invalid command checksum digit: 0x%02x"
ae23c9
 gdbstub_err_checksum_incorrect(uint8_t expected, uint8_t got) "got command packet with incorrect checksum, expected=0x%02x, received=0x%02x"
ae23c9
 
ae23c9
+# job.c
ae23c9
+job_state_transition(void *job,  int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)"
ae23c9
+job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)"
ae23c9
+
ae23c9
 ### Guest events, keep at bottom
ae23c9
 
ae23c9
 
ae23c9
-- 
ae23c9
1.8.3.1
ae23c9