aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From wfar...@apache.org
Subject incubator-aurora git commit: Change remaining update-related RPCs to use JobUpdateKey.
Date Sat, 07 Mar 2015 01:17:36 GMT
Repository: incubator-aurora
Updated Branches:
  refs/heads/master 0913a9c66 -> fdc11e3bd


Change remaining update-related RPCs to use JobUpdateKey.

Bugs closed: AURORA-1093

Reviewed at https://reviews.apache.org/r/31779/


Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/fdc11e3b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/fdc11e3b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/fdc11e3b

Branch: refs/heads/master
Commit: fdc11e3bd5b90bc01b80b45f5e0909e9575578a5
Parents: 0913a9c
Author: Bill Farner <wfarner@apache.org>
Authored: Fri Mar 6 17:16:36 2015 -0800
Committer: Bill Farner <wfarner@apache.org>
Committed: Fri Mar 6 17:16:36 2015 -0800

----------------------------------------------------------------------
 .../thrift/org/apache/aurora/gen/api.thrift     |  17 ++--
 .../thrift/SchedulerThriftInterface.java        |  38 ++++---
 .../scheduler/updater/JobUpdateController.java  |  19 ++--
 .../updater/JobUpdateControllerImpl.java        | 100 ++++++++++---------
 .../updater/JobUpdateStateMachine.java          |   8 +-
 .../python/apache/aurora/client/api/__init__.py |  24 ++---
 .../python/apache/aurora/client/cli/__init__.py |   1 +
 .../python/apache/aurora/client/cli/update.py   |  96 +++++++++++-------
 .../thrift/SchedulerThriftInterfaceTest.java    |  42 ++++----
 .../scheduler/thrift/aop/ForwardingThrift.java  |  12 +--
 .../updater/JobUpdateStateMachineTest.java      |   2 +-
 .../aurora/scheduler/updater/JobUpdaterIT.java  |  36 ++++---
 .../python/apache/aurora/client/api/test_api.py |  32 +-----
 .../apache/aurora/client/cli/test_create.py     |   2 +-
 .../apache/aurora/client/cli/test_quota.py      |   2 +-
 .../apache/aurora/client/cli/test_restart.py    |   3 +-
 .../apache/aurora/client/cli/test_supdate.py    |  95 +++++++++++++-----
 .../python/apache/aurora/client/cli/util.py     |   2 +-
 18 files changed, 292 insertions(+), 239 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/api/src/main/thrift/org/apache/aurora/gen/api.thrift
----------------------------------------------------------------------
diff --git a/api/src/main/thrift/org/apache/aurora/gen/api.thrift b/api/src/main/thrift/org/apache/aurora/gen/api.thrift
index 6ca1a1e..badb8ee 100644
--- a/api/src/main/thrift/org/apache/aurora/gen/api.thrift
+++ b/api/src/main/thrift/org/apache/aurora/gen/api.thrift
@@ -983,10 +983,10 @@ service ReadOnlyScheduler {
   /** Returns all stored context specific resource/operation locks. */
   Response getLocks()
 
-  /** Gets job update summaries. Not implemented yet. */
+  /** Gets job update summaries. */
   Response getJobUpdateSummaries(1: JobUpdateQuery jobUpdateQuery)
 
-  /** Gets job update details. Not implemented yet. */
+  /** Gets job update details. */
   Response getJobUpdateDetails(1: JobUpdateKey key)
 }
 
@@ -1054,16 +1054,15 @@ service AuroraSchedulerManager extends ReadOnlyScheduler {
   Response startJobUpdate(1: JobUpdateRequest request, 2: SessionKey session)
 
   /**
-   * Pauses the update progress for the specified job. Can be resumed by resumeUpdate call.
-   * Not implemented yet.
+   * Pauses the specified job update. Can be resumed by resumeUpdate call.
    */
-  Response pauseJobUpdate(1: JobKey jobKey, 2: SessionKey session)
+  Response pauseJobUpdate(1: JobUpdateKey key, 2: SessionKey session)
 
-  /** Resumes progress of a previously paused job update. Not implemented yet. */
-  Response resumeJobUpdate(1: JobKey jobKey, 2: SessionKey session)
+  /** Resumes progress of a previously paused job update. */
+  Response resumeJobUpdate(1: JobUpdateKey key, 2: SessionKey session)
 
-  /** Permanently aborts the job update. Does not remove the update history. Not implemented yet. */
-  Response abortJobUpdate(1: JobKey jobKey, 2: SessionKey session)
+  /** Permanently aborts the job update. Does not remove the update history. */
+  Response abortJobUpdate(1: JobUpdateKey key, 2: SessionKey session)
 
   /**
    * Allows progress of the job update in case blockIfNoPulsesAfterMs is specified in

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java b/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
index dbb5c19..c0d4803 100644
--- a/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
+++ b/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
@@ -1215,7 +1215,6 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
                 .setKey(new JobUpdateKey(job.newBuilder(), updateId))
                 .setJobKey(job.newBuilder())
                 .setUpdateId(updateId)
-                .setKey(new JobUpdateKey(job.newBuilder(), updateId))
                 .setUser(context.getIdentity()))
             .setInstructions(instructions));
         try {
@@ -1235,16 +1234,15 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
   }
 
   private Response changeJobUpdateState(
-      JobKey mutableJobKey,
+      JobUpdateKey mutableKey,
       SessionKey session,
       final JobUpdateStateChange change) {
 
-    final IJobKey jobKey = JobKeys.assertValid(IJobKey.build(requireNonNull(mutableJobKey)));
+    final IJobUpdateKey key = IJobUpdateKey.build(mutableKey);
+    JobKeys.assertValid(key.getJob());
     final SessionContext context;
     try {
-      // TODO(maxim): pass a JobUpdateKey here instead of generating a fake one. AURORA-1093.
-      IJobUpdateKey updateKey = IJobUpdateKey.build(new JobUpdateKey().setJob(mutableJobKey));
-      context = authorizeJobUpdateAction(updateKey, session);
+      context = authorizeJobUpdateAction(key, session);
     } catch (AuthFailedException e) {
       return error(AUTH_FAILED, e);
     }
@@ -1252,7 +1250,7 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
       @Override
       public Response apply(MutableStoreProvider storeProvider) {
         try {
-          change.modifyUpdate(jobUpdateController, jobKey, context.getIdentity());
+          change.modifyUpdate(jobUpdateController, key, context.getIdentity());
           return ok();
         } catch (UpdateStateException e) {
           return error(INVALID_REQUEST, e);
@@ -1262,50 +1260,50 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
   }
 
   private interface JobUpdateStateChange {
-    void modifyUpdate(JobUpdateController controller, IJobKey job, String invokingUser)
+    void modifyUpdate(JobUpdateController controller, IJobUpdateKey key, String invokingUser)
         throws UpdateStateException;
   }
 
   private static final JobUpdateStateChange PAUSE = new JobUpdateStateChange() {
     @Override
-    public void modifyUpdate(JobUpdateController controller, IJobKey job, String invokingUser)
+    public void modifyUpdate(JobUpdateController controller, IJobUpdateKey key, String invokingUser)
         throws UpdateStateException {
 
-      controller.pause(job, invokingUser);
+      controller.pause(key, invokingUser);
     }
   };
 
   private static final JobUpdateStateChange RESUME = new JobUpdateStateChange() {
     @Override
-    public void modifyUpdate(JobUpdateController controller, IJobKey job, String invokingUser)
+    public void modifyUpdate(JobUpdateController controller, IJobUpdateKey key, String invokingUser)
         throws UpdateStateException {
 
-      controller.resume(job, invokingUser);
+      controller.resume(key, invokingUser);
     }
   };
 
   private static final JobUpdateStateChange ABORT = new JobUpdateStateChange() {
     @Override
-    public void modifyUpdate(JobUpdateController controller, IJobKey job, String invokingUser)
+    public void modifyUpdate(JobUpdateController controller, IJobUpdateKey key, String invokingUser)
         throws UpdateStateException {
 
-      controller.abort(job, invokingUser);
+      controller.abort(key, invokingUser);
     }
   };
 
   @Override
-  public Response pauseJobUpdate(JobKey mutableJobKey, SessionKey session) {
-    return changeJobUpdateState(mutableJobKey, session, PAUSE);
+  public Response pauseJobUpdate(JobUpdateKey mutableKey, SessionKey session) {
+    return changeJobUpdateState(mutableKey, session, PAUSE);
   }
 
   @Override
-  public Response resumeJobUpdate(final JobKey mutableJobKey, final SessionKey session) {
-    return changeJobUpdateState(mutableJobKey, session, RESUME);
+  public Response resumeJobUpdate(JobUpdateKey mutableKey, SessionKey session) {
+    return changeJobUpdateState(mutableKey, session, RESUME);
   }
 
   @Override
-  public Response abortJobUpdate(final JobKey mutableJobKey, final SessionKey session) {
-    return changeJobUpdateState(mutableJobKey, session, ABORT);
+  public Response abortJobUpdate(JobUpdateKey mutableKey, SessionKey session) {
+    return changeJobUpdateState(mutableKey, session, ABORT);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateController.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateController.java b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateController.java
index 5989a62..78024a8 100644
--- a/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateController.java
+++ b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateController.java
@@ -15,7 +15,6 @@ package org.apache.aurora.scheduler.updater;
 
 import org.apache.aurora.gen.JobUpdatePulseStatus;
 import org.apache.aurora.scheduler.storage.entities.IInstanceKey;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdate;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
@@ -39,13 +38,13 @@ public interface JobUpdateController {
   /**
    * Pauses an in-progress update.
    * <p>
-   * A paused update may be resumed by invoking {@link #resume(IJobKey, String)}.
+   * A paused update may be resumed by invoking {@link #resume(IJobUpdateKey, String)}.
    *
-   * @param job Job whose update should be paused.
+   * @param key Update to pause.
    * @param pausingUser The name of the user who is pausing the update.
    * @throws UpdateStateException If the job update is not in a state that may be paused.
    */
-  void pause(IJobKey job, String pausingUser) throws UpdateStateException;
+  void pause(IJobUpdateKey key, String pausingUser) throws UpdateStateException;
 
   /**
    * Resumes a paused in-progress update.
@@ -54,11 +53,11 @@ public interface JobUpdateController {
    * updater was rolling forward, it will resume rolling forward. If it was rolling back, it will
    * resume rolling back.
    *
-   * @param job Job whose update should be resumed.
+   * @param key Update to resume.
    * @param resumingUser The name of the user who is resuming the update.
    * @throws UpdateStateException If the job update is not in a state that may be resumed.
    */
-  void resume(IJobKey job, String resumingUser) throws UpdateStateException;
+  void resume(IJobUpdateKey key, String resumingUser) throws UpdateStateException;
 
   /**
    * Aborts an in-progress update.
@@ -66,11 +65,11 @@ public interface JobUpdateController {
    * This will abandon the update, and make no further modifications to the job on behalf of the
    * update. An aborted update may not be resumed.
    *
-   * @param job Job whose update should be aborted.
+   * @param key Update to abort.
    * @param abortingUser The name of the user who is aborting the update.
    * @throws UpdateStateException If there is no active update for the job.
    */
-  void abort(IJobKey job, String abortingUser) throws UpdateStateException;
+  void abort(IJobUpdateKey key, String abortingUser) throws UpdateStateException;
 
   /**
    * Notifies the updater that the state of an instance has changed. A state change could also mean
@@ -89,8 +88,8 @@ public interface JobUpdateController {
 
   /**
    * Restores active updates that have been halted due to the scheduler restarting.
-   * This is distinct from {@link #resume(IJobKey, String)} in that it does not change the state of
-   * updates, but resumes after a restart of the scheduler process.
+   * This is distinct from {@link #resume(IJobUpdateKey, String)} in that it does not change the
+   * state of updates, but resumes after a restart of the scheduler process.
    */
   void systemResume();
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateControllerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateControllerImpl.java b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateControllerImpl.java
index acdade3..5d3b16a 100644
--- a/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateControllerImpl.java
+++ b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateControllerImpl.java
@@ -24,6 +24,7 @@ import java.util.logging.Logger;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
+import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -196,24 +197,24 @@ class JobUpdateControllerImpl implements JobUpdateController {
   }
 
   @Override
-  public void pause(final IJobKey job, String user) throws UpdateStateException {
-    requireNonNull(job);
-    LOG.info("Attempting to pause update for " + job);
-    unscopedChangeUpdateStatus(job, GET_PAUSE_STATE, Optional.of(user));
+  public void pause(final IJobUpdateKey key, String user) throws UpdateStateException {
+    requireNonNull(key);
+    LOG.info("Attempting to pause update " + key);
+    unscopedChangeUpdateStatus(key, GET_PAUSE_STATE, Optional.of(user));
   }
 
   @Override
-  public void resume(final IJobKey job, final String user) throws UpdateStateException {
-    requireNonNull(job);
-    LOG.info("Attempting to resume update for " + job);
+  public void resume(final IJobUpdateKey key, final String user) throws UpdateStateException {
+    requireNonNull(key);
+    LOG.info("Attempting to resume update " + key);
     storage.write(new MutateWork.NoResult<UpdateStateException>() {
       @Override
       protected void execute(MutableStoreProvider storeProvider) throws UpdateStateException {
         IJobUpdateDetails details = Iterables.getOnlyElement(
-            storeProvider.getJobUpdateStore().fetchJobUpdateDetails(queryActiveByJob(job)), null);
+            storeProvider.getJobUpdateStore().fetchJobUpdateDetails(queryByUpdate(key)), null);
 
         if (details == null) {
-          throw new UpdateStateException("There is no active update for " + job);
+          throw new UpdateStateException("Update does not exist: " + key);
         }
 
         IJobUpdate update = details.getUpdate();
@@ -230,9 +231,9 @@ class JobUpdateControllerImpl implements JobUpdateController {
   }
 
   @Override
-  public void abort(IJobKey job, String user) throws UpdateStateException {
-    requireNonNull(job);
-    unscopedChangeUpdateStatus(job, new Function<JobUpdateStatus, JobUpdateStatus>() {
+  public void abort(IJobUpdateKey key, String user) throws UpdateStateException {
+    requireNonNull(key);
+    unscopedChangeUpdateStatus(key, new Function<JobUpdateStatus, JobUpdateStatus>() {
       @Override
       public JobUpdateStatus apply(JobUpdateStatus input) {
         return ABORTED;
@@ -259,14 +260,18 @@ class JobUpdateControllerImpl implements JobUpdateController {
             pulseHandler.initializePulseState(details.getUpdate(), status);
           }
 
-          changeJobUpdateStatus(storeProvider, key, status, NO_USER, false);
+          try {
+            changeJobUpdateStatus(storeProvider, key, status, NO_USER, false);
+          } catch (UpdateStateException e) {
+            throw Throwables.propagate(e);
+          }
         }
       }
     });
   }
 
   @Override
-  public JobUpdatePulseStatus pulse(IJobUpdateKey key) throws UpdateStateException {
+  public JobUpdatePulseStatus pulse(final IJobUpdateKey key) throws UpdateStateException {
     final PulseState state = pulseHandler.pulseAndGet(key);
     if (state == null) {
       LOG.info("Not pulsing inactive job update: " + key);
@@ -285,7 +290,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
         public void run() {
           try {
             unscopedChangeUpdateStatus(
-                state.getJobKey(),
+                key,
                 GET_UNBLOCKED_STATE,
                 Optional.of(INTERNAL_USER));
           } catch (UpdateStateException e) {
@@ -323,11 +328,15 @@ class JobUpdateControllerImpl implements JobUpdateController {
         if (update != null) {
           if (update.getUpdater().containsInstance(instance.getInstanceId())) {
             LOG.info("Forwarding task change for " + InstanceKeys.toString(instance));
-            evaluateUpdater(
-                storeProvider,
-                update,
-                getOnlyMatch(storeProvider.getJobUpdateStore(), queryActiveByJob(job)),
-                ImmutableMap.of(instance.getInstanceId(), state));
+            try {
+              evaluateUpdater(
+                  storeProvider,
+                  update,
+                  getOnlyMatch(storeProvider.getJobUpdateStore(), queryActiveByJob(job)),
+                  ImmutableMap.of(instance.getInstanceId(), state));
+            } catch (UpdateStateException e) {
+              throw Throwables.propagate(e);
+            }
           } else {
             LOG.info("Instance " + instance + " is not part of active update for "
                 + JobKeys.canonicalString(job));
@@ -353,14 +362,14 @@ class JobUpdateControllerImpl implements JobUpdateController {
    * when responding to outside inputs that are inherently un-scoped, such as a user action or task
    * state change.
    *
-   * @param job Job whose update state should be changed.
+   * @param key Update identifier.
    * @param stateChange State change computation, based on the current state of the update.
    * @param user The user who is changing the state.
    * @throws UpdateStateException If no active update exists for the provided {@code job}, or
    *                              if the proposed state transition is not allowed.
    */
   private void unscopedChangeUpdateStatus(
-      final IJobKey job,
+      final IJobUpdateKey key,
       final Function<JobUpdateStatus, JobUpdateStatus> stateChange,
       final Optional<String> user)
       throws UpdateStateException {
@@ -371,9 +380,9 @@ class JobUpdateControllerImpl implements JobUpdateController {
           throws UpdateStateException {
 
         IJobUpdateSummary update = Iterables.getOnlyElement(
-            storeProvider.getJobUpdateStore().fetchJobUpdateSummaries(queryActiveByJob(job)), null);
+            storeProvider.getJobUpdateStore().fetchJobUpdateSummaries(queryByUpdate(key)), null);
         if (update == null) {
-          throw new UpdateStateException("There is no active update for " + job);
+          throw new UpdateStateException("Update does not exist " + key);
         }
 
         JobUpdateStatus status = update.getState().getStatus();
@@ -387,7 +396,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
       MutableStoreProvider storeProvider,
       IJobUpdateSummary updateSummary,
       JobUpdateStatus newStatus,
-      Optional<String> user) {
+      Optional<String> user) throws UpdateStateException {
 
     if (updateSummary.getState().getStatus() == newStatus) {
       return;
@@ -401,7 +410,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
       MutableStoreProvider storeProvider,
       IJobUpdateKey key,
       JobUpdateStatus status,
-      Optional<String> user) {
+      Optional<String> user) throws UpdateStateException {
 
     changeJobUpdateStatus(storeProvider, key, status, user, true);
   }
@@ -419,7 +428,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
       IJobUpdateKey key,
       JobUpdateStatus newStatus,
       Optional<String> user,
-      boolean recordChange) {
+      boolean recordChange) throws UpdateStateException {
 
     JobUpdateStatus status;
     boolean record;
@@ -520,7 +529,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
       final MutableStoreProvider storeProvider,
       final UpdateFactory.Update update,
       IJobUpdateSummary summary,
-      Map<Integer, Optional<IScheduledTask>> changedInstance) {
+      Map<Integer, Optional<IScheduledTask>> changedInstance) throws UpdateStateException {
 
     JobUpdateStatus updaterStatus = summary.getState().getStatus();
     final IJobUpdateKey key = summary.getKey();
@@ -664,16 +673,20 @@ class JobUpdateControllerImpl implements JobUpdateController {
             // Suppress this evaluation if the updater is not currently active.
             if (JobUpdateStateMachine.isActive(status)) {
               UpdateFactory.Update update = updates.get(instance.getJobKey());
-              evaluateUpdater(
-                  storeProvider,
-                  update,
-                  summary,
-                  ImmutableMap.of(
-                      instance.getInstanceId(),
-                      getActiveInstance(
-                          storeProvider.getTaskStore(),
-                          instance.getJobKey(),
-                          instance.getInstanceId())));
+              try {
+                evaluateUpdater(
+                    storeProvider,
+                    update,
+                    summary,
+                    ImmutableMap.of(
+                        instance.getInstanceId(),
+                        getActiveInstance(
+                            storeProvider.getTaskStore(),
+                            instance.getJobKey(),
+                            instance.getInstanceId())));
+              } catch (UpdateStateException e) {
+                throw Throwables.propagate(e);
+              }
             }
           }
         });
@@ -696,7 +709,6 @@ class JobUpdateControllerImpl implements JobUpdateController {
 
     synchronized void initializePulseState(IJobUpdate update, JobUpdateStatus status) {
       pulseStates.put(update.getSummary().getKey(), new PulseState(
-          update.getSummary().getJobKey(),
           status,
           update.getInstructions().getSettings().getBlockIfNoPulsesAfterMs(),
           0L));
@@ -706,7 +718,6 @@ class JobUpdateControllerImpl implements JobUpdateController {
       PulseState state = pulseStates.get(key);
       if (state != null) {
         state = pulseStates.put(key, new PulseState(
-            state.getJobKey(),
             state.getStatus(),
             state.getPulseTimeoutMs(),
             clock.nowMillis()));
@@ -718,7 +729,6 @@ class JobUpdateControllerImpl implements JobUpdateController {
       PulseState state = pulseStates.get(key);
       if (state != null) {
         pulseStates.put(key, new PulseState(
-            state.getJobKey(),
             status,
             state.getPulseTimeoutMs(),
             state.getLastPulseMs()));
@@ -735,22 +745,16 @@ class JobUpdateControllerImpl implements JobUpdateController {
   }
 
   private static class PulseState {
-    private final IJobKey jobKey;
     private final JobUpdateStatus status;
     private final long pulseTimeoutMs;
     private final long lastPulseMs;
 
-    PulseState(IJobKey jobKey, JobUpdateStatus status, long pulseTimeoutMs, long lastPulseMs) {
-      this.jobKey = requireNonNull(jobKey);
+    PulseState(JobUpdateStatus status, long pulseTimeoutMs, long lastPulseMs) {
       this.status = requireNonNull(status);
       this.pulseTimeoutMs = pulseTimeoutMs;
       this.lastPulseMs = lastPulseMs;
     }
 
-    IJobKey getJobKey() {
-      return jobKey;
-    }
-
     JobUpdateStatus getStatus() {
       return status;
     }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachine.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachine.java b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachine.java
index 9b15b2d..74e915c 100644
--- a/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachine.java
+++ b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachine.java
@@ -179,11 +179,13 @@ final class JobUpdateStateMachine {
    *
    * @param from Starting state.
    * @param to Desired target state.
-   * @throws IllegalStateException if the requested transition is not allowed.
+   * @throws UpdateStateException if the requested transition is not allowed.
    */
-  static void assertTransitionAllowed(JobUpdateStatus from, JobUpdateStatus to) {
+  static void assertTransitionAllowed(JobUpdateStatus from, JobUpdateStatus to)
+      throws UpdateStateException {
+
     if (!ALLOWED_TRANSITIONS.containsEntry(from, to)) {
-      throw new IllegalStateException("Cannot transition update from " + from + " to " + to);
+      throw new UpdateStateException("Cannot transition update from " + from + " to " + to);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/main/python/apache/aurora/client/api/__init__.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/api/__init__.py b/src/main/python/apache/aurora/client/api/__init__.py
index c071227..194629f 100644
--- a/src/main/python/apache/aurora/client/api/__init__.py
+++ b/src/main/python/apache/aurora/client/api/__init__.py
@@ -173,41 +173,35 @@ class AuroraClientAPI(object):
 
     return self._scheduler_proxy.startJobUpdate(request)
 
-  def pause_job_update(self, job_key):
+  def pause_job_update(self, update_key):
     """Requests Scheduler to pause active job update.
 
     Arguments:
-    job_key -- Job key identifying the update to pause.
+    update_key -- Update identifier.
 
     Returns response object.
     """
-    self._assert_valid_job_key(job_key)
-    log.info("Pausing update for: %s" % job_key.to_path())
-    return self._scheduler_proxy.pauseJobUpdate(job_key.to_thrift())
+    return self._scheduler_proxy.pauseJobUpdate(update_key)
 
-  def resume_job_update(self, job_key):
+  def resume_job_update(self, update_key):
     """Requests Scheduler to resume a job update paused previously.
 
     Arguments:
-    job_key -- Job key identifying the update to resume.
+    update_key -- Update identifier.
 
     Returns response object.
     """
-    self._assert_valid_job_key(job_key)
-    log.info("Resuming update for: %s" % job_key.to_path())
-    return self._scheduler_proxy.resumeJobUpdate(job_key.to_thrift())
+    return self._scheduler_proxy.resumeJobUpdate(update_key)
 
-  def abort_job_update(self, job_key):
+  def abort_job_update(self, update_key):
     """Requests Scheduler to abort active or paused job update.
 
     Arguments:
-    job_key -- Job key identifying the update to abort.
+    update_key -- Update identifier.
 
     Returns response object.
     """
-    self._assert_valid_job_key(job_key)
-    log.info("Aborting update for: %s" % job_key.to_path())
-    return self._scheduler_proxy.abortJobUpdate(job_key.to_thrift())
+    return self._scheduler_proxy.abortJobUpdate(update_key)
 
   def query_job_updates(self, role=None, job_key=None, user=None, update_statuses=None):
     """Returns all job updates matching the query.

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/main/python/apache/aurora/client/cli/__init__.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/__init__.py b/src/main/python/apache/aurora/client/cli/__init__.py
index 2e99677..4d9ef09 100644
--- a/src/main/python/apache/aurora/client/cli/__init__.py
+++ b/src/main/python/apache/aurora/client/cli/__init__.py
@@ -325,6 +325,7 @@ class CommandLine(AbstractClass):
       self.print_err("Error executing command: %s" % c.msg)
       return c.code
     except Exception:
+      # TODO(wfarner): Remove this block - it hides exceptions and complicates testing.
       print("Fatal error running command:", file=sys.stderr)
       print(traceback.format_exc(), file=sys.stderr)
       return EXIT_UNKNOWN_ERROR

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/main/python/apache/aurora/client/cli/update.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/update.py b/src/main/python/apache/aurora/client/cli/update.py
index a489005..37cc498 100644
--- a/src/main/python/apache/aurora/client/cli/update.py
+++ b/src/main/python/apache/aurora/client/cli/update.py
@@ -49,6 +49,57 @@ from gen.apache.aurora.api.constants import ACTIVE_JOB_UPDATE_STATES
 from gen.apache.aurora.api.ttypes import JobUpdateAction, JobUpdateStatus
 
 
+class UpdateController(object):
+  def __init__(self, api, context):
+    self.api = api
+    self.context = context
+
+  def get_update_key(self, job_key):
+    response = self.api.query_job_updates(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=job_key)
+    self.context.log_response_and_raise(response)
+    summaries = response.result.getJobUpdateSummariesResult.updateSummaries
+    if response.result.getJobUpdateSummariesResult.updateSummaries:
+      if len(summaries) == 1:
+        return summaries[0].key
+      else:
+        raise self.context.CommandError(
+            EXIT_API_ERROR,
+            "scheduler returned multiple active updates for this job.")
+    else:
+      return None
+
+  def _modify_update(self, job_key, mutate_fn, error_msg, success_msg):
+    update_key = self.get_update_key(job_key)
+    if update_key is None:
+      self.context.print_err("No active update found for this job.")
+      return EXIT_INVALID_PARAMETER
+    resp = mutate_fn(update_key)
+    self.context.log_response_and_raise(resp, err_code=EXIT_API_ERROR, err_msg=error_msg)
+    self.context.print_out(success_msg)
+    return EXIT_OK
+
+  def pause(self, job_key):
+    return self._modify_update(
+        job_key,
+        lambda key: self.api.pause_job_update(key),
+        "Failed to pause update due to error:",
+        "Update has been paused.")
+
+  def resume(self, job_key):
+    return self._modify_update(
+        job_key,
+        lambda key: self.api.resume_job_update(key),
+        "Failed to resume update due to error:",
+        "Update has been resumed.")
+
+  def abort(self, job_key):
+    return self._modify_update(
+        job_key,
+        lambda key: self.api.abort_job_update(key),
+        "Failed to abort update due to error:",
+        "Update has been aborted.")
+
+
 class StartUpdate(Verb):
 
   UPDATE_MSG_TEMPLATE = "Job update has started. View your update progress at %s"
@@ -116,13 +167,8 @@ class PauseUpdate(Verb):
     return """Pause a scheduler-driven rolling update."""
 
   def execute(self, context):
-    jobkey = context.options.jobspec
-    api = context.get_api(jobkey.cluster)
-    resp = api.pause_job_update(jobkey)
-    context.log_response_and_raise(resp, err_code=EXIT_API_ERROR,
-      err_msg="Failed to pause update due to error:")
-    context.print_out("Update has been paused.")
-    return EXIT_OK
+    job_key = context.options.jobspec
+    return UpdateController(context.get_api(job_key.cluster), context).pause(job_key)
 
 
 class ResumeUpdate(Verb):
@@ -140,13 +186,8 @@ class ResumeUpdate(Verb):
     return """Resume a paused scheduler-driven rolling update."""
 
   def execute(self, context):
-    jobkey = context.options.jobspec
-    api = context.get_api(jobkey.cluster)
-    resp = api.resume_job_update(jobkey)
-    context.log_response_and_raise(resp, err_code=EXIT_API_ERROR,
-      err_msg="Failed to resume update due to error:")
-    context.print_out("Update has been resumed.")
-    return EXIT_OK
+    job_key = context.options.jobspec
+    return UpdateController(context.get_api(job_key.cluster), context).resume(job_key)
 
 
 class AbortUpdate(Verb):
@@ -164,13 +205,8 @@ class AbortUpdate(Verb):
     return """Abort an in-progress scheduler-driven rolling update."""
 
   def execute(self, context):
-    jobkey = context.options.jobspec
-    api = context.get_api(jobkey.cluster)
-    resp = api.abort_job_update(jobkey)
-    context.log_response_and_raise(resp, err_code=EXIT_API_ERROR,
-      err_msg="Failed to abort update due to error:")
-    context.print_out("Update has been aborted.")
-    return EXIT_OK
+    job_key = context.options.jobspec
+    return UpdateController(context.get_api(job_key.cluster), context).abort(job_key)
 
 
 class ListUpdates(Verb):
@@ -245,21 +281,11 @@ class UpdateStatus(Verb):
   def help(self):
     return """Display detailed status information about a scheduler-driven in-progress update."""
 
-  def _get_update_key(self, context, job_key):
-    api = context.get_api(context.options.jobspec.cluster)
-    response = api.query_job_updates(
-        update_statuses=ACTIVE_JOB_UPDATE_STATES,
-        job_key=context.options.jobspec)
-    context.log_response_and_raise(response)
-    for summary in response.result.getJobUpdateSummariesResult.updateSummaries:
-      if summary.key.job == job_key.to_thrift():
-        return summary.key
-    else:
-      return None
-
   def execute(self, context):
-    key = self._get_update_key(context, context.options.jobspec)
-    if not key:
+    key = UpdateController(
+        context.get_api(context.options.jobspec.cluster),
+        context).get_update_key(context.options.jobspec)
+    if key is None:
       context.print_err("No updates found for job %s" % context.options.jobspec)
       return EXIT_INVALID_PARAMETER
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
index 8247408..7f5e5c2 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
@@ -2203,22 +2203,22 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
   @Test
   public void testPauseJobUpdateByCoordinator() throws Exception {
     expectAuth(UPDATE_COORDINATOR, true);
-    jobUpdateController.pause(JOB_KEY, USER);
+    jobUpdateController.pause(UPDATE_KEY, USER);
 
     control.replay();
 
-    assertResponse(OK, thrift.pauseJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(OK, thrift.pauseJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testPauseJobUpdateByUser() throws Exception {
     expectAuth(ROLE, true);
     expectAuth(UPDATE_COORDINATOR, false);
-    jobUpdateController.pause(JOB_KEY, USER);
+    jobUpdateController.pause(UPDATE_KEY, USER);
 
     control.replay();
 
-    assertResponse(OK, thrift.pauseJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(OK, thrift.pauseJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
@@ -2228,39 +2228,39 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
 
     control.replay();
 
-    assertResponse(AUTH_FAILED, thrift.pauseJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(AUTH_FAILED, thrift.pauseJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testPauseJobUpdateFailsInController() throws Exception {
     expectAuth(UPDATE_COORDINATOR, true);
-    jobUpdateController.pause(JOB_KEY, USER);
+    jobUpdateController.pause(UPDATE_KEY, USER);
     expectLastCall().andThrow(new UpdateStateException("failed"));
 
     control.replay();
 
-    assertResponse(INVALID_REQUEST, thrift.pauseJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(INVALID_REQUEST, thrift.pauseJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testResumeJobUpdateByCoordinator() throws Exception {
     expectAuth(UPDATE_COORDINATOR, true);
-    jobUpdateController.resume(JOB_KEY, USER);
+    jobUpdateController.resume(UPDATE_KEY, USER);
 
     control.replay();
 
-    assertResponse(OK, thrift.resumeJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(OK, thrift.resumeJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testResumeJobUpdateByUser() throws Exception {
     expectAuth(ROLE, true);
     expectAuth(UPDATE_COORDINATOR, false);
-    jobUpdateController.resume(JOB_KEY, USER);
+    jobUpdateController.resume(UPDATE_KEY, USER);
 
     control.replay();
 
-    assertResponse(OK, thrift.resumeJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(OK, thrift.resumeJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
@@ -2270,39 +2270,39 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
 
     control.replay();
 
-    assertResponse(AUTH_FAILED, thrift.resumeJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(AUTH_FAILED, thrift.resumeJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testResumeJobUpdateFailsInController() throws Exception {
     expectAuth(UPDATE_COORDINATOR, true);
-    jobUpdateController.resume(JOB_KEY, USER);
+    jobUpdateController.resume(UPDATE_KEY, USER);
     expectLastCall().andThrow(new UpdateStateException("failed"));
 
     control.replay();
 
-    assertResponse(INVALID_REQUEST, thrift.resumeJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(INVALID_REQUEST, thrift.resumeJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testAbortJobUpdateByCoordinator() throws Exception {
     expectAuth(UPDATE_COORDINATOR, true);
-    jobUpdateController.abort(JOB_KEY, USER);
+    jobUpdateController.abort(UPDATE_KEY, USER);
 
     control.replay();
 
-    assertResponse(OK, thrift.abortJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(OK, thrift.abortJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testAbortJobUpdateByUser() throws Exception {
     expectAuth(ROLE, true);
     expectAuth(UPDATE_COORDINATOR, false);
-    jobUpdateController.abort(JOB_KEY, USER);
+    jobUpdateController.abort(UPDATE_KEY, USER);
 
     control.replay();
 
-    assertResponse(OK, thrift.abortJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(OK, thrift.abortJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
@@ -2312,18 +2312,18 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
 
     control.replay();
 
-    assertResponse(AUTH_FAILED, thrift.abortJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(AUTH_FAILED, thrift.abortJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test
   public void testAbortJobUpdateFailsInController() throws Exception {
     expectAuth(UPDATE_COORDINATOR, true);
-    jobUpdateController.abort(JOB_KEY, USER);
+    jobUpdateController.abort(UPDATE_KEY, USER);
     expectLastCall().andThrow(new UpdateStateException("failed"));
 
     control.replay();
 
-    assertResponse(INVALID_REQUEST, thrift.abortJobUpdate(JOB_KEY.newBuilder(), SESSION));
+    assertResponse(INVALID_REQUEST, thrift.abortJobUpdate(UPDATE_KEY.newBuilder(), SESSION));
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/java/org/apache/aurora/scheduler/thrift/aop/ForwardingThrift.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/aop/ForwardingThrift.java b/src/test/java/org/apache/aurora/scheduler/thrift/aop/ForwardingThrift.java
index 459f745..eebe01b 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/aop/ForwardingThrift.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/aop/ForwardingThrift.java
@@ -268,20 +268,20 @@ abstract class ForwardingThrift implements AuroraAdmin.Iface {
   }
 
   @Override
-  public Response pauseJobUpdate(JobKey jobKey, SessionKey session) throws TException {
-    return delegate.pauseJobUpdate(jobKey, session);
+  public Response pauseJobUpdate(JobUpdateKey key, SessionKey session) throws TException {
+    return delegate.pauseJobUpdate(key, session);
   }
 
   @Override
-  public Response resumeJobUpdate(JobKey jobKey, SessionKey session)
+  public Response resumeJobUpdate(JobUpdateKey key, SessionKey session)
       throws TException {
 
-    return delegate.resumeJobUpdate(jobKey, session);
+    return delegate.resumeJobUpdate(key, session);
   }
 
   @Override
-  public Response abortJobUpdate(JobKey jobKey, SessionKey session) throws TException {
-    return delegate.abortJobUpdate(jobKey, session);
+  public Response abortJobUpdate(JobUpdateKey key, SessionKey session) throws TException {
+    return delegate.abortJobUpdate(key, session);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachineTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachineTest.java b/src/test/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachineTest.java
index 7ebc4d4..a6a8885 100644
--- a/src/test/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachineTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/updater/JobUpdateStateMachineTest.java
@@ -87,7 +87,7 @@ public class JobUpdateStateMachineTest {
             fail("Transition " + key + " should have been disallowed, but got result " + actual);
           }
           assertEquals("Failed transition " + key, expected, actual);
-        } catch (IllegalStateException e) {
+        } catch (UpdateStateException e) {
           if (expected != null) {
             fail("Expected " + expected + " for transition " + key
                 + " but the transition was disallowed: " + e);

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/java/org/apache/aurora/scheduler/updater/JobUpdaterIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/updater/JobUpdaterIT.java b/src/test/java/org/apache/aurora/scheduler/updater/JobUpdaterIT.java
index e24d6bd..0fe153c 100644
--- a/src/test/java/org/apache/aurora/scheduler/updater/JobUpdaterIT.java
+++ b/src/test/java/org/apache/aurora/scheduler/updater/JobUpdaterIT.java
@@ -356,14 +356,14 @@ public class JobUpdaterIT extends EasyMockTest {
 
     // Updates may be paused for arbitrarily-long amounts of time, and the updater should not
     // take action while paused.
-    updater.pause(JOB, USER);
-    updater.pause(JOB, USER);  // Pausing again is a no-op.
+    updater.pause(UPDATE_ID, USER);
+    updater.pause(UPDATE_ID, USER);  // Pausing again is a no-op.
     assertState(ROLL_FORWARD_PAUSED, actions.build());
     clock.advance(ONE_DAY);
     changeState(JOB, 1, FAILED, ASSIGNED, STARTING, RUNNING);
     changeState(JOB, 2, FAILED, ASSIGNED, STARTING, RUNNING);
     clock.advance(WATCH_TIMEOUT);
-    updater.resume(JOB, USER);
+    updater.resume(UPDATE_ID, USER);
 
     actions.putAll(1, INSTANCE_UPDATING, INSTANCE_UPDATED)
         .put(2, INSTANCE_UPDATING);
@@ -381,6 +381,14 @@ public class JobUpdaterIT extends EasyMockTest {
     assertJobState(
         JOB,
         ImmutableMap.of(0, NEW_CONFIG, 1, NEW_CONFIG, 2, NEW_CONFIG, 100, NEW_CONFIG));
+
+    // Attempting to abort a finished update should fail.
+    try {
+      updater.abort(UPDATE_ID, USER);
+      fail("It should not be possible to abort a completed update.");
+    } catch (UpdateStateException e) {
+      // Expected.
+    }
   }
 
   @Test
@@ -502,11 +510,11 @@ public class JobUpdaterIT extends EasyMockTest {
     assertState(ROLL_FORWARD_AWAITING_PULSE, actions.build());
 
     // Pause the awaiting pulse update.
-    updater.pause(JOB, USER);
+    updater.pause(UPDATE_ID, USER);
     assertState(ROLL_FORWARD_PAUSED, actions.build());
 
     // Resume into awaiting pulse state.
-    updater.resume(JOB, USER);
+    updater.resume(UPDATE_ID, USER);
     assertState(ROLL_FORWARD_AWAITING_PULSE, actions.build());
 
     assertEquals(JobUpdatePulseStatus.OK, updater.pulse(UPDATE_ID));
@@ -562,14 +570,14 @@ public class JobUpdaterIT extends EasyMockTest {
     clock.advance(Amount.of(PULSE_TIMEOUT_MS, Time.MILLISECONDS));
 
     // Update is paused
-    updater.pause(JOB, USER);
+    updater.pause(UPDATE_ID, USER);
     assertState(ROLL_FORWARD_PAUSED, actions.build());
 
     // A paused update is pulsed.
     assertEquals(JobUpdatePulseStatus.OK, updater.pulse(UPDATE_ID));
 
     // Update is resumed
-    updater.resume(JOB, USER);
+    updater.resume(UPDATE_ID, USER);
     actions.putAll(1, INSTANCE_UPDATING, INSTANCE_UPDATED);
     actions.put(2, INSTANCE_UPDATING);
     assertState(ROLLING_FORWARD, actions.build());
@@ -756,10 +764,10 @@ public class JobUpdaterIT extends EasyMockTest {
     clock.advance(WATCH_TIMEOUT);
 
     // A rollback may be paused.
-    updater.pause(JOB, USER);
+    updater.pause(UPDATE_ID, USER);
     assertState(ROLL_BACK_PAUSED, actions.build());
     clock.advance(ONE_DAY);
-    updater.resume(JOB, USER);
+    updater.resume(UPDATE_ID, USER);
     actions.putAll(1, INSTANCE_ROLLING_BACK)
         .putAll(2, INSTANCE_ROLLING_BACK, INSTANCE_ROLLED_BACK);
     assertState(ROLLING_BACK, actions.build());
@@ -845,7 +853,7 @@ public class JobUpdaterIT extends EasyMockTest {
     actions.putAll(0, INSTANCE_UPDATING, INSTANCE_UPDATED)
         .putAll(1, INSTANCE_UPDATING);
 
-    updater.abort(JOB, USER);
+    updater.abort(UPDATE_ID, USER);
     assertState(ABORTED, actions.build());
     clock.advance(WATCH_TIMEOUT);
     assertJobState(JOB, ImmutableMap.of(0, NEW_CONFIG, 1, NEW_CONFIG, 2, OLD_CONFIG));
@@ -1215,7 +1223,7 @@ public class JobUpdaterIT extends EasyMockTest {
   public void testPauseUnknownUpdate() throws Exception {
     control.replay();
 
-    updater.pause(JOB, USER);
+    updater.pause(UPDATE_ID, USER);
   }
 
   @Test
@@ -1236,7 +1244,7 @@ public class JobUpdaterIT extends EasyMockTest {
     actions.putAll(0, INSTANCE_UPDATING);
     assertState(ROLLING_FORWARD, actions.build());
     releaseAllLocks();
-    updater.abort(update.getSummary().getJobKey(), "test");
+    updater.abort(update.getSummary().getKey(), "test");
     clock.advance(WATCH_TIMEOUT);
     assertState(ERROR, actions.build());
   }
@@ -1263,7 +1271,7 @@ public class JobUpdaterIT extends EasyMockTest {
     actions.putAll(0, INSTANCE_UPDATING);
     assertState(ROLLING_FORWARD, actions.build());
 
-    updater.pause(update.getSummary().getJobKey(), "test");
+    updater.pause(update.getSummary().getKey(), "test");
     assertState(ROLL_FORWARD_PAUSED, actions.build());
     clock.advance(WATCH_TIMEOUT);
 
@@ -1285,7 +1293,7 @@ public class JobUpdaterIT extends EasyMockTest {
   public void testResumeUnknownUpdate() throws Exception {
     control.replay();
 
-    updater.resume(JOB, USER);
+    updater.resume(UPDATE_ID, USER);
   }
 
   private static IJobUpdateSummary makeUpdateSummary() {

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/python/apache/aurora/client/api/test_api.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/api/test_api.py b/src/test/python/apache/aurora/client/api/test_api.py
index 0d552e8..d211fb9 100644
--- a/src/test/python/apache/aurora/client/api/test_api.py
+++ b/src/test/python/apache/aurora/client/api/test_api.py
@@ -53,6 +53,7 @@ class TestJobUpdateApis(unittest.TestCase):
   }
 
   JOB_KEY = AuroraJobKey("foo", "role", "env", "name")
+  UPDATE_KEY = JobUpdateKey(job=JOB_KEY.to_thrift(), id="id")
 
   @classmethod
   def create_blank_response(cls, code, msg):
@@ -136,39 +137,16 @@ class TestJobUpdateApis(unittest.TestCase):
     api, mock_proxy = self.mock_api()
     mock_proxy.pauseJobUpdate.return_value = self.create_simple_success_response()
 
-    api.pause_job_update(self.JOB_KEY)
-    mock_proxy.pauseJobUpdate.assert_called_once_with(self.JOB_KEY.to_thrift())
-
-  def test_pause_job_update_invalid_key(self):
-    """Test job update pause with invalid job key."""
-    api, mock_proxy = self.mock_api()
-    self.assertRaises(AuroraClientAPI.TypeError, api.pause_job_update, "invalid")
+    api.pause_job_update(self.UPDATE_KEY)
+    mock_proxy.pauseJobUpdate.assert_called_once_with(self.UPDATE_KEY)
 
   def test_resume_job_update(self):
     """Test successful job update resume."""
     api, mock_proxy = self.mock_api()
     mock_proxy.resumeJobUpdate.return_value = self.create_simple_success_response()
 
-    api.resume_job_update(self.JOB_KEY)
-    mock_proxy.resumeJobUpdate.assert_called_once_with(self.JOB_KEY.to_thrift())
-
-  def test_resume_job_update_invalid_key(self):
-    """Test job update resume with invalid job key."""
-    api, mock_proxy = self.mock_api()
-    self.assertRaises(AuroraClientAPI.TypeError, api.resume_job_update, "invalid")
-
-  def test_abort_job_update(self):
-    """Test successful job update abort."""
-    api, mock_proxy = self.mock_api()
-    mock_proxy.abortJobUpdate.return_value = self.create_simple_success_response()
-
-    api.abort_job_update(self.JOB_KEY)
-    mock_proxy.abortJobUpdate.assert_called_once_with(self.JOB_KEY.to_thrift())
-
-  def test_abort_job_update_invalid_key(self):
-    """Test job update abort with invalid job key."""
-    api, mock_proxy = self.mock_api()
-    self.assertRaises(AuroraClientAPI.TypeError, api.abort_job_update, "invalid")
+    api.resume_job_update(self.UPDATE_KEY)
+    mock_proxy.resumeJobUpdate.assert_called_once_with(self.UPDATE_KEY)
 
   def test_query_job_updates(self):
     """Test querying job updates."""

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/python/apache/aurora/client/cli/test_create.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_create.py b/src/test/python/apache/aurora/client/cli/test_create.py
index a65aab7..459d157 100644
--- a/src/test/python/apache/aurora/client/cli/test_create.py
+++ b/src/test/python/apache/aurora/client/cli/test_create.py
@@ -355,7 +355,7 @@ class TestClientCreateCommand(AuroraClientCommandTest):
       # Check that create_job was called exactly once, with an AuroraConfig parameter.
       assert mock_context.get_out() == []
       assert mock_context.get_err() == [
-        'Job creation failed due to error:', '\tDamn']
+        'Job creation failed due to error:', '\tWhoops']
 
   def test_simple_successful_create_job_with_bindings(self):
     """Run a test of the "create" command against a mocked-out API:

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/python/apache/aurora/client/cli/test_quota.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_quota.py b/src/test/python/apache/aurora/client/cli/test_quota.py
index c8d217d..b97e5c4 100644
--- a/src/test/python/apache/aurora/client/cli/test_quota.py
+++ b/src/test/python/apache/aurora/client/cli/test_quota.py
@@ -77,7 +77,7 @@ class TestGetQuotaCommand(AuroraClientCommandTest):
 
     self._call_get_quota(fake_context, ['quota', 'get', 'west/bozo'])
 
-    assert fake_context.get_err() == ['Error retrieving quota for role bozo', '\tDamn']
+    assert fake_context.get_err() == ['Error retrieving quota for role bozo', '\tWhoops']
 
   def _get_quota(self, include_consumption, command_args):
     mock_context = FakeAuroraCommandContext()

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/python/apache/aurora/client/cli/test_restart.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_restart.py b/src/test/python/apache/aurora/client/cli/test_restart.py
index b596bab..4f20f6b 100644
--- a/src/test/python/apache/aurora/client/cli/test_restart.py
+++ b/src/test/python/apache/aurora/client/cli/test_restart.py
@@ -233,8 +233,7 @@ class TestRestartCommand(AuroraClientCommandTest):
         assert result == EXIT_API_ERROR
         # Error message should be written to log, and it should be what was returned
         # by the getTasksWithoutConfigs call.
-        assert mock_io.get() == ["Error restarting job west/bozo/test/hello:",
-                                 "\tDamn"]
+        assert mock_io.get() == ["Error restarting job west/bozo/test/hello:", "\tWhoops"]
 
   def test_restart_failed_restart(self):
     (mock_api, mock_scheduler_proxy) = self.create_mock_api()

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/python/apache/aurora/client/cli/test_supdate.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_supdate.py b/src/test/python/apache/aurora/client/cli/test_supdate.py
index 93a5532..1806769 100644
--- a/src/test/python/apache/aurora/client/cli/test_supdate.py
+++ b/src/test/python/apache/aurora/client/cli/test_supdate.py
@@ -55,12 +55,14 @@ from gen.apache.aurora.api.ttypes import (
     StartJobUpdateResult
 )
 
+UPDATE_KEY = JobUpdateKey(job=AuroraClientCommandTest.TEST_JOBKEY.to_thrift(), id="update_id")
+
 
 class TestStartUpdateCommand(AuroraClientCommandTest):
 
   def setUp(self):
     self._command = StartUpdate()
-    self._job_key = AuroraJobKey("cluster", "role", "env", "job")
+    self._job_key = AuroraJobKey.from_thrift("cluster", UPDATE_KEY.job)
     self._mock_options = mock_verb_options(self._command)
     self._mock_options.instance_spec = TaskInstanceKey(self._job_key, [])
     self._fake_context = FakeAuroraCommandContext()
@@ -142,7 +144,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
     patcher = patch("time.ctime")
     self.addCleanup(patcher.stop)
     self.mock_ctime = patcher.start()
-    self.mock_ctime.return_value = TestUpdateCommand.CTIME
+    self.mock_ctime.return_value = self.CTIME
 
   def test_start_update_command_line_succeeds(self):
     mock_context = FakeAuroraCommandContext()
@@ -200,6 +202,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
       mock_api = mock_context.get_api(self.TEST_CLUSTER)
+      mock_api.query_job_updates.return_value = self.get_status_query_response()
       mock_api.pause_job_update.return_value = self.create_simple_success_response()
       with temporary_file() as fp:
         fp.write(self.get_valid_config())
@@ -208,7 +211,9 @@ class TestUpdateCommand(AuroraClientCommandTest):
         result = cmd.execute(['beta-update', 'pause', self.TEST_JOBSPEC])
         assert result == EXIT_OK
 
-      assert mock_api.pause_job_update.mock_calls == [call(self.TEST_JOBKEY)]
+      assert mock_api.query_job_updates.mock_calls == [
+        call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)]
+      assert mock_api.pause_job_update.mock_calls == [call(UPDATE_KEY)]
       assert mock_context.get_out() == ["Update has been paused."]
       assert mock_context.get_err() == []
 
@@ -218,6 +223,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
       mock_api = mock_context.get_api(self.TEST_CLUSTER)
+      mock_api.query_job_updates.return_value = self.get_status_query_response()
       mock_api.abort_job_update.return_value = self.create_simple_success_response()
       with temporary_file() as fp:
         fp.write(self.get_valid_config())
@@ -226,7 +232,9 @@ class TestUpdateCommand(AuroraClientCommandTest):
         result = cmd.execute(['beta-update', 'abort', self.TEST_JOBSPEC])
         assert result == EXIT_OK
 
-      assert mock_api.abort_job_update.mock_calls == [call(self.TEST_JOBKEY)]
+      assert mock_api.query_job_updates.mock_calls == [
+        call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)]
+      assert mock_api.abort_job_update.mock_calls == [call(UPDATE_KEY)]
       assert mock_context.get_out() == ["Update has been aborted."]
       assert mock_context.get_err() == []
 
@@ -236,6 +244,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
       mock_api = mock_context.get_api(self.TEST_CLUSTER)
+      mock_api.query_job_updates.return_value = self.get_status_query_response()
       mock_api.resume_job_update.return_value = self.create_simple_success_response()
       with temporary_file() as fp:
         fp.write(self.get_valid_config())
@@ -244,7 +253,9 @@ class TestUpdateCommand(AuroraClientCommandTest):
         result = cmd.execute(['beta-update', 'resume', self.TEST_JOBSPEC])
         assert result == EXIT_OK
 
-      assert mock_api.resume_job_update.mock_calls == [call(self.TEST_JOBKEY)]
+      assert mock_api.query_job_updates.mock_calls == [
+        call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)]
+      assert mock_api.resume_job_update.mock_calls == [call(UPDATE_KEY)]
       assert mock_context.get_out() == ["Update has been resumed."]
 
   def test_update_invalid_config(self):
@@ -267,6 +278,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
       mock_api = mock_context.get_api(self.TEST_CLUSTER)
+      mock_api.query_job_updates.return_value = self.get_status_query_response()
       mock_api.resume_job_update.return_value = self.create_error_response()
       with temporary_file() as fp:
         fp.write(self.get_valid_config())
@@ -275,9 +287,11 @@ class TestUpdateCommand(AuroraClientCommandTest):
         result = cmd.execute(['beta-update', 'resume', self.TEST_JOBSPEC])
         assert result == EXIT_API_ERROR
 
-      assert mock_api.resume_job_update.mock_calls == [call(self.TEST_JOBKEY)]
+      assert mock_api.query_job_updates.mock_calls == [
+        call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)]
+      assert mock_api.resume_job_update.mock_calls == [call(UPDATE_KEY)]
       assert mock_context.get_out() == []
-      assert mock_context.get_err() == ["Failed to resume update due to error:", "\tDamn"]
+      assert mock_context.get_err() == ["Failed to resume update due to error:", "\tWhoops"]
 
   def test_abort_update_command_line_error(self):
     mock_context = FakeAuroraCommandContext()
@@ -285,6 +299,30 @@ class TestUpdateCommand(AuroraClientCommandTest):
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
       mock_api = mock_context.get_api(self.TEST_CLUSTER)
+      mock_api.query_job_updates.return_value = self.get_status_query_response()
+      mock_api.abort_job_update.return_value = self.create_error_response()
+      with temporary_file() as fp:
+        fp.write(self.get_valid_config())
+        fp.flush()
+        cmd = AuroraCommandLine()
+        result = cmd.execute(['beta-update', 'abort', self.TEST_JOBSPEC])
+        assert result == EXIT_API_ERROR
+
+      assert mock_api.query_job_updates.mock_calls == [
+        call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)]
+      assert mock_api.abort_job_update.mock_calls == [call(UPDATE_KEY)]
+      assert mock_context.get_out() == []
+      assert mock_context.get_err() == ["Failed to abort update due to error:", "\tWhoops"]
+
+  def test_abort_invalid_api_response(self):
+    mock_context = FakeAuroraCommandContext()
+    with contextlib.nested(
+        patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
+        patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
+      mock_api = mock_context.get_api(self.TEST_CLUSTER)
+
+      # Mimic the API returning two active updates for one job, which should be impossible.
+      mock_api.query_job_updates.return_value = self.get_status_query_response(count=2)
       mock_api.abort_job_update.return_value = self.create_error_response()
       with temporary_file() as fp:
         fp.write(self.get_valid_config())
@@ -293,9 +331,11 @@ class TestUpdateCommand(AuroraClientCommandTest):
         result = cmd.execute(['beta-update', 'abort', self.TEST_JOBSPEC])
         assert result == EXIT_API_ERROR
 
-      assert mock_api.abort_job_update.mock_calls == [call(self.TEST_JOBKEY)]
+      assert mock_api.query_job_updates.mock_calls == [
+        call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)]
+      assert mock_api.abort_job_update.mock_calls == []
       assert mock_context.get_out() == []
-      assert mock_context.get_err() == ["Failed to abort update due to error:", "\tDamn"]
+      assert mock_context.get_err() == []
 
   def test_pause_update_command_line_error(self):
     mock_context = FakeAuroraCommandContext()
@@ -303,6 +343,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
       mock_api = mock_context.get_api(self.TEST_CLUSTER)
+      mock_api.query_job_updates.return_value = self.get_status_query_response()
       mock_api.pause_job_update.return_value = self.create_error_response()
       with temporary_file() as fp:
         fp.write(self.get_valid_config())
@@ -311,18 +352,20 @@ class TestUpdateCommand(AuroraClientCommandTest):
         result = cmd.execute(['beta-update', 'pause', self.TEST_JOBSPEC])
         assert result == EXIT_API_ERROR
 
-      assert mock_api.pause_job_update.mock_calls == [call(self.TEST_JOBKEY)]
+      assert mock_api.query_job_updates.mock_calls == [
+        call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)]
+      assert mock_api.pause_job_update.mock_calls == [call(UPDATE_KEY)]
       assert mock_context.get_out() == []
-      assert mock_context.get_err() == ["Failed to pause update due to error:", "\tDamn"]
+      assert mock_context.get_err() == ["Failed to pause update due to error:", "\tWhoops"]
 
-  def get_status_query_response(self, count=3):
+  def get_status_query_response(self, count=1):
     return Response(
         responseCode=ResponseCode.OK,
         result=Result(
             getJobUpdateSummariesResult=GetJobUpdateSummariesResult(
                 updateSummaries=[
                     JobUpdateSummary(
-                        key=JobUpdateKey(job=self.TEST_JOBKEY.to_thrift(), id="%s" % i),
+                        key=UPDATE_KEY,
                         user="me",
                         state=JobUpdateState(
                             status=JobUpdateStatus.ROLLED_FORWARD,
@@ -335,7 +378,8 @@ class TestUpdateCommand(AuroraClientCommandTest):
 
   def test_list_updates_command(self):
     mock_context = FakeAuroraCommandContext()
-    mock_context.get_api('west').query_job_updates.return_value = self.get_status_query_response()
+    mock_context.get_api('west').query_job_updates.return_value = (
+        self.get_status_query_response(count=3))
     with contextlib.nested(
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
@@ -343,16 +387,17 @@ class TestUpdateCommand(AuroraClientCommandTest):
       result = cmd.execute(["beta-update", "list", self.TEST_CLUSTER, "--user=me"])
       assert result == EXIT_OK
       assert mock_context.get_out_str() == textwrap.dedent("""\
-          Job: west/bozo/test/hello, Id: 0, User: me, Status: ROLLED_FORWARD
+          Job: west/bozo/test/hello, Id: update_id, User: me, Status: ROLLED_FORWARD
             Created: 1411404927, Last Modified 14114056030
-          Job: west/bozo/test/hello, Id: 1, User: me, Status: ROLLED_FORWARD
+          Job: west/bozo/test/hello, Id: update_id, User: me, Status: ROLLED_FORWARD
             Created: 1411404927, Last Modified 14114056030
-          Job: west/bozo/test/hello, Id: 2, User: me, Status: ROLLED_FORWARD
+          Job: west/bozo/test/hello, Id: update_id, User: me, Status: ROLLED_FORWARD
             Created: 1411404927, Last Modified 14114056030""")
 
   def test_list_updates_command_json(self):
     mock_context = FakeAuroraCommandContext()
-    mock_context.get_api('west').query_job_updates.return_value = self.get_status_query_response()
+    mock_context.get_api('west').query_job_updates.return_value = (
+        self.get_status_query_response(count=3))
     with contextlib.nested(
         patch('apache.aurora.client.cli.update.Update.create_context', return_value=mock_context),
         patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
@@ -368,7 +413,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
               "lastModified": 14114056030,
               "user": "me",
               "jobkey": "west/bozo/test/hello",
-              "id": "0"
+              "id": "update_id"
           },
           {
               "status": "ROLLED_FORWARD",
@@ -376,7 +421,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
               "lastModified": 14114056030,
               "user": "me",
               "jobkey": "west/bozo/test/hello",
-              "id": "1"
+              "id": "update_id"
           },
           {
               "status": "ROLLED_FORWARD",
@@ -384,7 +429,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
               "lastModified": 14114056030,
               "user": "me",
               "jobkey": "west/bozo/test/hello",
-              "id": "2"
+              "id": "update_id"
           }
       ]
 
@@ -395,7 +440,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
     details = JobUpdateDetails(
         update=JobUpdate(
             summary=JobUpdateSummary(
-                key=JobUpdateKey(self.TEST_JOBKEY.to_thrift(), "0"),
+                key=UPDATE_KEY,
                 user="me",
                 state=JobUpdateState(
                   status=JobUpdateStatus.ROLLING_FORWARD,
@@ -444,7 +489,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
       result = cmd.execute(["beta-update", "status", self.TEST_JOBSPEC])
       assert result == EXIT_OK
       assert ('\n'.join(mock_context.get_out()) ==
-          """Job: west/bozo/test/hello, UpdateID: 0
+          """Job: west/bozo/test/hello, UpdateID: update_id
 Started %(ctime)s, last activity: %(ctime)s
 Current status: ROLLING_FORWARD
 Update events:
@@ -455,7 +500,7 @@ Instance events:
   Instance 1 at %(ctime)s: INSTANCE_UPDATING
   Instance 2 at %(ctime)s: INSTANCE_UPDATING
   Instance 1 at %(ctime)s: INSTANCE_UPDATED
-  Instance 2 at %(ctime)s: INSTANCE_UPDATED""" % {'ctime': TestUpdateCommand.CTIME})
+  Instance 2 at %(ctime)s: INSTANCE_UPDATED""" % {'ctime': self.CTIME})
       assert self.mock_ctime.mock_calls == [call(n) for n in range(1, 10)]
       assert mock_context.get_api(self.TEST_CLUSTER).query_job_updates.mock_calls == [
           call(update_statuses=ACTIVE_JOB_UPDATE_STATES, job_key=self.TEST_JOBKEY)
@@ -499,7 +544,7 @@ Instance events:
               }
           ],
           "job": "west/bozo/test/hello",
-          "updateId": "0",
+          "updateId": "update_id",
           "instance_update_events": [
               {
                   "action": "INSTANCE_UPDATING",

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fdc11e3b/src/test/python/apache/aurora/client/cli/util.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/util.py b/src/test/python/apache/aurora/client/cli/util.py
index 6d3cc51..b65970a 100644
--- a/src/test/python/apache/aurora/client/cli/util.py
+++ b/src/test/python/apache/aurora/client/cli/util.py
@@ -144,7 +144,7 @@ class AuroraClientCommandTest(unittest.TestCase):
 
   @classmethod
   def create_error_response(cls):
-    return cls.create_blank_response(ResponseCode.ERROR, 'Damn')
+    return cls.create_blank_response(ResponseCode.ERROR, 'Whoops')
 
   @classmethod
   def create_mock_api(cls):


Mime
View raw message