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: Update thrift API and internal code to use JobUpdateSummary.key rather than job key and id.
Date Fri, 27 Feb 2015 22:00:52 GMT
Repository: incubator-aurora
Updated Branches:
  refs/heads/master 938f47471 -> b436ec521


Update thrift API and internal code to use JobUpdateSummary.key rather than job key and id.

Bugs closed: AURORA-1093

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


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

Branch: refs/heads/master
Commit: b436ec52101cfd5d6f65075a5f7a857ed5a41b5f
Parents: 938f474
Author: Bill Farner <wfarner@apache.org>
Authored: Fri Feb 27 13:50:15 2015 -0800
Committer: Bill Farner <wfarner@apache.org>
Committed: Fri Feb 27 13:50:15 2015 -0800

----------------------------------------------------------------------
 .../thrift/org/apache/aurora/gen/api.thrift     |  11 +-
 .../aurora/scheduler/quota/QuotaManager.java    |   8 +-
 .../scheduler/storage/ForwardingStore.java      |  20 +--
 .../scheduler/storage/JobUpdateStore.java       |  22 +--
 .../scheduler/storage/db/DBJobUpdateStore.java  |  90 +++++--------
 .../storage/log/SnapshotStoreImpl.java          |   5 +-
 .../scheduler/thrift/ReadOnlySchedulerImpl.java |  10 +-
 .../thrift/SchedulerThriftInterface.java        |   8 +-
 .../updater/JobUpdateControllerImpl.java        |  43 +++---
 .../aurora/scheduler/updater/Updates.java       |  11 --
 .../python/apache/aurora/client/api/__init__.py |  13 +-
 .../python/apache/aurora/client/cli/context.py  |   6 +-
 .../python/apache/aurora/client/cli/update.py   |  45 ++++---
 .../storage/db/JobUpdateDetailsMapper.xml       |   3 +-
 .../aurora/scheduler/storage/db/schema.sql      |   2 +-
 .../resources/scheduler/assets/breadcrumb.html  |   2 +-
 src/main/resources/scheduler/assets/job.html    |   5 +-
 .../scheduler/assets/js/controllers.js          |  13 +-
 .../resources/scheduler/assets/js/services.js   |   4 +-
 .../scheduler/assets/latestUpdates.html         |   4 +-
 .../scheduler/quota/QuotaManagerImplTest.java   |  44 +++---
 .../storage/db/DBJobUpdateStoreTest.java        |  47 ++++---
 .../thrift/ReadOnlySchedulerImplTest.java       |  11 +-
 .../thrift/SchedulerThriftInterfaceTest.java    |   9 +-
 .../scheduler/thrift/aop/ForwardingThrift.java  |   4 +-
 .../aurora/scheduler/updater/JobUpdaterIT.java  |   2 +-
 .../python/apache/aurora/client/api/test_api.py |   7 +-
 .../apache/aurora/client/cli/test_status.py     |  16 +--
 .../apache/aurora/client/cli/test_supdate.py    | 135 ++++++++++---------
 29 files changed, 294 insertions(+), 306 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 6a00ce2..a5198df 100644
--- a/api/src/main/thrift/org/apache/aurora/gen/api.thrift
+++ b/api/src/main/thrift/org/apache/aurora/gen/api.thrift
@@ -783,9 +783,6 @@ struct JobUpdateRequest {
  * (terms are AND'ed together).
  */
 struct JobUpdateQuery {
-  /** Update ID. */
-  1: string updateId
-
   /** Job role. */
   2: string role
 
@@ -854,8 +851,8 @@ struct GetPendingReasonResult {
 
 /** Result of the startUpdate call. */
 struct StartJobUpdateResult {
-  /** Job update ID. */
-  1: string updateId
+  /** Unique identifier for the job update. */
+  1: JobUpdateKey key
 }
 
 /** Result of the getJobUpdateSummaries call. */
@@ -966,7 +963,7 @@ service ReadOnlyScheduler {
   Response getJobUpdateSummaries(1: JobUpdateQuery jobUpdateQuery)
 
   /** Gets job update details. Not implemented yet. */
-  Response getJobUpdateDetails(1: string updateId)
+  Response getJobUpdateDetails(1: JobUpdateKey key)
 }
 
 // Due to assumptions in the client all authenticated RPCs must have a SessionKey as their
@@ -1047,7 +1044,7 @@ service AuroraSchedulerManager extends ReadOnlyScheduler {
   /**
    * Allows progress of the job update in case blockIfNoPulsesAfterMs is specified in
    * JobUpdateSettings. Unblocks progress if the update was previously blocked.
-   * Responds with ResponseCode.INVALID_REQUEST in case an unknown updateId is specified.
+   * Responds with ResponseCode.INVALID_REQUEST in case an unknown update key is specified.
    */
   Response pulseJobUpdate(1: JobUpdateKey key, 2: SessionKey session)
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/java/org/apache/aurora/scheduler/quota/QuotaManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/quota/QuotaManager.java b/src/main/java/org/apache/aurora/scheduler/quota/QuotaManager.java
index 8444d22..8955df1 100644
--- a/src/main/java/org/apache/aurora/scheduler/quota/QuotaManager.java
+++ b/src/main/java/org/apache/aurora/scheduler/quota/QuotaManager.java
@@ -174,7 +174,7 @@ public interface QuotaManager {
       }
 
       QuotaInfo quotaInfo = getQuotaInfo(
-          jobUpdate.getSummary().getJobKey().getRole(),
+          jobUpdate.getSummary().getKey().getJob().getRole(),
           Optional.of(jobUpdate),
           storeProvider);
 
@@ -208,7 +208,7 @@ public interface QuotaManager {
       // This would be an update that is not saved in the store yet (i.e. the one quota is
       // checked for).
       if (requestedUpdate.isPresent()) {
-        updates.put(requestedUpdate.get().getSummary().getJobKey(), requestedUpdate.get());
+        updates.put(requestedUpdate.get().getSummary().getKey().getJob(), requestedUpdate.get());
       }
 
       Map<IJobKey, IJobConfiguration> cronTemplates =
@@ -351,7 +351,7 @@ public interface QuotaManager {
 
       Set<IJobUpdate> updates = Sets.newHashSet();
       for (IJobUpdateSummary summary : summaries) {
-        updates.add(jobUpdateStore.fetchJobUpdate(summary.getUpdateId()).get());
+        updates.add(jobUpdateStore.fetchJobUpdate(summary.getKey()).get());
       }
 
       return FluentIterable.from(updates);
@@ -459,7 +459,7 @@ public interface QuotaManager {
         new Function<IJobUpdate, IJobKey>() {
           @Override
           public IJobKey apply(IJobUpdate input) {
-            return input.getSummary().getJobKey();
+            return input.getSummary().getKey().getJob();
           }
         };
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/java/org/apache/aurora/scheduler/storage/ForwardingStore.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/ForwardingStore.java b/src/main/java/org/apache/aurora/scheduler/storage/ForwardingStore.java
index 6ade817..a8e3b14 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/ForwardingStore.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/ForwardingStore.java
@@ -150,8 +150,8 @@ public class ForwardingStore implements
   }
 
   @Override
-  public Optional<IJobUpdateDetails> fetchJobUpdateDetails(String updateId) {
-    return jobUpdateStore.fetchJobUpdateDetails(updateId);
+  public Optional<IJobUpdateDetails> fetchJobUpdateDetails(IJobUpdateKey key) {
+    return jobUpdateStore.fetchJobUpdateDetails(key);
   }
 
   @Override
@@ -160,13 +160,13 @@ public class ForwardingStore implements
   }
 
   @Override
-  public Optional<IJobUpdate> fetchJobUpdate(String updateId) {
-    return jobUpdateStore.fetchJobUpdate(updateId);
+  public Optional<IJobUpdate> fetchJobUpdate(IJobUpdateKey key) {
+    return jobUpdateStore.fetchJobUpdate(key);
   }
 
   @Override
-  public Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(String updateId) {
-    return jobUpdateStore.fetchJobUpdateInstructions(updateId);
+  public Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(IJobUpdateKey key) {
+    return jobUpdateStore.fetchJobUpdateInstructions(key);
   }
 
   @Override
@@ -175,12 +175,12 @@ public class ForwardingStore implements
   }
 
   @Override
-  public Optional<String> getLockToken(String updateId) {
-    return jobUpdateStore.getLockToken(updateId);
+  public Optional<String> getLockToken(IJobUpdateKey key) {
+    return jobUpdateStore.getLockToken(key);
   }
 
   @Override
-  public List<IJobInstanceUpdateEvent> fetchInstanceEvents(String updateId, int instanceId) {
-    return jobUpdateStore.fetchInstanceEvents(updateId, instanceId);
+  public List<IJobInstanceUpdateEvent> fetchInstanceEvents(IJobUpdateKey key, int instanceId) {
+    return jobUpdateStore.fetchInstanceEvents(key, instanceId);
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/java/org/apache/aurora/scheduler/storage/JobUpdateStore.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/JobUpdateStore.java b/src/main/java/org/apache/aurora/scheduler/storage/JobUpdateStore.java
index 4b8b00c..159cb0c 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/JobUpdateStore.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/JobUpdateStore.java
@@ -52,26 +52,26 @@ public interface JobUpdateStore {
   /**
    * Fetches a read-only view of job update details.
    *
-   * @param updateId Update ID to fetch details for.
+   * @param key Update identifier.
    * @return A read-only view of job update details.
    */
-  Optional<IJobUpdateDetails> fetchJobUpdateDetails(String updateId);
+  Optional<IJobUpdateDetails> fetchJobUpdateDetails(IJobUpdateKey key);
 
   /**
    * Fetches a read-only view of a job update.
    *
-   * @param updateId Update ID to fetch.
+   * @param key Update identifier.
    * @return A read-only view of job update.
    */
-  Optional<IJobUpdate> fetchJobUpdate(String updateId);
+  Optional<IJobUpdate> fetchJobUpdate(IJobUpdateKey key);
 
   /**
    * Fetches a read-only view of the instructions for a job update.
    *
-   * @param updateId Update ID to fetch instructions for.
+   * @param key Update identifier.
    * @return A read-only view of job update instructions.
    */
-  Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(String updateId);
+  Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(IJobUpdateKey key);
 
   /**
    * Fetches a read-only view of all job update details available in the store.
@@ -97,19 +97,19 @@ public interface JobUpdateStore {
   /**
    * Gets the lock token associated with a job update.
    *
-   * @param updateId Job update ID.
+   * @param key Update identifier.
    * @return the token associated with the update id, if it exists.
    */
-  Optional<String> getLockToken(String updateId);
+  Optional<String> getLockToken(IJobUpdateKey key);
 
   /**
    * Fetches the events that have affected an instance within a job update.
    *
-   * @param updateId Update to fetch events from.
+   * @param key Update identifier.
    * @param instanceId Instance to fetch events for.
-   * @return Instance events in {@code updateId} that affected {@code instanceId}.
+   * @return Instance events in {@code key} that affected {@code instanceId}.
    */
-  List<IJobInstanceUpdateEvent> fetchInstanceEvents(String updateId, int instanceId);
+  List<IJobInstanceUpdateEvent> fetchInstanceEvents(IJobUpdateKey key, int instanceId);
 
   interface Mutable extends JobUpdateStore {
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java b/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
index d52c15e..94ce5c3 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStore.java
@@ -21,7 +21,6 @@ import javax.inject.Inject;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.twitter.common.base.MorePreconditions;
 
@@ -200,53 +199,38 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
 
   @Timed("job_update_store_fetch_details")
   @Override
-  public Optional<IJobUpdateDetails> fetchJobUpdateDetails(final String updateId) {
-    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
-    if (key.isPresent()) {
-      return Optional.fromNullable(detailsMapper.selectDetails(key.get()))
-          .transform(new Function<StoredJobUpdateDetails, IJobUpdateDetails>() {
-            @Override
-            public IJobUpdateDetails apply(StoredJobUpdateDetails input) {
-              return IJobUpdateDetails.build(input.getDetails());
-            }
-          });
-    } else {
-      return Optional.absent();
-    }
+  public Optional<IJobUpdateDetails> fetchJobUpdateDetails(final IJobUpdateKey key) {
+    return Optional.fromNullable(detailsMapper.selectDetails(key))
+        .transform(new Function<StoredJobUpdateDetails, IJobUpdateDetails>() {
+          @Override
+          public IJobUpdateDetails apply(StoredJobUpdateDetails input) {
+            return IJobUpdateDetails.build(input.getDetails());
+          }
+        });
   }
 
   @Timed("job_update_store_fetch_update")
   @Override
-  public Optional<IJobUpdate> fetchJobUpdate(String updateId) {
-    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
-    if (key.isPresent()) {
-      return Optional.fromNullable(detailsMapper.selectUpdate(key.get()))
-          .transform(new Function<JobUpdate, IJobUpdate>() {
-            @Override
-            public IJobUpdate apply(JobUpdate input) {
-              return IJobUpdate.build(input);
-            }
-          });
-    } else {
-      return Optional.absent();
-    }
+  public Optional<IJobUpdate> fetchJobUpdate(IJobUpdateKey key) {
+    return Optional.fromNullable(detailsMapper.selectUpdate(key))
+        .transform(new Function<JobUpdate, IJobUpdate>() {
+          @Override
+          public IJobUpdate apply(JobUpdate input) {
+            return IJobUpdate.build(input);
+          }
+        });
   }
 
   @Timed("job_update_store_fetch_instructions")
   @Override
-  public Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(String updateId) {
-    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
-    if (key.isPresent()) {
-      return Optional.fromNullable(detailsMapper.selectInstructions(key.get()))
-          .transform(new Function<JobUpdateInstructions, IJobUpdateInstructions>() {
-            @Override
-            public IJobUpdateInstructions apply(JobUpdateInstructions input) {
-              return IJobUpdateInstructions.build(input);
-            }
-          });
-    } else {
-      return Optional.absent();
-    }
+  public Optional<IJobUpdateInstructions> fetchJobUpdateInstructions(IJobUpdateKey key) {
+    return Optional.fromNullable(detailsMapper.selectInstructions(key))
+        .transform(new Function<JobUpdateInstructions, IJobUpdateInstructions>() {
+          @Override
+          public IJobUpdateInstructions apply(JobUpdateInstructions input) {
+            return IJobUpdateInstructions.build(input);
+          }
+        });
   }
 
   @Timed("job_update_store_fetch_all_details")
@@ -264,27 +248,17 @@ public class DBJobUpdateStore implements JobUpdateStore.Mutable {
 
   @Timed("job_update_store_get_lock_token")
   @Override
-  public Optional<String> getLockToken(String updateId) {
-    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
-    if (key.isPresent()) {
-      // We assume here that cascading deletes will cause a lock-update associative row to disappear
-      // when the lock is invalidated.  This further assumes that a lock row is deleted when a lock
-      // is no longer valid.
-      return Optional.fromNullable(detailsMapper.selectLockToken(key.get()));
-    } else {
-      return Optional.absent();
-    }
+  public Optional<String> getLockToken(IJobUpdateKey key) {
+    // We assume here that cascading deletes will cause a lock-update associative row to disappear
+    // when the lock is invalidated.  This further assumes that a lock row is deleted when a lock
+    // is no longer valid.
+    return Optional.fromNullable(detailsMapper.selectLockToken(key));
   }
 
   @Timed("job_update_store_fetch_instance_events")
   @Override
-  public List<IJobInstanceUpdateEvent> fetchInstanceEvents(String updateId, int instanceId) {
-    Optional<IJobUpdateKey> key = fetchUpdateKey(updateId);
-    if (key.isPresent()) {
-      return IJobInstanceUpdateEvent.listFromBuilders(
-          detailsMapper.selectInstanceUpdateEvents(key.get(), instanceId));
-    } else {
-      return ImmutableList.of();
-    }
+  public List<IJobInstanceUpdateEvent> fetchInstanceEvents(IJobUpdateKey key, int instanceId) {
+    return IJobInstanceUpdateEvent.listFromBuilders(
+        detailsMapper.selectInstanceUpdateEvents(key, instanceId));
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImpl.java b/src/main/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImpl.java
index 06de538..c5e2323 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImpl.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImpl.java
@@ -49,6 +49,7 @@ import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
 import org.apache.aurora.scheduler.storage.entities.IJobInstanceUpdateEvent;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdate;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateEvent;
+import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateSummary;
 import org.apache.aurora.scheduler.storage.entities.ILock;
 import org.apache.aurora.scheduler.storage.entities.IResourceAggregate;
@@ -229,7 +230,7 @@ public class SnapshotStoreImpl implements SnapshotStore<Snapshot> {
               if (details.getUpdateEventsSize() > 0) {
                 for (JobUpdateEvent updateEvent : details.getUpdateEvents()) {
                   updateStore.saveJobUpdateEvent(
-                      Updates.getKey(IJobUpdateSummary.build(details.getUpdate().getSummary())),
+                      IJobUpdateKey.build(details.getUpdate().getSummary().getKey()),
                       IJobUpdateEvent.build(updateEvent));
                 }
               }
@@ -237,7 +238,7 @@ public class SnapshotStoreImpl implements SnapshotStore<Snapshot> {
               if (details.getInstanceEventsSize() > 0) {
                 for (JobInstanceUpdateEvent instanceEvent : details.getInstanceEvents()) {
                   updateStore.saveJobInstanceUpdateEvent(
-                      Updates.getKey(IJobUpdateSummary.build(details.getUpdate().getSummary())),
+                      IJobUpdateKey.build(details.getUpdate().getSummary().getKey()),
                       IJobInstanceUpdateEvent.build(instanceEvent));
                 }
               }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImpl.java b/src/main/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImpl.java
index b9dce16..53582c6 100644
--- a/src/main/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImpl.java
+++ b/src/main/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImpl.java
@@ -52,6 +52,7 @@ import org.apache.aurora.gen.JobConfiguration;
 import org.apache.aurora.gen.JobKey;
 import org.apache.aurora.gen.JobSummary;
 import org.apache.aurora.gen.JobSummaryResult;
+import org.apache.aurora.gen.JobUpdateKey;
 import org.apache.aurora.gen.JobUpdateQuery;
 import org.apache.aurora.gen.PendingReason;
 import org.apache.aurora.gen.PopulateJobResult;
@@ -85,6 +86,7 @@ import org.apache.aurora.scheduler.storage.entities.IAssignedTask;
 import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
 import org.apache.aurora.scheduler.storage.entities.IJobKey;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateDetails;
+import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateQuery;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateSummary;
 import org.apache.aurora.scheduler.storage.entities.ILock;
@@ -322,13 +324,13 @@ class ReadOnlySchedulerImpl implements ReadOnlyScheduler.Iface {
   }
 
   @Override
-  public Response getJobUpdateDetails(final String updateId) {
-    requireNonNull(updateId);
+  public Response getJobUpdateDetails(JobUpdateKey mutableKey) {
+    final IJobUpdateKey key = IJobUpdateKey.build(mutableKey);
     Optional<IJobUpdateDetails> details =
         storage.read(new Quiet<Optional<IJobUpdateDetails>>() {
           @Override
           public Optional<IJobUpdateDetails> apply(StoreProvider storeProvider) {
-            return storeProvider.getJobUpdateStore().fetchJobUpdateDetails(updateId);
+            return storeProvider.getJobUpdateStore().fetchJobUpdateDetails(key);
           }
         });
 
@@ -336,7 +338,7 @@ class ReadOnlySchedulerImpl implements ReadOnlyScheduler.Iface {
       return ok(Result.getJobUpdateDetailsResult(
           new GetJobUpdateDetailsResult().setDetails(details.get().newBuilder())));
     } else {
-      return invalidRequest("Invalid update ID:" + updateId);
+      return invalidRequest("Invalid update: " + key);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 56398f3..b62adba 100644
--- a/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
+++ b/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
@@ -1216,6 +1216,7 @@ 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 {
@@ -1225,7 +1226,8 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
               quotaManager.checkJobUpdate(update, storeProvider));
 
           jobUpdateController.start(update, context.getIdentity());
-          return ok(Result.startJobUpdateResult(new StartJobUpdateResult(updateId)));
+          return ok(Result.startJobUpdateResult(
+              new StartJobUpdateResult(update.getSummary().getKey().newBuilder())));
         } catch (UpdateStateException | TaskValidationException e) {
           return error(INVALID_REQUEST, e);
         }
@@ -1327,8 +1329,8 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
   }
 
   @Override
-  public Response getJobUpdateDetails(String updateId) throws TException {
-    return readOnlyScheduler.getJobUpdateDetails(updateId);
+  public Response getJobUpdateDetails(JobUpdateKey key) throws TException {
+    return readOnlyScheduler.getJobUpdateDetails(key);
   }
 
   private Optional<SessionContext> isUpdateCoordinator(SessionKey session) {

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 d1bd3c9..acdade3 100644
--- a/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateControllerImpl.java
+++ b/src/main/java/org/apache/aurora/scheduler/updater/JobUpdateControllerImpl.java
@@ -188,7 +188,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
 
         recordAndChangeJobUpdateStatus(
             storeProvider,
-            Updates.getKey(summary),
+            summary.getKey(),
             status,
             Optional.of(updatingUser));
       }
@@ -217,7 +217,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
         }
 
         IJobUpdate update = details.getUpdate();
-        IJobUpdateKey key = Updates.getKey(update.getSummary());
+        IJobUpdateKey key = update.getSummary().getKey();
         Function<JobUpdateStatus, JobUpdateStatus> stateChange =
             isCoordinatedAndPulseExpired(key, update.getInstructions())
                 ? GET_BLOCKED_RESUME_STATE
@@ -250,7 +250,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
 
           IJobUpdateSummary summary = details.getUpdate().getSummary();
           IJobUpdateInstructions instructions = details.getUpdate().getInstructions();
-          IJobUpdateKey key = Updates.getKey(summary);
+          IJobUpdateKey key = summary.getKey();
           JobUpdateStatus status = summary.getState().getStatus();
 
           LOG.info("Automatically resuming update " + key);
@@ -348,12 +348,6 @@ class JobUpdateControllerImpl implements JobUpdateController {
         .setUpdateStatuses(Updates.ACTIVE_JOB_UPDATE_STATES));
   }
 
-  @VisibleForTesting
-  static IJobUpdateQuery queryByUpdateId(String updateId) {
-    return IJobUpdateQuery.build(new JobUpdateQuery()
-        .setUpdateId(updateId));
-  }
-
   /**
    * Changes the state of an update, without the 'scope' of an update ID.  This should only be used
    * when responding to outside inputs that are inherently un-scoped, such as a user action or task
@@ -400,7 +394,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
     }
 
     assertTransitionAllowed(updateSummary.getState().getStatus(), newStatus);
-    recordAndChangeJobUpdateStatus(storeProvider, Updates.getKey(updateSummary), newStatus, user);
+    recordAndChangeJobUpdateStatus(storeProvider, updateSummary.getKey(), newStatus, user);
   }
 
   private void recordAndChangeJobUpdateStatus(
@@ -431,7 +425,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
     boolean record;
 
     JobUpdateStore.Mutable updateStore = storeProvider.getJobUpdateStore();
-    Optional<String> updateLock = updateStore.getLockToken(key.getId());
+    Optional<String> updateLock = updateStore.getLockToken(key);
     if (updateLock.isPresent()) {
       status = newStatus;
       record = recordChange;
@@ -474,7 +468,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
         checkState(!updates.containsKey(job), "Updater already exists for " + job);
       }
 
-      IJobUpdate jobUpdate = updateStore.fetchJobUpdate(key.getId()).get();
+      IJobUpdate jobUpdate = updateStore.fetchJobUpdate(key).get();
       UpdateFactory.Update update;
       try {
         update = updateFactory.newUpdate(jobUpdate.getInstructions(), action == ROLL_FORWARD);
@@ -529,17 +523,15 @@ class JobUpdateControllerImpl implements JobUpdateController {
       Map<Integer, Optional<IScheduledTask>> changedInstance) {
 
     JobUpdateStatus updaterStatus = summary.getState().getStatus();
-    final IJobUpdateKey key = Updates.getKey(summary);
+    final IJobUpdateKey key = summary.getKey();
 
     JobUpdateStore.Mutable updateStore = storeProvider.getJobUpdateStore();
-    if (!updateStore.getLockToken(summary.getUpdateId()).isPresent()) {
+    if (!updateStore.getLockToken(key).isPresent()) {
       recordAndChangeJobUpdateStatus(storeProvider, key, ERROR, NO_USER);
       return;
     }
 
-    IJobUpdateInstructions instructions =
-        updateStore.fetchJobUpdateInstructions(summary.getUpdateId()).get();
-
+    IJobUpdateInstructions instructions = updateStore.fetchJobUpdateInstructions(key).get();
     if (isCoordinatedAndPulseExpired(key, instructions)) {
       // Move coordinated update into awaiting pulse state.
       JobUpdateStatus blockedStatus = getBlockedState(summary.getState().getStatus());
@@ -566,7 +558,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
       // was immediately recognized as being healthy and in the desired state.
       if (entry.getValue().getStatusChanges().equals(NOOP_INSTANCE_UPDATE)) {
         List<IJobInstanceUpdateEvent> savedEvents =
-            updateStore.fetchInstanceEvents(summary.getUpdateId(), entry.getKey());
+            updateStore.fetchInstanceEvents(key, entry.getKey());
         if (savedEvents.isEmpty()) {
           LOG.info("Suppressing no-op update for instance " + entry.getKey());
           statusChanges = ImmutableSet.of();
@@ -588,7 +580,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
                 .setInstanceId(entry.getKey())
                 .setTimestampMs(clock.nowMillis())
                 .setAction(action));
-        updateStore.saveJobInstanceUpdateEvent(Updates.getKey(summary), event);
+        updateStore.saveJobInstanceUpdateEvent(summary.getKey(), event);
       }
     }
 
@@ -620,7 +612,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
                 stateManager,
                 updaterStatus);
             executor.schedule(
-                getDeferredEvaluator(instance, summary.getUpdateId()),
+                getDeferredEvaluator(instance, key),
                 reevaluateDelay.getValue(),
                 reevaluateDelay.getUnit().getTimeUnit());
           }
@@ -654,7 +646,12 @@ class JobUpdateControllerImpl implements JobUpdateController {
               JobUpdateAction.INSTANCE_ROLLBACK_FAILED)
           .build();
 
-  private Runnable getDeferredEvaluator(final IInstanceKey instance, final String updateId) {
+  @VisibleForTesting
+  static IJobUpdateQuery queryByUpdate(IJobUpdateKey key) {
+    return IJobUpdateQuery.build(new JobUpdateQuery().setKey(key.newBuilder()));
+  }
+
+  private Runnable getDeferredEvaluator(final IInstanceKey instance, final IJobUpdateKey key) {
     return new Runnable() {
       @Override
       public void run() {
@@ -662,7 +659,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
           @Override
           protected void execute(MutableStoreProvider storeProvider) {
             IJobUpdateSummary summary =
-                getOnlyMatch(storeProvider.getJobUpdateStore(), queryByUpdateId(updateId));
+                getOnlyMatch(storeProvider.getJobUpdateStore(), queryByUpdate(key));
             JobUpdateStatus status = summary.getState().getStatus();
             // Suppress this evaluation if the updater is not currently active.
             if (JobUpdateStateMachine.isActive(status)) {
@@ -698,7 +695,7 @@ class JobUpdateControllerImpl implements JobUpdateController {
     }
 
     synchronized void initializePulseState(IJobUpdate update, JobUpdateStatus status) {
-      pulseStates.put(Updates.getKey(update.getSummary()), new PulseState(
+      pulseStates.put(update.getSummary().getKey(), new PulseState(
           update.getSummary().getJobKey(),
           status,
           update.getInstructions().getSettings().getBlockIfNoPulsesAfterMs(),

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/java/org/apache/aurora/scheduler/updater/Updates.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/updater/Updates.java b/src/main/java/org/apache/aurora/scheduler/updater/Updates.java
index b8c9195..776278c 100644
--- a/src/main/java/org/apache/aurora/scheduler/updater/Updates.java
+++ b/src/main/java/org/apache/aurora/scheduler/updater/Updates.java
@@ -21,12 +21,8 @@ import org.apache.aurora.gen.JobUpdateKey;
 import org.apache.aurora.gen.JobUpdateStatus;
 import org.apache.aurora.gen.JobUpdateSummary;
 import org.apache.aurora.gen.apiConstants;
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateSummary;
 
-import static java.util.Objects.requireNonNull;
-
 /**
  * Utility functions for job updates.
  */
@@ -41,13 +37,6 @@ public final class Updates {
   public static final Set<JobUpdateStatus> ACTIVE_JOB_UPDATE_STATES =
       Sets.immutableEnumSet(apiConstants.ACTIVE_JOB_UPDATE_STATES);
 
-  public static IJobUpdateKey getKey(IJobUpdateSummary summary) {
-    return IJobUpdateKey.build(
-        new JobUpdateKey(
-            JobKeys.assertValid(summary.getJobKey()).newBuilder(),
-            requireNonNull(summary.getUpdateId())));
-  }
-
   /**
    * Populates the {@link IJobUpdateSummary#getKey()} if it is not set by cloning other fields.
    *

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 07d1b1c..c071227 100644
--- a/src/main/python/apache/aurora/client/api/__init__.py
+++ b/src/main/python/apache/aurora/client/api/__init__.py
@@ -30,6 +30,7 @@ from .updater_util import UpdaterConfig
 from gen.apache.aurora.api.constants import LIVE_STATES
 from gen.apache.aurora.api.ttypes import (
     JobKey,
+    JobUpdateKey,
     JobUpdateQuery,
     JobUpdateRequest,
     Lock,
@@ -208,12 +209,10 @@ class AuroraClientAPI(object):
     log.info("Aborting update for: %s" % job_key.to_path())
     return self._scheduler_proxy.abortJobUpdate(job_key.to_thrift())
 
-  def query_job_updates(self, update_id=None, role=None, job_key=None, user=None,
-                        update_statuses=None):
+  def query_job_updates(self, role=None, job_key=None, user=None, update_statuses=None):
     """Returns all job updates matching the query.
 
     Arguments:
-    update_id -- job update ID.
     role -- job role.
     job_key -- job key.
     user -- user who initiated an update.
@@ -223,13 +222,12 @@ class AuroraClientAPI(object):
     """
     return self._scheduler_proxy.getJobUpdateSummaries(
         JobUpdateQuery(
-            updateId=update_id,
             role=role,
             jobKey=job_key.to_thrift() if job_key else None,
             user=user,
             updateStatuses=update_statuses))
 
-  def get_job_update_details(self, id):
+  def get_job_update_details(self, key):
     """Gets JobUpdateDetails for the specified job update ID.
 
     Arguments:
@@ -237,7 +235,10 @@ class AuroraClientAPI(object):
 
     Returns a response object with JobUpdateDetails.
     """
-    return self._scheduler_proxy.getJobUpdateDetails(id)
+    if not isinstance(key, JobUpdateKey):
+      raise self.TypeError('Invalid key %r: expected %s but got %s'
+                           % (key, JobUpdateKey.__name__, key.__class__.__name__))
+    return self._scheduler_proxy.getJobUpdateDetails(key)
 
   def cancel_update(self, job_key):
     """Cancel the update represented by job_key. Returns whether or not the cancellation was

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/python/apache/aurora/client/cli/context.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/context.py b/src/main/python/apache/aurora/client/cli/context.py
index ea64010..d3113ff 100644
--- a/src/main/python/apache/aurora/client/cli/context.py
+++ b/src/main/python/apache/aurora/client/cli/context.py
@@ -96,8 +96,10 @@ class AuroraCommandContext(Context):
     return synthesize_url(api.scheduler_proxy.scheduler_client().url, jobkey.role,
         jobkey.env, jobkey.name)
 
-  def get_update_page(self, api, jobkey, update_id):
-    return "%s/%s" % (self.get_job_page(api, jobkey), update_id)
+  def get_update_page(self, api, cluster, update_key):
+    return "%s/%s" % (
+        self.get_job_page(api, AuroraJobKey.from_thrift(cluster, update_key.job)),
+        update_key.id)
 
   def open_scheduler_page(self, cluster, role, env, name):
     """Open a scheduler page"""

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 bfa7a27..ee8146b 100644
--- a/src/main/python/apache/aurora/client/cli/update.py
+++ b/src/main/python/apache/aurora/client/cli/update.py
@@ -93,7 +93,7 @@ class StartUpdate(Verb):
         err_msg="Failed to start update due to error:")
 
     if resp.result:
-      url = context.get_update_page(api, job, resp.result.startJobUpdateResult.updateId)
+      url = context.get_update_page(api, config.cluster(), resp.result.startJobUpdateResult.key)
       context.print_out(self.UPDATE_MSG_TEMPLATE % url)
     else:
       context.print_out(combine_messages(resp))
@@ -210,8 +210,8 @@ class ListUpdates(Verb):
       result = []
       for summary in response.result.getJobUpdateSummariesResult.updateSummaries:
         job_entry = {
-            "jobkey": AuroraJobKey.from_thrift(cluster, summary.jobKey).to_path(),
-            "id": summary.updateId,
+            "jobkey": AuroraJobKey.from_thrift(cluster, summary.key.job).to_path(),
+            "id": summary.key.id,
             "user": summary.user,
             "started": summary.state.createdTimestampMs,
             "lastModified": summary.state.lastModifiedTimestampMs,
@@ -224,8 +224,8 @@ class ListUpdates(Verb):
         created = summary.state.createdTimestampMs
         lastMod = summary.state.lastModifiedTimestampMs
         context.print_out("Job: %s, Id: %s, User: %s, Status: %s" % (
-            AuroraJobKey.from_thrift(cluster, summary.jobKey).to_path(),
-            summary.updateId,
+            AuroraJobKey.from_thrift(cluster, summary.key.job).to_path(),
+            summary.key.id,
             summary.user,
             JobUpdateStatus._VALUES_TO_NAMES[summary.state.status]))
         context.print_out("Created: %s, Last Modified %s" % (created, lastMod), indent=2)
@@ -244,43 +244,44 @@ class UpdateStatus(Verb):
   def help(self):
     return """Display detailed status information about a scheduler-driven in-progress update."""
 
-  def _get_update_id(self, context, job_key):
+  def _get_update_key(self, context, job_key):
     api = context.get_api(context.options.jobspec.cluster)
     response = api.query_job_updates(job_key=context.options.jobspec)
     context.log_response_and_raise(response)
     for summary in response.result.getJobUpdateSummariesResult.updateSummaries:
-      if summary.jobKey == job_key.to_thrift():
-        return summary.updateId
+      if summary.key.job == job_key.to_thrift():
+        return summary.key
     else:
       return None
 
   def execute(self, context):
-    id = self._get_update_id(context, context.options.jobspec)
-    if not id:
+    key = self._get_update_key(context, context.options.jobspec)
+    if not key:
       context.print_err("No updates found for job %s" % context.options.jobspec)
       return EXIT_INVALID_PARAMETER
 
     api = context.get_api(context.options.jobspec.cluster)
-    response = api.get_job_update_details(id)
+    response = api.get_job_update_details(key)
     context.log_response_and_raise(response)
     details = response.result.getJobUpdateDetailsResult.details
     if context.options.write_json:
-      result = {}
-      # the following looks odd, but it's needed to convince the json renderer
-      # to render correctly.
-      result["updateId"] = ("%s" % details.update.summary.updateId)
-      result["job"] = str(context.options.jobspec)
-      result["started"] = details.update.summary.state.createdTimestampMs
-      result["last_updated"] = details.update.summary.state.lastModifiedTimestampMs
-      result["status"] = JobUpdateStatus._VALUES_TO_NAMES[details.update.summary.state.status]
-      result["update_events"] = []
+      result = {
+        "updateId": ("%s" % details.update.summary.key.id),
+        "job": str(context.options.jobspec),
+        "started": details.update.summary.state.createdTimestampMs,
+        "last_updated": details.update.summary.state.lastModifiedTimestampMs,
+        "status": JobUpdateStatus._VALUES_TO_NAMES[details.update.summary.state.status],
+        "update_events": [],
+        "instance_update_events": []
+      }
+
       update_events = details.updateEvents
       if update_events is not None and len(update_events) > 0:
         for event in update_events:
           result["update_events"].append({
               "status": JobUpdateStatus._VALUES_TO_NAMES[event.status],
               "timestampMs": event.timestampMs})
-      result["instance_update_events"] = []
+
       instance_events = details.instanceEvents
       if instance_events is not None and len(instance_events) > 0:
         for event in instance_events:
@@ -293,7 +294,7 @@ class UpdateStatus(Verb):
 
     else:
       context.print_out("Job: %s, UpdateID: %s" % (context.options.jobspec,
-          details.update.summary.updateId))
+          details.update.summary.key.id))
       context.print_out("Started %s, last updated: %s" %
           (time.ctime(details.update.summary.state.createdTimestampMs),
           time.ctime(details.update.summary.state.lastModifiedTimestampMs)))

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
index c08f09a..1b2aa74 100644
--- a/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
+++ b/src/main/resources/org/apache/aurora/scheduler/storage/db/JobUpdateDetailsMapper.xml
@@ -285,9 +285,8 @@
   </sql>
 
   <sql id="query_filter">
-    <if test="updateId != null || key != null || role != null || user != null || jobKey != null || updateStatuses != null || limit != 0 || offset != 0">
+    <if test="key != null || role != null || user != null || jobKey != null || updateStatuses != null || limit != 0 || offset != 0">
       WHERE TRUE
-      <if test="updateId != null">AND u.update_id = #{updateId}</if>
       <if test="key != null">
         AND <include refid="filter_by_update_key"/>
       </if>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql b/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
index e6184dd..a0d6b54 100644
--- a/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
+++ b/src/main/resources/org/apache/aurora/scheduler/storage/db/schema.sql
@@ -108,7 +108,7 @@ CREATE TABLE job_updates(
   wait_for_batch_completion BOOLEAN NOT NULL,
   block_if_no_pulses_after_ms INT NULL,
 
-  -- TODO(wfarner): Convert this to UNIQUE(job_key_id, update_id) to complete AURORA-1093.
+  -- TODO(wfarner): Convert this to UNIQUE(job_key_id, update_id) to complete AURORA-1139.
   UNIQUE(update_id)
 );
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/resources/scheduler/assets/breadcrumb.html
----------------------------------------------------------------------
diff --git a/src/main/resources/scheduler/assets/breadcrumb.html b/src/main/resources/scheduler/assets/breadcrumb.html
index b5f82ca..1314793 100644
--- a/src/main/resources/scheduler/assets/breadcrumb.html
+++ b/src/main/resources/scheduler/assets/breadcrumb.html
@@ -28,7 +28,7 @@
 
     <li ng-if='job && update'><a href='/scheduler/{{role}}/{{environment}}/{{job}}'>Job: {{job}}</a></li>
 
-    <li ng-if='update' class='active'>Update: {{update.update.summary.updateId}}</li>
+    <li ng-if='update' class='active'>Update: {{update.update.summary.key.id}}</li>
   </ul>
   </div>
 </div>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/resources/scheduler/assets/job.html
----------------------------------------------------------------------
diff --git a/src/main/resources/scheduler/assets/job.html b/src/main/resources/scheduler/assets/job.html
index 4a00a88..0c8fd54 100644
--- a/src/main/resources/scheduler/assets/job.html
+++ b/src/main/resources/scheduler/assets/job.html
@@ -39,7 +39,7 @@
     <div ng-if="updateInProgress" class="content-box in-progress-alert">
       <div class="row">
         <div class="col-md-4">
-          <a href="/scheduler/{{role}}/{{environment}}/{{job}}/{{updateInProgress.update.summary.updateId}}">Update In Progress</a>
+          <a href="/scheduler/{{role}}/{{environment}}/{{job}}/{{updateInProgress.update.summary.key.id}}">Update In Progress</a>
         </div>
         <div class="col-md-4">
           <progressbar class="progress" max="updateStats.totalInstancesToBeUpdated" value="updateStats.instancesUpdatedSoFar" type="success"><i>{{updateStats.instancesUpdatedSoFar}} of {{updateStats.totalInstancesToBeUpdated}}</i></progressbar>
@@ -146,7 +146,8 @@
             </tr>
             <tr ng-repeat="update in updates">
               <td>
-                <a href="/scheduler/{{role}}/{{environment}}/{{job}}/{{update.updateId}}">  {{update.updateId}}
+                <a href="/scheduler/{{role}}/{{environment}}/{{job}}/{{update.key.id}}">
+                  {{update.key.id}}
                 </a>
               </td>
               <td>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/resources/scheduler/assets/js/controllers.js
----------------------------------------------------------------------
diff --git a/src/main/resources/scheduler/assets/js/controllers.js b/src/main/resources/scheduler/assets/js/controllers.js
index 092e7d5..04ea1cb 100644
--- a/src/main/resources/scheduler/assets/js/controllers.js
+++ b/src/main/resources/scheduler/assets/js/controllers.js
@@ -12,7 +12,7 @@
  * limitations under the License.
  */
 (function () {
-  /* global ScheduleStatus:false, JobUpdateQuery:false, JobKey:false */
+  /* global ScheduleStatus:false, JobUpdateKey:false, JobUpdateQuery:false, JobKey:false */
   'use strict';
 
   /* Controllers */
@@ -267,7 +267,12 @@
 
   auroraUIControllers.controller('UpdateController',
     function ($scope, $routeParams, $timeout, auroraClient, updateUtil) {
-      var updateId = $routeParams.update;
+      var updateKey = new JobUpdateKey();
+      updateKey.job = new JobKey();
+      updateKey.job.role = $routeParams.role;
+      updateKey.job.environment = $routeParams.environment;
+      updateKey.job.name = $routeParams.job;
+      updateKey.id = $routeParams.update;
 
       $scope.role = $routeParams.role;
       $scope.environment = $routeParams.environment;
@@ -276,7 +281,7 @@
       $scope.instanceSummary = [];
 
       var getUpdateProgress = function () {
-        auroraClient.getJobUpdateDetails(updateId).then(function (response) {
+        auroraClient.getJobUpdateDetails(updateKey).then(function (response) {
           $scope.update = response.details;
           $scope.inProgress = updateUtil.isInProgress($scope.update.update.summary.state.status);
 
@@ -453,7 +458,7 @@
         $scope.updates = response.summaries;
 
         function getUpdateInProgress() {
-          auroraClient.getJobUpdateDetails($scope.updates[0].updateId).then(function (response) {
+          auroraClient.getJobUpdateDetails($scope.updates[0].key).then(function (response) {
             $scope.updateInProgress = response.details;
 
             $scope.updateStats = updateUtil.getUpdateStats($scope.updateInProgress);

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/resources/scheduler/assets/js/services.js
----------------------------------------------------------------------
diff --git a/src/main/resources/scheduler/assets/js/services.js b/src/main/resources/scheduler/assets/js/services.js
index 87eef8c..4782074 100644
--- a/src/main/resources/scheduler/assets/js/services.js
+++ b/src/main/resources/scheduler/assets/js/services.js
@@ -169,9 +169,9 @@
             });
           },
 
-          getJobUpdateDetails: function (id) {
+          getJobUpdateDetails: function (updateKey) {
             return async(function (deferred) {
-              auroraClient.getSchedulerClient().getJobUpdateDetails(id, function (response) {
+              auroraClient.getSchedulerClient().getJobUpdateDetails(updateKey, function (response) {
                 var result = auroraClient.processResponse(response);
                 result.details = response.result !== null ?
                   response.result.getJobUpdateDetailsResult.details : {};

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/main/resources/scheduler/assets/latestUpdates.html
----------------------------------------------------------------------
diff --git a/src/main/resources/scheduler/assets/latestUpdates.html b/src/main/resources/scheduler/assets/latestUpdates.html
index 9844eda..bcdb70f 100644
--- a/src/main/resources/scheduler/assets/latestUpdates.html
+++ b/src/main/resources/scheduler/assets/latestUpdates.html
@@ -26,8 +26,8 @@
   </tr>
   <tr ng-repeat="update in updates">
     <td>
-      <a href="/scheduler/{{update.jobKey.role}}/{{update.jobKey.environment}}/{{update.jobKey.name}}/{{update.updateId}}">
-        {{update.jobKey.role}}/{{update.jobKey.environment}}/{{update.jobKey.name}}
+      <a href="/scheduler/{{update.key.job.role}}/{{update.key.job.environment}}/{{update.key.job.name}}/{{update.key.id}}">
+        {{update.key.job.role}}/{{update.key.job.environment}}/{{update.key.job.name}}
       </a>
     </td>
     <td>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/test/java/org/apache/aurora/scheduler/quota/QuotaManagerImplTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/quota/QuotaManagerImplTest.java b/src/test/java/org/apache/aurora/scheduler/quota/QuotaManagerImplTest.java
index b79b07c..97e60d8 100644
--- a/src/test/java/org/apache/aurora/scheduler/quota/QuotaManagerImplTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/quota/QuotaManagerImplTest.java
@@ -27,20 +27,22 @@ import org.apache.aurora.gen.JobConfiguration;
 import org.apache.aurora.gen.JobKey;
 import org.apache.aurora.gen.JobUpdate;
 import org.apache.aurora.gen.JobUpdateInstructions;
+import org.apache.aurora.gen.JobUpdateKey;
 import org.apache.aurora.gen.JobUpdateSummary;
 import org.apache.aurora.gen.Range;
 import org.apache.aurora.gen.ResourceAggregate;
 import org.apache.aurora.gen.ScheduleStatus;
 import org.apache.aurora.gen.ScheduledTask;
 import org.apache.aurora.gen.TaskConfig;
+import org.apache.aurora.scheduler.base.JobKeys;
 import org.apache.aurora.scheduler.base.Query;
 import org.apache.aurora.scheduler.quota.QuotaManager.QuotaException;
 import org.apache.aurora.scheduler.quota.QuotaManager.QuotaManagerImpl;
 import org.apache.aurora.scheduler.storage.JobUpdateStore;
 import org.apache.aurora.scheduler.storage.Storage.StoreProvider;
 import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-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.IJobUpdateSummary;
 import org.apache.aurora.scheduler.storage.entities.IResourceAggregate;
 import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
@@ -61,6 +63,8 @@ public class QuotaManagerImplTest extends EasyMockTest {
   private static final String ROLE = "test";
   private static final String ENV = "test_env";
   private static final String JOB_NAME = "job";
+  private static final IJobUpdateKey UPDATE_KEY =
+      IJobUpdateKey.build(new JobUpdateKey(JobKeys.from(ROLE, ENV, JOB_NAME).newBuilder(), "u1"));
   private static final IResourceAggregate QUOTA = IResourceAggregate.build(new ResourceAggregate()
       .setNumCpus(1.0)
       .setRamMb(100L)
@@ -462,9 +466,8 @@ public class QuotaManagerImplTest extends EasyMockTest {
     expectQuota(IResourceAggregate.build(new ResourceAggregate(6, 6, 6))).times(2);
     expectTasks(createProdTask("foo", 2, 2, 2), createProdTask(JOB_NAME, 2, 2, 2)).times(2);
 
-    String updateId = "u1";
     ITaskConfig config = taskConfig(2, 2, 2, true);
-    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(updateId, config.getJob());
+    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(UPDATE_KEY);
     IJobUpdate update = buildJobUpdate(summaries.get(0), config, 1, config, 1);
     JobUpdate builder = update.newBuilder();
     builder.getInstructions().unsetDesiredState();
@@ -472,7 +475,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
     expect(jobUpdateStore.fetchJobUpdateSummaries(updateQuery(config.getJob().getRole())))
         .andReturn(summaries).times(2);
 
-    expect(jobUpdateStore.fetchJobUpdate(updateId))
+    expect(jobUpdateStore.fetchJobUpdate(UPDATE_KEY))
         .andReturn(Optional.of(IJobUpdate.build(builder))).times(2);
 
     expectNoCronJobs().times(2);
@@ -492,9 +495,8 @@ public class QuotaManagerImplTest extends EasyMockTest {
     expectQuota(IResourceAggregate.build(new ResourceAggregate(6, 6, 6))).times(2);
     expectTasks(createProdTask("foo", 2, 2, 2), createProdTask(JOB_NAME, 2, 2, 2)).times(2);
 
-    String updateId = "u1";
     ITaskConfig config = taskConfig(2, 2, 2, true);
-    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(updateId, config.getJob());
+    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(UPDATE_KEY);
     IJobUpdate update = buildJobUpdate(summaries.get(0), config, 1, config, 1);
     JobUpdate builder = update.newBuilder();
     builder.getInstructions().setInitialState(ImmutableSet.<InstanceTaskConfig>of());
@@ -502,7 +504,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
     expect(jobUpdateStore.fetchJobUpdateSummaries(updateQuery(config.getJob().getRole())))
         .andReturn(summaries).times(2);
 
-    expect(jobUpdateStore.fetchJobUpdate(updateId))
+    expect(jobUpdateStore.fetchJobUpdate(UPDATE_KEY))
         .andReturn(Optional.of(IJobUpdate.build(builder))).times(2);
 
     expectNoCronJobs().times(2);
@@ -522,9 +524,8 @@ public class QuotaManagerImplTest extends EasyMockTest {
     expectQuota(IResourceAggregate.build(new ResourceAggregate(6, 6, 6))).times(2);
     expectTasks(createProdTask("foo", 2, 2, 2), createProdTask("bar", 2, 2, 2)).times(2);
 
-    String updateId = "u1";
     ITaskConfig config = taskConfig(2, 2, 2, true);
-    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(updateId, config.getJob());
+    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(UPDATE_KEY);
     IJobUpdate update = buildJobUpdate(summaries.get(0), config, 1, config, 1);
     JobUpdate builder = update.newBuilder();
     builder.getInstructions().unsetDesiredState();
@@ -532,7 +533,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
     expect(jobUpdateStore.fetchJobUpdateSummaries(updateQuery(config.getJob().getRole())))
         .andReturn(summaries).times(2);
 
-    expect(jobUpdateStore.fetchJobUpdate(updateId))
+    expect(jobUpdateStore.fetchJobUpdate(UPDATE_KEY))
         .andReturn(Optional.of(IJobUpdate.build(builder))).times(2);
 
     expectNoCronJobs().times(2);
@@ -558,7 +559,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
 
     ITaskConfig config = taskConfig(1, 1, 1, true);
     IJobUpdate update = buildJobUpdate(
-        buildJobUpdateSummaries("u1", config.getJob()).get(0),
+        buildJobUpdateSummaries(UPDATE_KEY).get(0),
         taskConfig(2, 2, 2, true),
         1,
         config,
@@ -583,7 +584,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
 
     ITaskConfig config = taskConfig(2, 2, 2, true);
     IJobUpdate update = buildJobUpdate(
-        buildJobUpdateSummaries("u1", config.getJob()).get(0),
+        buildJobUpdateSummaries(UPDATE_KEY).get(0),
         config,
         1,
         config,
@@ -611,7 +612,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
 
     ITaskConfig config = taskConfig(2, 2, 2, true);
     IJobUpdate update = buildJobUpdate(
-        buildJobUpdateSummaries("u1", config.getJob()).get(0),
+        buildJobUpdateSummaries(UPDATE_KEY).get(0),
         config,
         1,
         config,
@@ -632,7 +633,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
   public void testCheckQuotaNewUpdateSkippedForNonProdDesiredState() {
     ITaskConfig config = taskConfig(2, 2, 2, false);
     IJobUpdate update = buildJobUpdate(
-        buildJobUpdateSummaries("u1", config.getJob()).get(0),
+        buildJobUpdateSummaries(UPDATE_KEY).get(0),
         taskConfig(2, 2, 2, true),
         1,
         config,
@@ -648,7 +649,7 @@ public class QuotaManagerImplTest extends EasyMockTest {
   public void testCheckQuotaNewUpdateSkippedForEmptyDesiredState() {
     ITaskConfig config = taskConfig(2, 2, 2, true);
     IJobUpdate update = buildJobUpdate(
-        buildJobUpdateSummaries("u1", config.getJob()).get(0),
+        buildJobUpdateSummaries(UPDATE_KEY).get(0),
         config,
         1,
         config,
@@ -708,8 +709,8 @@ public class QuotaManagerImplTest extends EasyMockTest {
       int desiredInstances,
       int times) {
 
-    String updateId = "u1";
-    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(updateId, initial.getJob());
+    IJobUpdateKey key = IJobUpdateKey.build(new JobUpdateKey(initial.getJob().newBuilder(), "u1"));
+    List<IJobUpdateSummary> summaries = buildJobUpdateSummaries(key);
     IJobUpdate update =
         buildJobUpdate(summaries.get(0), initial, intialInstances, desired, desiredInstances);
 
@@ -717,14 +718,13 @@ public class QuotaManagerImplTest extends EasyMockTest {
         .andReturn(summaries)
         .times(times);
 
-    expect(jobUpdateStore.fetchJobUpdate(updateId)).andReturn(Optional.of(update)).times(times);
+    expect(jobUpdateStore.fetchJobUpdate(key)).andReturn(Optional.of(update)).times(times);
 
   }
 
-  private List<IJobUpdateSummary> buildJobUpdateSummaries(String updateId, IJobKey jobKey) {
-    return ImmutableList.of(IJobUpdateSummary.build(new JobUpdateSummary()
-        .setJobKey(jobKey.newBuilder())
-        .setUpdateId(updateId)));
+  private List<IJobUpdateSummary> buildJobUpdateSummaries(IJobUpdateKey key) {
+    return ImmutableList.of(IJobUpdateSummary.build(
+        new JobUpdateSummary().setKey(key.newBuilder())));
   }
 
   private IJobUpdate buildJobUpdate(

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java b/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
index 81c8be7..7198db0 100644
--- a/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/storage/db/DBJobUpdateStoreTest.java
@@ -57,7 +57,6 @@ import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateQuery;
 import org.apache.aurora.scheduler.storage.entities.IJobUpdateSummary;
 import org.apache.aurora.scheduler.storage.entities.ILock;
-import org.apache.aurora.scheduler.updater.Updates;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -81,6 +80,10 @@ import static org.junit.Assert.fail;
 public class DBJobUpdateStoreTest {
 
   private static final IJobKey JOB = JobKeys.from("testRole", "testEnv", "job");
+  private static final IJobUpdateKey UPDATE1 =
+      IJobUpdateKey.build(new JobUpdateKey(JOB.newBuilder(), "update1"));
+  private static final IJobUpdateKey UPDATE2 = IJobUpdateKey.build(
+      new JobUpdateKey(JobKeys.from("testRole", "testEnv", "job2").newBuilder(), "update2"));
   private static final long CREATED_MS = 111L;
   private static final IJobUpdateEvent FIRST_EVENT =
       makeJobUpdateEvent(ROLLING_FORWARD, CREATED_MS);
@@ -557,7 +560,7 @@ public class DBJobUpdateStoreTest {
 
     assertEquals(
         ImmutableList.of(populateExpected(update).getSummary()),
-        getSummaries(new JobUpdateQuery().setUpdateId("update1")));
+        getSummaries(new JobUpdateQuery().setKey(UPDATE1.newBuilder())));
 
     // If the lock has been released for this job, we can start another update.
     saveUpdate(makeJobUpdate(makeKey("update2")), Optional.of("lock2"));
@@ -570,35 +573,33 @@ public class DBJobUpdateStoreTest {
     storage.write(new MutateWork.NoResult.Quiet() {
       @Override
       public void execute(MutableStoreProvider storeProvider) {
-        final IJobUpdate update1 =
-            makeJobUpdate(makeKey(JobKeys.from("role", "env", "name1"), "update1"));
-        final IJobUpdate update2 =
-            makeJobUpdate(makeKey(JobKeys.from("role", "env", "name2"), "update2"));
+        final IJobUpdate update1 = makeJobUpdate(UPDATE1);
+        final IJobUpdate update2 = makeJobUpdate(UPDATE2);
         saveUpdate(update1, Optional.of("lock1"));
         assertEquals(
             Optional.of("lock1"),
-            storeProvider.getJobUpdateStore().getLockToken("update1"));
-        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken("update2"));
+            storeProvider.getJobUpdateStore().getLockToken(UPDATE1));
+        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken(UPDATE2));
 
         saveUpdate(update2, Optional.of("lock2"));
         assertEquals(
             Optional.of("lock1"),
-            storeProvider.getJobUpdateStore().getLockToken("update1"));
+            storeProvider.getJobUpdateStore().getLockToken(UPDATE1));
         assertEquals(
             Optional.of("lock2"),
-            storeProvider.getJobUpdateStore().getLockToken("update2"));
+            storeProvider.getJobUpdateStore().getLockToken(UPDATE2));
 
         storeProvider.getLockStore().removeLock(
             makeLock(update1.getSummary().getJobKey(), "lock1").getKey());
-        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken("update1"));
+        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken(UPDATE1));
         assertEquals(
             Optional.of("lock2"),
-            storeProvider.getJobUpdateStore().getLockToken("update2"));
+            storeProvider.getJobUpdateStore().getLockToken(UPDATE2));
 
         storeProvider.getLockStore().removeLock(
             makeLock(update2.getSummary().getJobKey(), "lock2").getKey());
-        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken("update1"));
-        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken("update2"));
+        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken(UPDATE1));
+        assertEquals(NO_TOKEN, storeProvider.getJobUpdateStore().getLockToken(UPDATE2));
       }
     });
   }
@@ -626,7 +627,9 @@ public class DBJobUpdateStoreTest {
     assertEquals(ImmutableList.of(s3, s5, s4, s2, s1), getSummaries(new JobUpdateQuery()));
 
     // Test query by updateId.
-    assertEquals(ImmutableList.of(s1), getSummaries(new JobUpdateQuery().setUpdateId("u1")));
+    assertEquals(
+        ImmutableList.of(s1),
+        getSummaries(new JobUpdateQuery().setKey(new JobUpdateKey(job1.newBuilder(), "u1"))));
 
     // Test query by role.
     assertEquals(
@@ -735,7 +738,7 @@ public class DBJobUpdateStoreTest {
     // Test query by update ID.
     assertEquals(
         ImmutableList.of(details1),
-        queryDetails(new JobUpdateQuery().setUpdateId(updateId1.getId())));
+        queryDetails(new JobUpdateQuery().setKey(updateId1.newBuilder())));
 
     // Test query by role.
     assertEquals(
@@ -767,7 +770,7 @@ public class DBJobUpdateStoreTest {
   }
 
   private void assertUpdate(IJobUpdate expected) {
-    IJobUpdateKey key = Updates.getKey(expected.getSummary());
+    IJobUpdateKey key = expected.getSummary().getKey();
     assertEquals(populateExpected(expected), getUpdate(key).get());
     assertEquals(getUpdate(key).get(), getUpdateDetails(key).get().getUpdate());
     assertEquals(getUpdateInstructions(key).get(), expected.getInstructions());
@@ -777,7 +780,7 @@ public class DBJobUpdateStoreTest {
     return storage.read(new Quiet<Optional<IJobUpdate>>() {
       @Override
       public Optional<IJobUpdate> apply(Storage.StoreProvider storeProvider) {
-        return storeProvider.getJobUpdateStore().fetchJobUpdate(key.getId());
+        return storeProvider.getJobUpdateStore().fetchJobUpdate(key);
       }
     });
   }
@@ -786,7 +789,7 @@ public class DBJobUpdateStoreTest {
     return storage.read(new Quiet<List<IJobInstanceUpdateEvent>>() {
       @Override
       public List<IJobInstanceUpdateEvent> apply(Storage.StoreProvider storeProvider) {
-        return storeProvider.getJobUpdateStore().fetchInstanceEvents(key.getId(), id);
+        return storeProvider.getJobUpdateStore().fetchInstanceEvents(key, id);
       }
     });
   }
@@ -795,7 +798,7 @@ public class DBJobUpdateStoreTest {
     return storage.read(new Quiet<Optional<IJobUpdateInstructions>>() {
       @Override
       public Optional<IJobUpdateInstructions> apply(Storage.StoreProvider storeProvider) {
-        return storeProvider.getJobUpdateStore().fetchJobUpdateInstructions(key.getId());
+        return storeProvider.getJobUpdateStore().fetchJobUpdateInstructions(key);
       }
     });
   }
@@ -804,7 +807,7 @@ public class DBJobUpdateStoreTest {
     return storage.read(new Quiet<Optional<IJobUpdateDetails>>() {
       @Override
       public Optional<IJobUpdateDetails> apply(Storage.StoreProvider storeProvider) {
-        return storeProvider.getJobUpdateStore().fetchJobUpdateDetails(key.getId());
+        return storeProvider.getJobUpdateStore().fetchJobUpdateDetails(key);
       }
     });
   }
@@ -856,7 +859,7 @@ public class DBJobUpdateStoreTest {
         }
         storeProvider.getJobUpdateStore().saveJobUpdate(update, lockToken);
         storeProvider.getJobUpdateStore().saveJobUpdateEvent(
-            Updates.getKey(update.getSummary()),
+            update.getSummary().getKey(),
             FIRST_EVENT);
       }
     });

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/test/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImplTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImplTest.java b/src/test/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImplTest.java
index e969b97..ce5a626 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImplTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImplTest.java
@@ -81,6 +81,7 @@ import static org.apache.aurora.scheduler.thrift.Fixtures.LOCK;
 import static org.apache.aurora.scheduler.thrift.Fixtures.QUOTA;
 import static org.apache.aurora.scheduler.thrift.Fixtures.ROLE;
 import static org.apache.aurora.scheduler.thrift.Fixtures.ROLE_IDENTITY;
+import static org.apache.aurora.scheduler.thrift.Fixtures.UPDATE_KEY;
 import static org.apache.aurora.scheduler.thrift.Fixtures.USER;
 import static org.apache.aurora.scheduler.thrift.Fixtures.assertOkResponse;
 import static org.apache.aurora.scheduler.thrift.Fixtures.assertResponse;
@@ -550,14 +551,13 @@ public class ReadOnlySchedulerImplTest extends EasyMockTest {
 
   @Test
   public void testGetJobUpdateDetails() throws Exception {
-    String id = "id";
     JobUpdateDetails details = createJobUpdateDetails();
-    expect(storageUtil.jobUpdateStore.fetchJobUpdateDetails(id))
+    expect(storageUtil.jobUpdateStore.fetchJobUpdateDetails(UPDATE_KEY))
         .andReturn(Optional.of(IJobUpdateDetails.build(details)));
 
     control.replay();
 
-    Response response = assertOkResponse(thrift.getJobUpdateDetails(id));
+    Response response = assertOkResponse(thrift.getJobUpdateDetails(UPDATE_KEY.newBuilder()));
     assertEquals(
         details,
         response.getResult().getGetJobUpdateDetailsResult().getDetails());
@@ -660,12 +660,11 @@ public class ReadOnlySchedulerImplTest extends EasyMockTest {
 
   @Test
   public void testGetJobUpdateDetailsInvalidId() throws Exception {
-    String id = "id";
-    expect(storageUtil.jobUpdateStore.fetchJobUpdateDetails(id))
+    expect(storageUtil.jobUpdateStore.fetchJobUpdateDetails(UPDATE_KEY))
         .andReturn(Optional.<IJobUpdateDetails>absent());
 
     control.replay();
 
-    assertResponse(INVALID_REQUEST, thrift.getJobUpdateDetails(id));
+    assertResponse(INVALID_REQUEST, thrift.getJobUpdateDetails(UPDATE_KEY.newBuilder()));
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 d364188..98cb25a 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
@@ -78,6 +78,7 @@ import org.apache.aurora.gen.RewriteConfigsRequest;
 import org.apache.aurora.gen.ScheduleStatus;
 import org.apache.aurora.gen.ScheduledTask;
 import org.apache.aurora.gen.SessionKey;
+import org.apache.aurora.gen.StartJobUpdateResult;
 import org.apache.aurora.gen.TaskConfig;
 import org.apache.aurora.gen.TaskConstraint;
 import org.apache.aurora.gen.TaskQuery;
@@ -1845,7 +1846,9 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
     request.getTaskConfig().unsetJob();
 
     Response response = assertOkResponse(thrift.startJobUpdate(request, SESSION));
-    assertEquals(UPDATE_KEY.getId(), response.getResult().getStartJobUpdateResult().getUpdateId());
+    assertEquals(
+        new StartJobUpdateResult(UPDATE_KEY.newBuilder()),
+        response.getResult().getStartJobUpdateResult());
   }
 
   @Test
@@ -1887,7 +1890,9 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
 
     Response response =
         assertOkResponse(thrift.startJobUpdate(buildJobUpdateRequest(update), SESSION));
-    assertEquals(UPDATE_KEY.getId(), response.getResult().getStartJobUpdateResult().getUpdateId());
+    assertEquals(
+        new StartJobUpdateResult(UPDATE_KEY.newBuilder()),
+        response.getResult().getStartJobUpdateResult());
   }
 
   @Test(expected = NullPointerException.class)

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 2405bb8..459f745 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
@@ -295,7 +295,7 @@ abstract class ForwardingThrift implements AuroraAdmin.Iface {
   }
 
   @Override
-  public Response getJobUpdateDetails(String updateId) throws TException {
-    return delegate.getJobUpdateDetails(updateId);
+  public Response getJobUpdateDetails(JobUpdateKey key) throws TException {
+    return delegate.getJobUpdateDetails(key);
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 3ba6274..e24d6bd 100644
--- a/src/test/java/org/apache/aurora/scheduler/updater/JobUpdaterIT.java
+++ b/src/test/java/org/apache/aurora/scheduler/updater/JobUpdaterIT.java
@@ -280,7 +280,7 @@ public class JobUpdaterIT extends EasyMockTest {
     IJobUpdateDetails details = storage.read(new Work.Quiet<IJobUpdateDetails>() {
       @Override
       public IJobUpdateDetails apply(StoreProvider storeProvider) {
-        return storeProvider.getJobUpdateStore().fetchJobUpdateDetails(UPDATE_ID.getId()).get();
+        return storeProvider.getJobUpdateStore().fetchJobUpdateDetails(UPDATE_ID).get();
       }
     });
     Iterable<IJobInstanceUpdateEvent> orderedEvents =

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 ff1aff2..0d552e8 100644
--- a/src/test/python/apache/aurora/client/api/test_api.py
+++ b/src/test/python/apache/aurora/client/api/test_api.py
@@ -25,6 +25,8 @@ from ...api_util import SchedulerThriftApiSpec
 
 from gen.apache.aurora.api.ttypes import (
     JobConfiguration,
+    JobKey,
+    JobUpdateKey,
     JobUpdateQuery,
     JobUpdateRequest,
     JobUpdateSettings,
@@ -188,5 +190,6 @@ class TestJobUpdateApis(unittest.TestCase):
   def test_get_job_update_details(self):
     """Test getting job update details."""
     api, mock_proxy = self.mock_api()
-    api.get_job_update_details("id")
-    mock_proxy.getJobUpdateDetails.assert_called_once_with("id")
+    key = JobUpdateKey(job=JobKey(role="role", environment="env", name="name"), id="id")
+    api.get_job_update_details(key)
+    mock_proxy.getJobUpdateDetails.assert_called_once_with(key)

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/src/test/python/apache/aurora/client/cli/test_status.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_status.py b/src/test/python/apache/aurora/client/cli/test_status.py
index 1f39aa1..cf69de8 100644
--- a/src/test/python/apache/aurora/client/cli/test_status.py
+++ b/src/test/python/apache/aurora/client/cli/test_status.py
@@ -13,6 +13,7 @@
 #
 
 import contextlib
+import json
 import re
 import textwrap
 
@@ -442,15 +443,14 @@ class TestJobStatus(AuroraClientCommandTest):
       cmd = AuroraCommandLine()
       cmd.execute(['job', 'status', '--write-json', 'west/bozo/test/hello'])
       actual = re.sub("\\d\\d:\\d\\d:\\d\\d", "##:##:##", '\n'.join(mock_context.get_out()))
-      expected = textwrap.dedent("""\
-        [
+      expected = [
           {
             "active": [
               {
                 "status": "RUNNING",
                 "assignedTask": {
                   "task": {
-                    "isService": false,
+                    "isService": False,
                     "environment": "prod",
                     "container": {
                       "mesos": {}
@@ -468,7 +468,7 @@ class TestJobStatus(AuroraClientCommandTest):
                       "role": "nobody",
                       "name": "flibber"
                     },
-                    "production": false,
+                    "production": False,
                     "diskMb": 4096,
                     "ramMb": 2048,
                     "maxTaskFailures": 3,
@@ -506,7 +506,7 @@ class TestJobStatus(AuroraClientCommandTest):
                 "status": "RUNNING",
                 "assignedTask": {
                   "task": {
-                    "isService": false,
+                    "isService": False,
                     "environment": "prod",
                     "container": {
                       "mesos": {}
@@ -524,7 +524,7 @@ class TestJobStatus(AuroraClientCommandTest):
                       "role": "nobody",
                       "name": "flibber"
                     },
-                    "production": false,
+                    "production": False,
                     "diskMb": 4096,
                     "ramMb": 2048,
                     "maxTaskFailures": 3,
@@ -562,8 +562,8 @@ class TestJobStatus(AuroraClientCommandTest):
             "job": "west/bozo/test/hello",
             "inactive": []
           }
-        ]""")
-      assert actual == expected
+      ]
+      assert json.loads(actual) == expected
 
   def test_status_job_not_found(self):
     """Regression test: there was a crasher bug when metadata was None."""

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/b436ec52/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 6413c0d..1173650 100644
--- a/src/test/python/apache/aurora/client/cli/test_supdate.py
+++ b/src/test/python/apache/aurora/client/cli/test_supdate.py
@@ -12,6 +12,7 @@
 # limitations under the License.
 #
 import contextlib
+import json
 import textwrap
 
 import pytest
@@ -37,10 +38,12 @@ from gen.apache.aurora.api.ttypes import (
     GetJobUpdateDetailsResult,
     GetJobUpdateSummariesResult,
     JobInstanceUpdateEvent,
+    JobKey,
     JobUpdate,
     JobUpdateAction,
     JobUpdateDetails,
     JobUpdateEvent,
+    JobUpdateKey,
     JobUpdateState,
     JobUpdateStatus,
     JobUpdateSummary,
@@ -141,7 +144,8 @@ class TestUpdateCommand(AuroraClientCommandTest):
   def test_start_update_command_line_succeeds(self):
     mock_context = FakeAuroraCommandContext()
     resp = self.create_simple_success_response()
-    resp.result = Result(startJobUpdateResult=StartJobUpdateResult(updateId="id"))
+    resp.result = Result(startJobUpdateResult=StartJobUpdateResult(
+      key=JobUpdateKey(job=JobKey(role="role", environment="env", name="name"), id="id")))
     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)):
@@ -155,7 +159,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
         assert result == EXIT_OK
 
       update_url_msg = StartUpdate.UPDATE_MSG_TEMPLATE % (
-          mock_context.get_update_page(mock_api, AuroraJobKey.from_path(self.TEST_JOBSPEC), "id"))
+          'http://something_or_other/scheduler/role/env/name/id')
 
       assert mock_api.start_job_update.call_count == 1
       args, kwargs = mock_api.start_job_update.call_args
@@ -309,20 +313,22 @@ class TestUpdateCommand(AuroraClientCommandTest):
       assert mock_context.get_err() == ["Failed to pause update due to error:", "\tDamn"]
 
   def get_status_query_response(self, count=3):
-    query_response = Response()
-    query_response.responseCode = ResponseCode.OK
-    query_response.result = Result()
-    summaries = GetJobUpdateSummariesResult()
-    query_response.result.getJobUpdateSummariesResult = summaries
-    summaries.updateSummaries = [JobUpdateSummary(
-        updateId="%s" % i,
-        jobKey=self.TEST_JOBKEY.to_thrift(),
-        user="me",
-        state=JobUpdateState(
-            status=JobUpdateStatus.ROLLED_FORWARD,
-            createdTimestampMs=1411404927,
-            lastModifiedTimestampMs=14114056030)) for i in range(count)]
-    return query_response
+    return Response(
+        responseCode=ResponseCode.OK,
+        result=Result(
+            getJobUpdateSummariesResult=GetJobUpdateSummariesResult(
+                updateSummaries=[
+                    JobUpdateSummary(
+                        key=JobUpdateKey(job=self.TEST_JOBKEY.to_thrift(), id="%s" % i),
+                        user="me",
+                        state=JobUpdateState(
+                            status=JobUpdateStatus.ROLLED_FORWARD,
+                            createdTimestampMs=1411404927,
+                            lastModifiedTimestampMs=14114056030)) for i in range(count)
+                ]
+            )
+        )
+    )
 
   def test_list_updates_command(self):
     mock_context = FakeAuroraCommandContext()
@@ -350,33 +356,34 @@ class TestUpdateCommand(AuroraClientCommandTest):
       cmd = AuroraCommandLine()
       result = cmd.execute(["beta-update", "list", self.TEST_CLUSTER, "--user=me", '--write-json'])
       assert result == EXIT_OK
-      assert mock_context.get_out_str() == textwrap.dedent("""\
-          [
-            {
+      # TODO(wfarner): We really should not be performing string equality matching on JSON data,
+      # as it is sensitive to field ordering.
+      assert json.loads(mock_context.get_out_str()) == [
+          {
               "status": "ROLLED_FORWARD",
               "started": 1411404927,
               "lastModified": 14114056030,
               "user": "me",
               "jobkey": "west/bozo/test/hello",
               "id": "0"
-            },
-            {
+          },
+          {
               "status": "ROLLED_FORWARD",
               "started": 1411404927,
               "lastModified": 14114056030,
               "user": "me",
               "jobkey": "west/bozo/test/hello",
               "id": "1"
-            },
-            {
+          },
+          {
               "status": "ROLLED_FORWARD",
               "started": 1411404927,
               "lastModified": 14114056030,
               "user": "me",
               "jobkey": "west/bozo/test/hello",
               "id": "2"
-            }
-          ]""")
+          }
+      ]
 
   def get_update_details_response(self):
     query_response = Response()
@@ -385,8 +392,7 @@ class TestUpdateCommand(AuroraClientCommandTest):
     details = JobUpdateDetails(
         update=JobUpdate(
             summary=JobUpdateSummary(
-                jobKey=self.TEST_JOBKEY.to_thrift(),
-                updateId="0",
+                key=JobUpdateKey(self.TEST_JOBKEY.to_thrift(), "0"),
                 user="me",
                 state=JobUpdateState(
                   status=JobUpdateStatus.ROLLING_FORWARD,
@@ -453,7 +459,8 @@ class TestUpdateCommand(AuroraClientCommandTest):
   def test_update_status_json(self):
     mock_context = FakeAuroraCommandContext()
     api = mock_context.get_api(self.TEST_CLUSTER)
-    api.query_job_updates.return_value = self.get_status_query_response(count=1)
+    update_status_response = self.get_status_query_response(count=1)
+    api.query_job_updates.return_value = update_status_response
     api.get_job_update_details.return_value = self.get_update_details_response()
 
     with contextlib.nested(
@@ -464,48 +471,48 @@ class TestUpdateCommand(AuroraClientCommandTest):
       assert result == EXIT_OK
       mock_context.get_api(self.TEST_CLUSTER).query_job_updates.assert_called_with(
           job_key=self.TEST_JOBKEY)
-      mock_context.get_api(self.TEST_CLUSTER).get_job_update_details.assert_called_with("0")
-      assert mock_context.get_out_str() == textwrap.dedent("""\
-        {
+      mock_context.get_api(self.TEST_CLUSTER).get_job_update_details.assert_called_with(
+          update_status_response.result.getJobUpdateSummariesResult.updateSummaries[0].key)
+      assert json.loads(mock_context.get_out_str()) == {
           "status": "ROLLING_FORWARD",
           "last_updated": 14114056030,
           "started": 1411404927,
           "update_events": [
-            {
-              "status": "ROLLING_FORWARD",
-              "timestampMs": 1411404927
-            },
-            {
-              "status": "ROLL_FORWARD_PAUSED",
-              "timestampMs": 1411405000
-            },
-            {
-              "status": "ROLLING_FORWARD",
-              "timestampMs": 1411405100
-            }
+              {
+                "status": "ROLLING_FORWARD",
+                "timestampMs": 1411404927
+              },
+              {
+                  "status": "ROLL_FORWARD_PAUSED",
+                  "timestampMs": 1411405000
+              },
+              {
+                  "status": "ROLLING_FORWARD",
+                  "timestampMs": 1411405100
+              }
           ],
           "job": "west/bozo/test/hello",
           "updateId": "0",
           "instance_update_events": [
-            {
-              "action": "INSTANCE_UPDATING",
-              "instance": 1,
-              "timestamp": 1411404930
-            },
-            {
-              "action": "INSTANCE_UPDATING",
-              "instance": 2,
-              "timestamp": 1411404940
-            },
-            {
-              "action": "INSTANCE_UPDATED",
-              "instance": 1,
-              "timestamp": 1411404950
-            },
-            {
-              "action": "INSTANCE_UPDATED",
-              "instance": 2,
-              "timestamp": 1411404960
-            }
+              {
+                  "action": "INSTANCE_UPDATING",
+                  "instance": 1,
+                  "timestamp": 1411404930
+              },
+              {
+                  "action": "INSTANCE_UPDATING",
+                  "instance": 2,
+                  "timestamp": 1411404940
+              },
+              {
+                  "action": "INSTANCE_UPDATED",
+                  "instance": 1,
+                  "timestamp": 1411404950
+              },
+              {
+                  "action": "INSTANCE_UPDATED",
+                  "instance": 2,
+                  "timestamp": 1411404960
+              }
           ]
-        }""")
+      }


Mime
View raw message