aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject [2/2] aurora git commit: Schema changes for resource management refactoring
Date Mon, 25 Apr 2016 23:19:47 GMT
Schema changes for resource management refactoring

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


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

Branch: refs/heads/master
Commit: ff6e05f058cb2191f405d1f691790b2c79b14f2b
Parents: d339036
Author: Maxim Khutornenko <maxim@apache.org>
Authored: Mon Apr 25 16:19:29 2016 -0700
Committer: Maxim Khutornenko <maxim@apache.org>
Committed: Mon Apr 25 16:19:29 2016 -0700

----------------------------------------------------------------------
 .../thrift/org/apache/aurora/gen/api.thrift     |  10 ++
 .../aurora/scheduler/base/TaskTestUtil.java     |   8 +-
 .../configuration/ConfigurationManager.java     |  17 +++
 .../http/api/GsonMessageBodyHandler.java        |  26 +++-
 .../scheduler/resources/ResourceType.java       | 140 +++++++++++++++++--
 .../resources/ResourceTypeConverter.java        |  65 +++++++++
 .../aurora/scheduler/storage/db/DbStorage.java  |   5 +
 .../scheduler/storage/db/TaskConfigManager.java |  13 ++
 .../scheduler/storage/db/TaskConfigMapper.java  |  10 ++
 .../V003_CreateResourceTypesTable.java          |  56 ++++++++
 .../migration/V004_CreateTaskResourceTable.java |  74 ++++++++++
 .../db/typehandlers/ResourceTypeHandler.java    |  26 ++++
 .../storage/db/typehandlers/TypeHandlers.java   |   1 +
 .../scheduler/storage/db/views/DBResource.java  |  30 ++++
 .../storage/db/views/DbTaskConfig.java          |   9 +-
 .../scheduler/storage/log/LogStorage.java       |  16 +--
 .../storage/log/SnapshotStoreImpl.java          |   9 +-
 .../scheduler/storage/log/ThriftBackfill.java   | 127 +++++++++++++++++
 src/main/python/apache/aurora/config/thrift.py  |   5 +
 .../aurora/tools/java/thrift_wrapper_codegen.py |  57 ++++++--
 .../scheduler/storage/db/TaskConfigMapper.xml   |  36 +++++
 .../aurora/scheduler/storage/db/schema.sql      |  16 +++
 .../aurora/scheduler/app/SchedulerIT.java       |  16 ++-
 .../configuration/ConfigurationManagerTest.java |  40 +++++-
 .../resources/ResourceTypeConverterTest.java    |  30 ++++
 .../scheduler/resources/ResourceTypeTest.java   |  36 +++++
 .../scheduler/storage/backup/RecoveryTest.java  |   6 +-
 .../scheduler/storage/log/LogStorageTest.java   |  41 ++++--
 .../storage/log/SnapshotStoreImplIT.java        |  24 ++++
 .../storage/log/ThriftBackfillTest.java         |  96 +++++++++++++
 .../aurora/scheduler/thrift/Fixtures.java       |   7 +-
 .../thrift/ReadOnlySchedulerImplTest.java       |  20 +--
 .../thrift/SchedulerThriftInterfaceTest.java    |   8 +-
 .../apache/aurora/client/cli/test_inspect.py    |   5 +-
 .../python/apache/aurora/config/test_thrift.py  |  10 +-
 35 files changed, 1020 insertions(+), 75 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/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 0cf4d6e..b1b2f1d 100644
--- a/api/src/main/thrift/org/apache/aurora/gen/api.thrift
+++ b/api/src/main/thrift/org/apache/aurora/gen/api.thrift
@@ -224,6 +224,14 @@ union Image {
   2: AppcImage appc
 }
 
+/** Describes resource value required to run a task. */
+union Resource {
+  1: double numCpus
+  2: i64 ramMb
+  3: i64 diskMb
+  4: string namedPort
+}
+
 /** Description of the tasks contained within a job. */
 struct TaskConfig {
  /** Job task belongs to. */
@@ -246,6 +254,8 @@ struct TaskConfig {
   * specifying a container)
   */
  31: optional Image image
+ /** All resources required to run a task. */
+ 32: set<Resource> resources
 
  20: set<Constraint> constraints
  /** a list of named ports this task requests */

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java b/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
index d91c742..221417f 100644
--- a/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
+++ b/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
@@ -28,6 +28,7 @@ import org.apache.aurora.gen.ExecutorConfig;
 import org.apache.aurora.gen.Identity;
 import org.apache.aurora.gen.LimitConstraint;
 import org.apache.aurora.gen.Metadata;
+import org.apache.aurora.gen.Resource;
 import org.apache.aurora.gen.ScheduleStatus;
 import org.apache.aurora.gen.ScheduledTask;
 import org.apache.aurora.gen.TaskConfig;
@@ -104,7 +105,12 @@ public final class TaskTestUtil {
             new DockerContainer("imagename")
                 .setParameters(ImmutableList.of(
                     new DockerParameter("a", "b"),
-                    new DockerParameter("c", "d"))))));
+                    new DockerParameter("c", "d")))))
+        .setResources(ImmutableSet.of(
+            Resource.numCpus(1.0),
+            Resource.ramMb(1024),
+            Resource.diskMb(1024),
+            Resource.namedPort("http"))));
   }
 
   public static IScheduledTask makeTask(String id, IJobKey job) {

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
index 041cec3..9a15a4b 100644
--- a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
+++ b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
@@ -14,6 +14,7 @@
 package org.apache.aurora.scheduler.configuration;
 
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -37,12 +38,14 @@ import org.apache.aurora.gen.TaskConstraint;
 import org.apache.aurora.scheduler.TierManager;
 import org.apache.aurora.scheduler.base.JobKeys;
 import org.apache.aurora.scheduler.base.UserProvidedStrings;
+import org.apache.aurora.scheduler.resources.ResourceType;
 import org.apache.aurora.scheduler.storage.entities.IConstraint;
 import org.apache.aurora.scheduler.storage.entities.IContainer;
 import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
 import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
 import org.apache.aurora.scheduler.storage.entities.ITaskConstraint;
 import org.apache.aurora.scheduler.storage.entities.IValueConstraint;
+import org.apache.aurora.scheduler.storage.log.ThriftBackfill;
 
 import static java.util.Objects.requireNonNull;
 
@@ -310,6 +313,20 @@ public class ConfigurationManager {
       throw new TaskDescriptionException(CONTAINER_AND_IMAGE_ARE_MUTUALLY_EXCLUSIVE);
     }
 
+    ThriftBackfill.backfillTask(builder);
+
+    String types = config.getResources().stream()
+        .collect(Collectors.groupingBy(e -> ResourceType.fromResource(e)))
+        .entrySet().stream()
+            .filter(e -> !e.getKey().isMultipleAllowed() && e.getValue().size() > 1)
+            .map(r -> r.getKey().getAuroraName())
+            .sorted()
+            .collect(Collectors.joining(", "));
+
+    if (!Strings.isNullOrEmpty(types)) {
+      throw new TaskDescriptionException("Multiple resource values are not supported for " + types);
+    }
+
     return ITaskConfig.build(builder);
   }
 

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/http/api/GsonMessageBodyHandler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/api/GsonMessageBodyHandler.java b/src/main/java/org/apache/aurora/scheduler/http/api/GsonMessageBodyHandler.java
index 44295f8..1007a32 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/api/GsonMessageBodyHandler.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/api/GsonMessageBodyHandler.java
@@ -52,7 +52,9 @@ import com.google.gson.JsonSerializer;
 import org.apache.thrift.TFieldIdEnum;
 import org.apache.thrift.TUnion;
 import org.apache.thrift.meta_data.FieldMetaData;
+import org.apache.thrift.meta_data.FieldValueMetaData;
 import org.apache.thrift.meta_data.StructMetaData;
+import org.apache.thrift.protocol.TType;
 
 /**
  * A message body reader/writer that uses gson to translate JSON to and from java objects produced
@@ -200,8 +202,28 @@ public class GsonMessageBodyHandler
 
                 for (Entry<TFieldIdEnum, FieldMetaData> entry : metaDataMap.entrySet()) {
                   if (entry.getKey().getFieldName().equals(item.getKey())) {
-                    StructMetaData valueMetaData = (StructMetaData) entry.getValue().valueMetaData;
-                    Object result = context.deserialize(item.getValue(), valueMetaData.structClass);
+                    Object result;
+                    if (entry.getValue().valueMetaData.isStruct()) {
+                      StructMetaData valueMeta = (StructMetaData) entry.getValue().valueMetaData;
+                      result = context.deserialize(item.getValue(), valueMeta.structClass);
+                    } else {
+                      FieldValueMetaData valueMeta = entry.getValue().valueMetaData;
+                      Type type;
+                      switch (valueMeta.type) {
+                        case TType.DOUBLE:
+                          type = Double.TYPE;
+                          break;
+                        case TType.I64:
+                          type = Long.TYPE;
+                          break;
+                        case TType.STRING:
+                          type = String.class;
+                          break;
+                        default:
+                          throw new RuntimeException("Unmapped type: " + valueMeta.type);
+                      }
+                      result = context.deserialize(item.getValue(), type);
+                    }
                     return createUnion(clazz, entry.getKey(), result);
                   }
                 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/resources/ResourceType.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/resources/ResourceType.java b/src/main/java/org/apache/aurora/scheduler/resources/ResourceType.java
index b6fc949..6e4d694 100644
--- a/src/main/java/org/apache/aurora/scheduler/resources/ResourceType.java
+++ b/src/main/java/org/apache/aurora/scheduler/resources/ResourceType.java
@@ -13,37 +13,62 @@
  */
 package org.apache.aurora.scheduler.resources;
 
+import java.util.EnumSet;
+
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 
 import org.apache.aurora.common.quantity.Amount;
-import org.apache.aurora.common.quantity.Data;
+import org.apache.aurora.gen.Resource._Fields;
+import org.apache.aurora.scheduler.storage.entities.IResource;
+import org.apache.mesos.Protos;
+import org.apache.thrift.TEnum;
 
 import static java.util.Objects.requireNonNull;
 
+import static org.apache.aurora.common.quantity.Data.GB;
+import static org.apache.aurora.common.quantity.Data.MB;
+import static org.apache.aurora.scheduler.resources.ResourceTypeConverter.DOUBLE;
+import static org.apache.aurora.scheduler.resources.ResourceTypeConverter.LONG;
+import static org.apache.aurora.scheduler.resources.ResourceTypeConverter.STRING;
+import static org.apache.mesos.Protos.Value.Type.RANGES;
+import static org.apache.mesos.Protos.Value.Type.SCALAR;
+
 /**
  * Describes Mesos resource types and their Aurora traits.
  */
 @VisibleForTesting
-public enum ResourceType {
+public enum ResourceType implements TEnum {
   /**
    * CPU resource.
    */
-  CPUS("cpus", "CPU", 16),
+  CPUS(_Fields.NUM_CPUS, SCALAR, "cpus", DOUBLE, "CPU", 16, false),
 
   /**
    * RAM resource.
    */
-  RAM_MB("mem", "RAM", Amount.of(24, Data.GB).as(Data.MB)),
+  RAM_MB(_Fields.RAM_MB, SCALAR, "mem", LONG, "RAM", Amount.of(24, GB).as(MB), false),
 
   /**
    * DISK resource.
    */
-  DISK_MB("disk", "disk", Amount.of(450, Data.GB).as(Data.MB)),
+  DISK_MB(_Fields.DISK_MB, SCALAR, "disk", LONG, "disk", Amount.of(450, GB).as(MB), false),
 
   /**
    * Port resource.
    */
-  PORTS("ports", "ports", 1000);
+  PORTS(_Fields.NAMED_PORT, RANGES, "ports", STRING, "ports", 1000, true);
+
+  /**
+   * Correspondent thrift {@link org.apache.aurora.gen.Resource} enum value.
+   */
+  private final _Fields value;
+
+  /**
+   * Mesos resource type.
+   */
+  private final Protos.Value.Type mesosType;
 
   /**
    * Mesos resource name.
@@ -51,28 +76,77 @@ public enum ResourceType {
   private final String mesosName;
 
   /**
+   * Type converter for resource values.
+   */
+  private final ResourceTypeConverter<?> typeConverter;
+
+  /**
    * Aurora resource name.
    */
   private final String auroraName;
 
   /**
-   * Scaling range to use for comparison of scheduling vetoes. This has no real bearing besides
-   * trying to determine if a veto along one resource vector is a 'stronger' veto than that of
-   * another vector. The value represents the typical slave machine resources.
+   * Scaling range for comparing scheduling vetoes.
    */
   private final int scalingRange;
 
   /**
+   * Indicates if multiple resource types are allowed in a task.
+   */
+  private final boolean isMultipleAllowed;
+
+  private static ImmutableMap<Integer, ResourceType> byField =
+      Maps.uniqueIndex(EnumSet.allOf(ResourceType.class),  ResourceType::getValue);
+
+  /**
    * Describes a Resource type.
    *
+   * @param value Correspondent {@link _Fields} value.
+   * @param mesosType See {@link #getMesosType()} for more details.
    * @param mesosName See {@link #getMesosName()} for more details.
+   * @param typeConverter See {@link #getTypeConverter()} for more details.
    * @param auroraName See {@link #getAuroraName()} for more details.
    * @param scalingRange See {@link #getScalingRange()} for more details.
+   * @param isMultipleAllowed See {@link #isMultipleAllowed()} for more details.
    */
-  ResourceType(String mesosName, String auroraName, int scalingRange) {
+  ResourceType(
+      _Fields value,
+      Protos.Value.Type mesosType,
+      String mesosName,
+      ResourceTypeConverter<?> typeConverter,
+      String auroraName,
+      int scalingRange,
+      boolean isMultipleAllowed) {
+
+    this.value = value;
+    this.mesosType = requireNonNull(mesosType);
     this.mesosName = requireNonNull(mesosName);
+    this.typeConverter = requireNonNull(typeConverter);
     this.auroraName = requireNonNull(auroraName);
     this.scalingRange = scalingRange;
+    this.isMultipleAllowed = isMultipleAllowed;
+  }
+
+  /**
+   * Get unique ID value.
+   *
+   * @return Enum ID.
+   */
+  @Override
+  public int getValue() {
+    return value.getThriftFieldId();
+  }
+
+  /**
+   * Gets Mesos resource type.
+   * <p>
+   * @see <a href="https://github.com/apache/mesos/blob/master/include/mesos/mesos.proto/">Mesos
+   * protobuf for more details</a>
+   *
+   * @return Mesos resource type.
+   */
+  public Protos.Value.Type getMesosType() {
+    return mesosType;
   }
 
   /**
@@ -88,6 +162,15 @@ public enum ResourceType {
   }
 
   /**
+   * Gets {@link ResourceTypeConverter} to convert resource values.
+   *
+   * @return {@link ResourceTypeConverter} instance.
+   */
+  public ResourceTypeConverter<?> getTypeConverter() {
+    return typeConverter;
+  }
+
+  /**
    * Gets resource name for internal Aurora representation (e.g. in the UI).
    *
    * @return Aurora resource name.
@@ -97,11 +180,46 @@ public enum ResourceType {
   }
 
   /**
-   * Returns scaling range for comparing scheduling vetoes.
+   * Scaling range to use for comparison of scheduling vetoes.
+   * <p>
+   * This has no real bearing besides trying to determine if a veto along one resource vector
+   * is a 'stronger' veto than that of another vector. The value represents the typical slave
+   * machine resources.
    *
    * @return Resource scaling range.
    */
   public int getScalingRange() {
     return scalingRange;
   }
+
+  /**
+   * Returns a flag indicating if multiple resource of the same type are allowed in a given task.
+   *
+   * @return True if multiple resources of the same type are allowed, false otherwise.
+   */
+  public boolean isMultipleAllowed() {
+    return isMultipleAllowed;
+  }
+
+  /**
+   * Returns a {@link ResourceType} for the given ID.
+   *
+   * @param value ID value to search by. See {@link #getValue()}.
+   * @return {@link ResourceType}.
+   */
+  public static ResourceType fromIdValue(int value) {
+    return requireNonNull(byField.get(value), "Unmapped value: " + value);
+  }
+
+  /**
+   * Returns a {@link ResourceType} for the given resource.
+   *
+   * @param resource {@link IResource} to search by.
+   * @return {@link ResourceType}.
+   */
+  public static ResourceType fromResource(IResource resource) {
+    return requireNonNull(
+        byField.get((int) resource.getSetField().getThriftFieldId()),
+        "Unknown resource: " + resource);
+  }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/resources/ResourceTypeConverter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/resources/ResourceTypeConverter.java b/src/main/java/org/apache/aurora/scheduler/resources/ResourceTypeConverter.java
new file mode 100644
index 0000000..11395ec
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/resources/ResourceTypeConverter.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.resources;
+
+import com.google.common.primitives.Longs;
+
+/**
+ * Converts resource values to/from generic (String) representation.
+ * @param <T> Resource value type to convert.
+ */
+public interface ResourceTypeConverter<T> {
+  /**
+   * Parses resource value from the string representation.
+   *
+   * @param value String value to parse.
+   * @return Resource value of type {@code T}.
+   */
+  T parseFrom(String value);
+
+  /**
+   * Converts resource of type {@code T} to its string representation.
+   *
+   * @param value Resource value to stringify.
+   * @return String representation of the resource value.
+   */
+  default String stringify(Object value) {
+    return value.toString();
+  }
+
+  LongConverter LONG = new LongConverter();
+  DoubleConverter DOUBLE = new DoubleConverter();
+  StringConverter STRING = new StringConverter();
+
+  class LongConverter implements ResourceTypeConverter<Long> {
+    @Override
+    public Long parseFrom(String value) {
+      return Longs.tryParse(value);
+    }
+  }
+
+  class DoubleConverter implements ResourceTypeConverter<Double> {
+    @Override
+    public Double parseFrom(String value) {
+      return Double.parseDouble(value);
+    }
+  }
+
+  class StringConverter implements ResourceTypeConverter<String> {
+    @Override
+    public String parseFrom(String value) {
+      return value;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/DbStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/DbStorage.java b/src/main/java/org/apache/aurora/scheduler/storage/db/DbStorage.java
index 360914e..acb4498 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/DbStorage.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/DbStorage.java
@@ -34,6 +34,7 @@ import org.apache.aurora.gen.ScheduleStatus;
 import org.apache.aurora.scheduler.async.AsyncModule.AsyncExecutor;
 import org.apache.aurora.scheduler.async.GatedWorkQueue;
 import org.apache.aurora.scheduler.async.GatedWorkQueue.GatedOperation;
+import org.apache.aurora.scheduler.resources.ResourceType;
 import org.apache.aurora.scheduler.storage.AttributeStore;
 import org.apache.aurora.scheduler.storage.CronJobStore;
 import org.apache.aurora.scheduler.storage.JobUpdateStore;
@@ -234,6 +235,10 @@ class DbStorage extends AbstractIdleService implements Storage {
       enumValueMapper.addEnumValue("task_states", status.getValue(), status.name());
     }
 
+    for (ResourceType resourceType : ResourceType.values()) {
+      enumValueMapper.addEnumValue("resource_types", resourceType.getValue(), resourceType.name());
+    }
+
     createPoolMetrics();
   }
 

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigManager.java b/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigManager.java
index 25160df..17b2490 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigManager.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigManager.java
@@ -20,6 +20,9 @@ import javax.inject.Inject;
 
 import com.google.common.collect.Maps;
 
+import org.apache.aurora.GuavaUtils;
+import org.apache.aurora.common.collections.Pair;
+import org.apache.aurora.scheduler.resources.ResourceType;
 import org.apache.aurora.scheduler.storage.db.views.DbTaskConfig;
 import org.apache.aurora.scheduler.storage.db.views.Pairs;
 import org.apache.aurora.scheduler.storage.entities.IAppcImage;
@@ -96,6 +99,16 @@ class TaskConfigManager {
       }
     }
 
+    if (!config.getResources().isEmpty()) {
+      configMapper.insertResources(
+          configInsert.getId(),
+          config.getResources().stream()
+              .map(e -> Pair.of(
+                  ResourceType.fromResource(e).getValue(),
+                  ResourceType.fromResource(e).getTypeConverter().stringify(e.getRawValue())))
+              .collect(GuavaUtils.toImmutableList()));
+    }
+
     if (!config.getRequestedPorts().isEmpty()) {
       configMapper.insertRequestedPorts(configInsert.getId(), config.getRequestedPorts());
     }

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.java b/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.java
index e778a39..04666ad 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.java
@@ -176,4 +176,14 @@ interface TaskConfigMapper extends GarbageCollectedTableMapper {
       @Param("configId") long configId,
       @Param("name") String name,
       @Param("tag") String tag);
+
+  /**
+   * Inserts task resources.
+   *
+   * @param configId Task config ID.
+   * @param values Resources to insert.
+   */
+  void insertResources(
+      @Param("configId") long configId,
+      @Param("values") List<Pair<Integer, String>> values);
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V003_CreateResourceTypesTable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V003_CreateResourceTypesTable.java b/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V003_CreateResourceTypesTable.java
new file mode 100644
index 0000000..76c6916
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V003_CreateResourceTypesTable.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.storage.db.migration;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import org.apache.aurora.scheduler.resources.ResourceType;
+import org.apache.ibatis.migration.MigrationScript;
+
+public class V003_CreateResourceTypesTable implements MigrationScript {
+  @Override
+  public BigDecimal getId() {
+    return BigDecimal.valueOf(3L);
+  }
+
+  @Override
+  public String getDescription() {
+    return "Create the resource_types table.";
+  }
+  @Override
+  public String getUpScript() {
+    return "CREATE TABLE IF NOT EXISTS resource_types("
+        + "id INT PRIMARY KEY,"
+        + "name VARCHAR NOT NULL,"
+        + "UNIQUE(name)"
+        + ");\n"
+        + populateScript();
+  }
+
+  @Override
+  public String getDownScript() {
+    return "DROP TABLE IF EXISTS resource_types;";
+  }
+
+  private static String populateScript() {
+    return Arrays.stream(ResourceType.values())
+        .map(e -> String.format(
+            "MERGE INTO resource_types(id, name) KEY(name) VALUES (%d, '%s');",
+            e.getValue(),
+            e.name()))
+        .collect(Collectors.joining("\n"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V004_CreateTaskResourceTable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V004_CreateTaskResourceTable.java b/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V004_CreateTaskResourceTable.java
new file mode 100644
index 0000000..af106a8
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/migration/V004_CreateTaskResourceTable.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.storage.db.migration;
+
+import java.math.BigDecimal;
+
+import org.apache.aurora.scheduler.resources.ResourceType;
+import org.apache.ibatis.migration.MigrationScript;
+
+public class V004_CreateTaskResourceTable implements MigrationScript {
+  @Override
+  public BigDecimal getId() {
+    return BigDecimal.valueOf(4L);
+  }
+
+  @Override
+  public String getDescription() {
+    return "Create the task_resource table.";
+  }
+
+  @Override
+  public String getUpScript() {
+    return "CREATE TABLE IF NOT EXISTS task_resource("
+        + "id IDENTITY,"
+        + "task_config_id BIGINT NOT NULL REFERENCES task_configs(id) ON DELETE CASCADE,"
+        + "type_id INT NOT NULL REFERENCES resource_types(id),"
+        + "value VARCHAR NOT NULL,"
+        + "UNIQUE(task_config_id, type_id, value)"
+        + ");\n"
+        + migrateScript();
+  }
+
+  @Override
+  public String getDownScript() {
+    return "DROP TABLE IF EXISTS task_resource;";
+  }
+
+  private static String migrateScript() {
+    return "MERGE INTO task_resource(task_config_id, type_id, value) "
+        + "KEY(task_config_id, type_id, value) "
+        + "SELECT t.id, rt.id, t.num_cpus FROM task_configs t "
+        + "JOIN resource_types rt ON rt.name = "
+        + String.format("'%s';%n", ResourceType.CPUS.name())
+
+        + "MERGE INTO task_resource(task_config_id, type_id, value) "
+        + "KEY(task_config_id, type_id, value) "
+        + "SELECT t.id, rt.id, t.ram_mb FROM task_configs t "
+        + "JOIN resource_types rt ON rt.NAME = "
+        + String.format("'%s';%n", ResourceType.RAM_MB.name())
+
+        + "MERGE INTO task_resource(task_config_id, type_id, value) "
+        + "KEY(task_config_id, type_id, value) "
+        + "SELECT t.id, rt.id, t.disk_mb FROM task_configs t "
+        + "JOIN resource_types rt ON rt.NAME = "
+        + String.format("'%s';%n", ResourceType.DISK_MB.name())
+
+        + "MERGE INTO task_resource(task_config_id, type_id, value) "
+        + "KEY(task_config_id, type_id, value) "
+        + "SELECT tcrp.task_config_id, rt.id, tcrp.port_name FROM task_config_requested_ports tcrp "
+        + "JOIN resource_types rt ON rt.NAME = "
+        + String.format("'%s';%n", ResourceType.PORTS.name());
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/ResourceTypeHandler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/ResourceTypeHandler.java b/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/ResourceTypeHandler.java
new file mode 100644
index 0000000..e1bb7fd
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/ResourceTypeHandler.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.storage.db.typehandlers;
+
+import org.apache.aurora.scheduler.resources.ResourceType;
+
+/**
+ * Type handler for {@link ResourceType} enum.
+ */
+public class ResourceTypeHandler extends AbstractTEnumTypeHandler<ResourceType> {
+  @Override
+  protected ResourceType fromValue(int value) {
+    return ResourceType.fromIdValue(value);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/TypeHandlers.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/TypeHandlers.java b/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/TypeHandlers.java
index ed561c6..e30c387 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/TypeHandlers.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/typehandlers/TypeHandlers.java
@@ -34,6 +34,7 @@ public final class TypeHandlers {
         .add(JobUpdateStatusTypeHandler.class)
         .add(MaintenanceModeTypeHandler.class)
         .add(ScheduleStatusTypeHandler.class)
+        .add(ResourceTypeHandler.class)
         .build();
   }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/views/DBResource.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/views/DBResource.java b/src/main/java/org/apache/aurora/scheduler/storage/db/views/DBResource.java
new file mode 100644
index 0000000..dc7e97d
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/views/DBResource.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.storage.db.views;
+
+import org.apache.aurora.gen.Resource;
+import org.apache.aurora.scheduler.resources.ResourceType;
+import org.apache.aurora.scheduler.storage.entities.IResource;
+
+public final class DBResource {
+  private ResourceType type;
+  private String value;
+
+  private DBResource() {
+  }
+
+  Resource toThrift() {
+    return IResource.newBuilder(type.getValue(), type.getTypeConverter().parseFrom(value));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/db/views/DbTaskConfig.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/db/views/DbTaskConfig.java b/src/main/java/org/apache/aurora/scheduler/storage/db/views/DbTaskConfig.java
index cdd1060..a7523c4 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/db/views/DbTaskConfig.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/db/views/DbTaskConfig.java
@@ -17,7 +17,6 @@ import java.util.List;
 
 import com.google.common.collect.ImmutableSet;
 
-import org.apache.aurora.GuavaUtils;
 import org.apache.aurora.common.collections.Pair;
 import org.apache.aurora.gen.Container;
 import org.apache.aurora.gen.ExecutorConfig;
@@ -28,6 +27,8 @@ import org.apache.aurora.gen.Metadata;
 import org.apache.aurora.gen.TaskConfig;
 import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
 
+import static org.apache.aurora.GuavaUtils.toImmutableSet;
+
 public final class DbTaskConfig {
   private long rowId;
   private JobKey job;
@@ -48,6 +49,7 @@ public final class DbTaskConfig {
   private DbContainer container;
   private String tier;
   private DbImage image;
+  private List<DBResource> resources;
 
   private DbTaskConfig() {
   }
@@ -71,14 +73,15 @@ public final class DbTaskConfig {
         .setImage(image == null ? null : image.toThrift())
         .setConstraints(constraints.stream()
             .map(DbConstraint::toThrift)
-            .collect(GuavaUtils.toImmutableSet()))
+            .collect(toImmutableSet()))
         .setRequestedPorts(ImmutableSet.copyOf(requestedPorts))
         .setTaskLinks(Pairs.toMap(taskLinks))
         .setContactEmail(contactEmail)
         .setExecutorConfig(executorConfig)
         .setMetadata(ImmutableSet.copyOf(metadata))
         .setContainer(
-            container == null ? Container.mesos(new MesosContainer()) : container.toThrift());
+            container == null ? Container.mesos(new MesosContainer()) : container.toThrift())
+        .setResources(resources.stream().map(DBResource::toThrift).collect(toImmutableSet()));
   }
 
   public ITaskConfig toImmutable() {

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/log/LogStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/log/LogStorage.java b/src/main/java/org/apache/aurora/scheduler/storage/log/LogStorage.java
index f586186..39b6567 100644
--- a/src/main/java/org/apache/aurora/scheduler/storage/log/LogStorage.java
+++ b/src/main/java/org/apache/aurora/scheduler/storage/log/LogStorage.java
@@ -35,7 +35,6 @@ import org.apache.aurora.common.quantity.Amount;
 import org.apache.aurora.common.quantity.Time;
 import org.apache.aurora.common.stats.SlidingStats;
 import org.apache.aurora.gen.HostAttributes;
-import org.apache.aurora.gen.JobUpdate;
 import org.apache.aurora.gen.storage.LogEntry;
 import org.apache.aurora.gen.storage.Op;
 import org.apache.aurora.gen.storage.RewriteTask;
@@ -62,16 +61,13 @@ import org.apache.aurora.scheduler.storage.Storage.MutateWork.NoResult;
 import org.apache.aurora.scheduler.storage.Storage.NonVolatileStorage;
 import org.apache.aurora.scheduler.storage.TaskStore;
 import org.apache.aurora.scheduler.storage.entities.IHostAttributes;
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
 import org.apache.aurora.scheduler.storage.entities.IJobInstanceUpdateEvent;
 import org.apache.aurora.scheduler.storage.entities.IJobKey;
-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.ILock;
 import org.apache.aurora.scheduler.storage.entities.ILockKey;
 import org.apache.aurora.scheduler.storage.entities.IResourceAggregate;
-import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
 import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -330,7 +326,7 @@ public class LogStorage implements NonVolatileStorage, DistributedSnapshotStore
         .put(Op._Fields.SAVE_CRON_JOB, op -> {
           SaveCronJob cronJob = op.getSaveCronJob();
           writeBehindJobStore.saveAcceptedJob(
-              IJobConfiguration.build(cronJob.getJobConfig()));
+              ThriftBackfill.backfillJobConfiguration(cronJob.getJobConfig()));
         })
         .put(
             Op._Fields.REMOVE_JOB,
@@ -338,7 +334,7 @@ public class LogStorage implements NonVolatileStorage, DistributedSnapshotStore
         .put(
             Op._Fields.SAVE_TASKS,
             op -> writeBehindTaskStore.saveTasks(
-                IScheduledTask.setFromBuilders(op.getSaveTasks().getTasks())))
+                ThriftBackfill.backfillTasks(op.getSaveTasks().getTasks())))
         .put(Op._Fields.REWRITE_TASK, op -> {
           RewriteTask rewriteTask = op.getRewriteTask();
           writeBehindTaskStore.unsafeModifyInPlace(
@@ -374,12 +370,10 @@ public class LogStorage implements NonVolatileStorage, DistributedSnapshotStore
         .put(
             Op._Fields.REMOVE_LOCK,
             op -> writeBehindLockStore.removeLock(ILockKey.build(op.getRemoveLock().getLockKey())))
-        .put(Op._Fields.SAVE_JOB_UPDATE, op -> {
-          JobUpdate update = op.getSaveJobUpdate().getJobUpdate();
+        .put(Op._Fields.SAVE_JOB_UPDATE, op ->
           writeBehindJobUpdateStore.saveJobUpdate(
-              IJobUpdate.build(update),
-              Optional.fromNullable(op.getSaveJobUpdate().getLockToken()));
-        })
+              ThriftBackfill.backFillJobUpdate(op.getSaveJobUpdate().getJobUpdate()),
+              Optional.fromNullable(op.getSaveJobUpdate().getLockToken())))
         .put(Op._Fields.SAVE_JOB_UPDATE_EVENT, op -> {
           SaveJobUpdateEvent event = op.getSaveJobUpdateEvent();
           writeBehindJobUpdateStore.saveJobUpdateEvent(

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/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 b6922e1..8eed1fc 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
@@ -60,7 +60,6 @@ import org.apache.aurora.scheduler.storage.db.MigrationManager;
 import org.apache.aurora.scheduler.storage.entities.IHostAttributes;
 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.ILock;
@@ -222,8 +221,8 @@ public class SnapshotStoreImpl implements SnapshotStore<Snapshot> {
           store.getUnsafeTaskStore().deleteAllTasks();
 
           if (snapshot.isSetTasks()) {
-            store.getUnsafeTaskStore().saveTasks(
-                IScheduledTask.setFromBuilders(snapshot.getTasks()));
+            store.getUnsafeTaskStore()
+                .saveTasks(ThriftBackfill.backfillTasks(snapshot.getTasks()));
           }
         }
       },
@@ -251,7 +250,7 @@ public class SnapshotStoreImpl implements SnapshotStore<Snapshot> {
           if (snapshot.isSetCronJobs()) {
             for (StoredCronJob job : snapshot.getCronJobs()) {
               store.getCronJobStore().saveAcceptedJob(
-                  IJobConfiguration.build(job.getJobConfiguration()));
+                  ThriftBackfill.backfillJobConfiguration(job.getJobConfiguration()));
             }
           }
         }
@@ -328,7 +327,7 @@ public class SnapshotStoreImpl implements SnapshotStore<Snapshot> {
             for (StoredJobUpdateDetails storedDetails : snapshot.getJobUpdateDetails()) {
               JobUpdateDetails details = storedDetails.getDetails();
               updateStore.saveJobUpdate(
-                  IJobUpdate.build(details.getUpdate()),
+                  ThriftBackfill.backFillJobUpdate(details.getUpdate()),
                   Optional.fromNullable(storedDetails.getLockToken()));
 
               if (details.getUpdateEventsSize() > 0) {

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/java/org/apache/aurora/scheduler/storage/log/ThriftBackfill.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/storage/log/ThriftBackfill.java b/src/main/java/org/apache/aurora/scheduler/storage/log/ThriftBackfill.java
new file mode 100644
index 0000000..9c86aa0
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/storage/log/ThriftBackfill.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.storage.log;
+
+import java.util.Set;
+
+import org.apache.aurora.GuavaUtils;
+import org.apache.aurora.gen.JobConfiguration;
+import org.apache.aurora.gen.JobUpdate;
+import org.apache.aurora.gen.JobUpdateInstructions;
+import org.apache.aurora.gen.Resource;
+import org.apache.aurora.gen.ScheduledTask;
+import org.apache.aurora.gen.TaskConfig;
+import org.apache.aurora.scheduler.resources.ResourceType;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobUpdate;
+import org.apache.aurora.scheduler.storage.entities.IResource;
+import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
+
+import static org.apache.aurora.scheduler.resources.ResourceType.CPUS;
+import static org.apache.aurora.scheduler.resources.ResourceType.DISK_MB;
+import static org.apache.aurora.scheduler.resources.ResourceType.PORTS;
+import static org.apache.aurora.scheduler.resources.ResourceType.RAM_MB;
+
+/**
+ * Helps migrating thrift schema by populating deprecated and/or replacement fields.
+ */
+public final class ThriftBackfill {
+  private ThriftBackfill() {
+    // Utility class.
+  }
+
+  private static Resource getResource(TaskConfig config, ResourceType type) {
+    return config.getResources().stream()
+        .filter(e -> ResourceType.fromResource(IResource.build(e)).equals(type))
+        .findFirst()
+        .orElseThrow(() -> new IllegalArgumentException("Missing resource definition for " + type));
+  }
+
+  /**
+   * Ensures TaskConfig.resources and correspondent task-level fields are all populated.
+   *
+   * @param config TaskConfig to backfill.
+   * @return Backfilled TaskConfig.
+   */
+  public static TaskConfig backfillTask(TaskConfig config) {
+    if (!config.isSetResources() || config.getResources().isEmpty()) {
+      config.addToResources(Resource.numCpus(config.getNumCpus()));
+      config.addToResources(Resource.ramMb(config.getRamMb()));
+      config.addToResources(Resource.diskMb(config.getDiskMb()));
+      if (config.isSetRequestedPorts()) {
+        for (String port : config.getRequestedPorts()) {
+          config.addToResources(Resource.namedPort(port));
+        }
+      }
+    } else {
+      config.setNumCpus(getResource(config, CPUS).getNumCpus());
+      config.setRamMb(getResource(config, RAM_MB).getRamMb());
+      config.setDiskMb(getResource(config, DISK_MB).getDiskMb());
+      Set<String> ports = config.getResources().stream()
+          .filter(e -> ResourceType.fromResource(IResource.build(e)).equals(PORTS))
+          .map(Resource::getNamedPort)
+          .collect(GuavaUtils.toImmutableSet());
+      if (!ports.isEmpty()) {
+        config.setRequestedPorts(ports);
+      }
+    }
+    return config;
+  }
+
+  /**
+   * Backfills JobConfiguration. See {@link #backfillTask(TaskConfig)}.
+   *
+   * @param jobConfig JobConfiguration to backfill.
+   * @return Backfilled JobConfiguration.
+   */
+  public static IJobConfiguration backfillJobConfiguration(JobConfiguration jobConfig) {
+    backfillTask(jobConfig.getTaskConfig());
+    return IJobConfiguration.build(jobConfig);
+  }
+
+  /**
+   * Backfills set of tasks. See {@link #backfillTask(TaskConfig)}.
+   *
+   * @param tasks Set of tasks to backfill.
+   * @return Backfilled set of tasks.
+   */
+  public static Set<IScheduledTask> backfillTasks(Set<ScheduledTask> tasks) {
+    return tasks.stream()
+        .map(ThriftBackfill::backfillScheduledTask)
+        .map(IScheduledTask::build)
+        .collect(GuavaUtils.toImmutableSet());
+  }
+
+  private static ScheduledTask backfillScheduledTask(ScheduledTask task) {
+    backfillTask(task.getAssignedTask().getTask());
+    return task;
+  }
+
+  /**
+   * Backfills JobUpdate. See {@link #backfillTask(TaskConfig)}.
+   *
+   * @param update JobUpdate to backfill.
+   * @return Backfilled job update.
+   */
+  static IJobUpdate backFillJobUpdate(JobUpdate update) {
+    JobUpdateInstructions instructions = update.getInstructions();
+    if (instructions.isSetDesiredState()) {
+      backfillTask(instructions.getDesiredState().getTask());
+    }
+
+    instructions.getInitialState().forEach(e -> backfillTask(e.getTask()));
+
+    return IJobUpdate.build(update);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/python/apache/aurora/config/thrift.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/config/thrift.py b/src/main/python/apache/aurora/config/thrift.py
index be0cd68..928ca93 100644
--- a/src/main/python/apache/aurora/config/thrift.py
+++ b/src/main/python/apache/aurora/config/thrift.py
@@ -35,6 +35,7 @@ from gen.apache.aurora.api.ttypes import (
     LimitConstraint,
     MesosContainer,
     Metadata,
+    Resource,
     TaskConfig,
     TaskConstraint,
     ValueConstraint
@@ -218,6 +219,10 @@ def convert(job, metadata=frozenset(), ports=frozenset()):
     raise InvalidConfig('Task has invalid resources.  cpu/ramMb/diskMb must all be positive: '
         'cpu:%r ramMb:%r diskMb:%r' % (task.numCpus, task.ramMb, task.diskMb))
 
+  task.resources = frozenset(
+      [Resource(numCpus=task.numCpus), Resource(ramMb=task.ramMb), Resource(diskMb=task.diskMb)] +
+      [Resource(namedPort=p) for p in ports])
+
   task.job = key
   task.owner = owner
   task.requestedPorts = ports

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/python/apache/aurora/tools/java/thrift_wrapper_codegen.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/tools/java/thrift_wrapper_codegen.py b/src/main/python/apache/aurora/tools/java/thrift_wrapper_codegen.py
index 3465fe9..7c28180 100644
--- a/src/main/python/apache/aurora/tools/java/thrift_wrapper_codegen.py
+++ b/src/main/python/apache/aurora/tools/java/thrift_wrapper_codegen.py
@@ -127,12 +127,21 @@ UNION_FIELD_TEMPLATE = '''  public %(type)s %(fn_name)s() {
 UNION_SWITCH_CASE = '''case %(case)s:
         %(body)s'''
 
-UNION_FIELD_SWITCH = '''switch (getSetField()) {
+UNION_DEFAULT_ERROR = 'throw new RuntimeException("Unrecognized field " + getSetField())';
+UNION_FIELD_SWITCH = '''switch (%(switch_by)s) {
       %(cases)s
       default:
-        throw new RuntimeException("Unrecognized field " + getSetField());
+        %(error)s;
     }'''
 
+UNION_COPY_CONSTRUCTOR_2 = '''  public static %(wrapped)s newBuilder(int id, Object value) {
+    %(body)s
+  }'''
+
+UNION_VALUE_ACCESSOR = '''  public Object getRawValue() {
+    return value;
+  }'''
+
 SIMPLE_ASSIGNMENT = 'this.%(field)s = wrapped.%(fn_name)s();'
 
 FIELD_DECLARATION = '''private final %(type)s %(field)s;'''
@@ -142,7 +151,7 @@ STRUCT_ASSIGNMENT = '''this.%(field)s = wrapped.%(isset)s()
 
 
 IMMUTABLE_COLLECTION_DECLARATION = (
-    '''private final Immutable%(collection)s<%(params)s> %(field)s;''')
+  '''private final Immutable%(collection)s<%(params)s> %(field)s;''')
 IMMUTABLE_COLLECTION_ASSIGNMENT = '''this.%(field)s = wrapped.%(isset)s()
         ? Immutable%(collection)s.copyOf(wrapped.%(fn_name)s())
         : Immutable%(collection)s.of();'''
@@ -481,7 +490,7 @@ def generate_union_field(code, struct, field):
                                             'enum_value': field_enum_value})
 
 
-def generate_struct_field(code, struct, field, builder_calls):
+def generate_struct_field(code, field, builder_calls):
   field_type = field.ttype.codegen_name()
   assignment = SIMPLE_ASSIGNMENT
   assignment_args = {
@@ -574,28 +583,58 @@ def generate_java(struct):
   if struct.kind == 'union':
     assign_cases = []
     copy_cases = []
+    copy_2_cases = []
     for field in struct.fields:
       generate_union_field(code, struct, field)
 
-      assign_case_body = 'value = %(codegen_name)s.build(wrapped.%(accessor_method)s());\nbreak;' % {
+      assert field.ttype.immutable or isinstance(field.ttype, StructType), 'Unrecognized type %s' % field.ttype.name
+
+      if field.ttype.immutable:
+        assign_case_body = 'value = wrapped.%s();\nbreak;' % field.accessor_method()
+        copy_case_body = 'return new %s(setField, %s());' % (struct.name, field.accessor_method())
+        copy_2_case_cast = field.ttype.codegen_name()
+      else:
+        assign_case_body = 'value = %(codegen_name)s.build(wrapped.%(accessor_method)s());\nbreak;' % {
           'codegen_name': field.ttype.codegen_name(),
           'accessor_method': field.accessor_method()}
+        copy_case_body = 'return new %s(setField, %s().newBuilder());' % (struct.name, field.accessor_method())
+        code.add_import('org.apache.aurora.gen.%s' % field.ttype.name)
+        copy_2_case_cast = field.ttype.name
+
       assign_cases.append(UNION_SWITCH_CASE % {'case': to_upper_snake_case(field.name),
                                                'body': assign_case_body})
 
-      copy_case_body = 'return new %s(setField, %s().newBuilder());' % (struct.name, field.accessor_method())
       copy_cases.append(UNION_SWITCH_CASE % {'case': to_upper_snake_case(field.name),
                                              'body': copy_case_body})
 
+      copy_2_case_body = 'return %(wrapped)s.%(method)s((%(cast)s) value);' % {
+        'wrapped': struct.name,
+        'method': field.name,
+        'cast': copy_2_case_cast}
+
+      copy_2_cases.append(UNION_SWITCH_CASE % {'case': to_upper_snake_case(field.name),
+                                               'body': copy_2_case_body})
+
     set_field_type = '%s._Fields' % struct.name
     code.add_accessor(FIELD_TEMPLATE % {'type': set_field_type, 'fn_name': 'getSetField', 'field': 'setField'})
     code.add_field(FIELD_DECLARATION % {'field': 'setField', 'type': set_field_type})
     code.add_assignment(SIMPLE_ASSIGNMENT % {'field': 'setField',
                                              'fn_name': 'getSetField'})
     code.add_field(FIELD_DECLARATION % {'field': 'value', 'type': 'Object'})
-    code.add_assignment(UNION_FIELD_SWITCH % {'cases': '\n      '.join(assign_cases)})
+    code.add_assignment(UNION_FIELD_SWITCH % {'cases': '\n      '.join(assign_cases),
+                                              'switch_by': 'getSetField()',
+                                              'error': UNION_DEFAULT_ERROR})
+
+    code.copy_constructor = UNION_FIELD_SWITCH % {'cases': '\n      '.join(copy_cases),
+                                                  'switch_by': 'getSetField()',
+                                                  'error': UNION_DEFAULT_ERROR}
+
+    copy_2_switch = UNION_FIELD_SWITCH % {'cases': '\n      '.join(copy_2_cases),
+                                          'switch_by': '%s.findByThriftId(id)' % set_field_type,
+                                          'error': 'throw new RuntimeException("Unrecognized id " + id)'}
 
-    code.copy_constructor = UNION_FIELD_SWITCH % {'cases': '\n      '.join(copy_cases)}
+    code.add_accessor(UNION_VALUE_ACCESSOR)
+    code.add_accessor(UNION_COPY_CONSTRUCTOR_2 % {'wrapped': struct.name, 'body': copy_2_switch})
 
     code.to_string = '.add("setField", setField).add("value", value)'
     code.equals = 'Objects.equals(setField, other.setField) && Objects.equals(value, other.value)'
@@ -603,7 +642,7 @@ def generate_java(struct):
   else:
     builder_calls = []
     for field in struct.fields:
-      generate_struct_field(code, struct, field, builder_calls)
+      generate_struct_field(code, field, builder_calls)
 
     field_names = [f.name for f in struct.fields]
     code.copy_constructor = 'return new %s()%s;' % (struct.name, '\n        ' + '\n        '.join(builder_calls))

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/main/resources/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.xml
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.xml b/src/main/resources/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.xml
index 36df7ed..4162797 100644
--- a/src/main/resources/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.xml
+++ b/src/main/resources/org/apache/aurora/scheduler/storage/db/TaskConfigMapper.xml
@@ -125,6 +125,13 @@
     <id column="id" />
   </resultMap>
 
+  <resultMap id="resourceMap" type="org.apache.aurora.scheduler.storage.db.views.DBResource">
+    <id column="id" />
+    <result property="type"
+            column="type_id"
+            typeHandler="org.apache.aurora.scheduler.storage.db.typehandlers.ResourceTypeHandler" />
+  </resultMap>
+
   <resultMap id="taskConfigMap" type="org.apache.aurora.scheduler.storage.db.views.DbTaskConfig">
     <id column="id" property="rowId" />
     <result column="creator_user" property="owner.user"/>
@@ -149,6 +156,12 @@
         select="selectTaskLinks"
         column="id"
         foreignColumn="task_config_id"/>
+    <!-- TODO(maxim): move resources to a main join when task level fields are removed. -->
+    <collection
+        property="resources"
+        select="selectResources"
+        column="id"
+        foreignColumn="task_config_id" />
   </resultMap>
 
   <sql id="unscopedConfigSelect">
@@ -265,6 +278,20 @@
     )
   </insert>
 
+  <insert id="insertResources">
+    INSERT INTO task_resource (
+      task_config_id,
+      type_id,
+      value
+    ) VALUES (
+      <foreach item="value" collection="values" separator="),(">
+        #{configId},
+        #{value.first},
+        #{value.second}
+      </foreach>
+    )
+  </insert>
+
   <insert id="insertTaskLinks" >
     INSERT INTO task_config_task_links (
       task_config_id,
@@ -295,6 +322,15 @@
     WHERE task_config_id = #{id}
   </select>
 
+  <select id="selectResources" resultMap="resourceMap">
+    SELECT
+      id,
+      type_id,
+      value
+    FROM task_resource
+    WHERE task_config_id = #{id}
+  </select>
+
   <insert id="insertContainer" useGeneratedKeys="true" keyColumn="id" keyProperty="result.id">
     INSERT INTO task_config_docker_containers (
       task_config_id,

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/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 92a0798..45ec1bf 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
@@ -100,6 +100,22 @@ CREATE TABLE task_configs(
   tier VARCHAR
 );
 
+CREATE TABLE resource_types(
+  id INT PRIMARY KEY,
+  name VARCHAR NOT NULL,
+
+  UNIQUE(name)
+);
+
+CREATE TABLE task_resource(
+  id IDENTITY,
+  task_config_id BIGINT NOT NULL REFERENCES task_configs(id) ON DELETE CASCADE,
+  type_id INT NOT NULL REFERENCES resource_types(id),
+  value VARCHAR NOT NULL,
+
+  UNIQUE(task_config_id, type_id, value)
+);
+
 CREATE TABLE task_constraints(
   id IDENTITY,
   task_config_id BIGINT NOT NULL REFERENCES task_configs(id) ON DELETE CASCADE,

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java b/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
index c310690..1e9e1ae 100644
--- a/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
+++ b/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
@@ -48,6 +48,8 @@ import org.apache.aurora.common.zookeeper.Credentials;
 import org.apache.aurora.common.zookeeper.ServerSetImpl;
 import org.apache.aurora.common.zookeeper.ZooKeeperClient;
 import org.apache.aurora.common.zookeeper.testing.BaseZooKeeperClientTest;
+import org.apache.aurora.gen.HostAttributes;
+import org.apache.aurora.gen.MaintenanceMode;
 import org.apache.aurora.gen.ScheduleStatus;
 import org.apache.aurora.gen.ScheduledTask;
 import org.apache.aurora.gen.ServerInfo;
@@ -73,6 +75,7 @@ import org.apache.aurora.scheduler.mesos.DriverSettings;
 import org.apache.aurora.scheduler.mesos.TestExecutorSettings;
 import org.apache.aurora.scheduler.resources.ResourceSlot;
 import org.apache.aurora.scheduler.storage.backup.BackupModule;
+import org.apache.aurora.scheduler.storage.entities.IHostAttributes;
 import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
 import org.apache.aurora.scheduler.storage.entities.IServerInfo;
 import org.apache.aurora.scheduler.storage.log.EntrySerializer;
@@ -113,6 +116,11 @@ public class SchedulerIT extends BaseZooKeeperClientTest {
   private static final String SERVERSET_PATH = "/fake/service/path";
   private static final String STATS_URL_PREFIX = "fake_url";
   private static final String FRAMEWORK_ID = "integration_test_framework_id";
+  private static final IHostAttributes HOST_ATTRIBUTES = IHostAttributes.build(new HostAttributes()
+      .setHost("host")
+      .setSlaveId("slave-id")
+      .setMode(MaintenanceMode.NONE)
+      .setAttributes(ImmutableSet.of()));
 
   private static final DriverSettings SETTINGS = new DriverSettings(
       "fakemaster",
@@ -276,7 +284,9 @@ public class SchedulerIT extends BaseZooKeeperClientTest {
         status,
         100)
         .newBuilder();
-    builder.getAssignedTask().setSlaveId("slave-id");
+    builder.getAssignedTask()
+        .setSlaveId(HOST_ATTRIBUTES.getSlaveId())
+        .setSlaveHost(HOST_ATTRIBUTES.getHost());
     return IScheduledTask.build(builder);
   }
 
@@ -293,7 +303,9 @@ public class SchedulerIT extends BaseZooKeeperClientTest {
     IScheduledTask snapshotTask = makeTask("snapshotTask", ScheduleStatus.ASSIGNED);
     IScheduledTask transactionTask = makeTask("transactionTask", ScheduleStatus.RUNNING);
     Iterable<Entry> recoveredEntries = toEntries(
-        LogEntry.snapshot(new Snapshot().setTasks(ImmutableSet.of(snapshotTask.newBuilder()))),
+        LogEntry.snapshot(new Snapshot()
+            .setTasks(ImmutableSet.of(snapshotTask.newBuilder()))
+            .setHostAttributes(ImmutableSet.of(HOST_ATTRIBUTES.newBuilder()))),
         LogEntry.transaction(new Transaction(
             ImmutableList.of(Op.saveTasks(
                 new SaveTasks(ImmutableSet.of(transactionTask.newBuilder())))),

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/configuration/ConfigurationManagerTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/configuration/ConfigurationManagerTest.java b/src/test/java/org/apache/aurora/scheduler/configuration/ConfigurationManagerTest.java
index 1ccfe01..7313279 100644
--- a/src/test/java/org/apache/aurora/scheduler/configuration/ConfigurationManagerTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/configuration/ConfigurationManagerTest.java
@@ -44,11 +44,16 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
+import static org.apache.aurora.gen.Resource.diskMb;
+import static org.apache.aurora.gen.Resource.namedPort;
+import static org.apache.aurora.gen.Resource.numCpus;
+import static org.apache.aurora.gen.Resource.ramMb;
 import static org.apache.aurora.gen.test.testConstants.INVALID_IDENTIFIERS;
 import static org.apache.aurora.gen.test.testConstants.VALID_IDENTIFIERS;
 import static org.apache.aurora.scheduler.base.UserProvidedStrings.isGoodIdentifier;
 import static org.apache.aurora.scheduler.configuration.ConfigurationManager.DEDICATED_ATTRIBUTE;
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -100,7 +105,11 @@ public class ConfigurationManagerTest {
                           .setName(DEDICATED_ATTRIBUTE)
                           .setConstraint(TaskConstraint.value(new ValueConstraint(
                               false, ImmutableSet.of("foo"))))))
-              .setOwner(new Identity().setUser("owner-user")));
+              .setOwner(new Identity().setUser("owner-user"))
+              .setResources(ImmutableSet.of(
+                  numCpus(1.0),
+                  ramMb(1),
+                  diskMb(1))));
   private static final ITaskConfig CONFIG_WITH_CONTAINER =
       TaskTestUtil.makeConfig(JobKeys.from("role", "env", "job"));
 
@@ -260,6 +269,35 @@ public class ConfigurationManagerTest {
     CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder));
   }
 
+  @Test
+  public void testTaskResourceBackfill() throws Exception {
+    TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
+    builder.unsetResources();
+
+    assertFalse(builder.isSetResources());
+    ITaskConfig populated =
+        DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder));
+    assertEquals(CONFIG_WITH_CONTAINER.getResources(), populated.getResources());
+  }
+
+  @Test
+  public void testMultipleResourceValuesBlocked() throws Exception {
+    TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
+    builder.addToResources(numCpus(3.0));
+    builder.addToResources(ramMb(72));
+
+    expectTaskDescriptionException("Multiple resource values are not supported for CPU, RAM");
+    DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder));
+  }
+
+  @Test
+  public void testMultipleResourceValuesAllowed() throws Exception {
+    TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
+    builder.addToResources(namedPort("thrift"));
+
+    DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder));
+  }
+
   private void expectTaskDescriptionException(String message) {
     expectedException.expect(TaskDescriptionException.class);
     expectedException.expectMessage(message);

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeConverterTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeConverterTest.java b/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeConverterTest.java
new file mode 100644
index 0000000..78da84a
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeConverterTest.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.resources;
+
+import org.junit.Test;
+
+import static org.apache.aurora.scheduler.resources.ResourceTypeConverter.DOUBLE;
+import static org.apache.aurora.scheduler.resources.ResourceTypeConverter.LONG;
+import static org.apache.aurora.scheduler.resources.ResourceTypeConverter.STRING;
+import static org.junit.Assert.assertEquals;
+
+public class ResourceTypeConverterTest {
+  @Test
+  public void testRoundtrip() {
+    assertEquals(234L, LONG.parseFrom(LONG.stringify(234L)).longValue());
+    assertEquals(2.34, DOUBLE.parseFrom(DOUBLE.stringify(2.34)).doubleValue(), 0.0);
+    assertEquals("http", STRING.parseFrom(STRING.stringify("http")));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeTest.java b/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeTest.java
new file mode 100644
index 0000000..dc9dc66
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/resources/ResourceTypeTest.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.resources;
+
+import org.apache.aurora.gen.Resource;
+import org.apache.aurora.scheduler.storage.entities.IResource;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class ResourceTypeTest {
+  @Test
+  public void testFindValueById() {
+    assertEquals(
+        ResourceType.CPUS,
+        ResourceType.fromIdValue(Resource.numCpus(1.0).getSetField().getThriftFieldId()));
+  }
+
+  @Test
+  public void testFindByResource() {
+    assertEquals(
+        ResourceType.CPUS,
+        ResourceType.fromResource(IResource.build(Resource.numCpus(1.0))));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/storage/backup/RecoveryTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/storage/backup/RecoveryTest.java b/src/test/java/org/apache/aurora/scheduler/storage/backup/RecoveryTest.java
index a33f6f7..e870087 100644
--- a/src/test/java/org/apache/aurora/scheduler/storage/backup/RecoveryTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/storage/backup/RecoveryTest.java
@@ -112,7 +112,7 @@ public class RecoveryTest extends EasyMockTest {
     recovery.stage(backup1);
     assertEquals(
         IScheduledTask.setFromBuilders(SNAPSHOT1.getTasks()),
-        recovery.query(Query.unscoped()));
+        ImmutableSet.copyOf(recovery.query(Query.unscoped())));
     recovery.commit();
     transaction.getValue().apply(storeProvider);
 
@@ -138,11 +138,11 @@ public class RecoveryTest extends EasyMockTest {
     recovery.stage(backup1);
     assertEquals(
         IScheduledTask.setFromBuilders(SNAPSHOT1.getTasks()),
-        recovery.query(Query.unscoped()));
+        ImmutableSet.copyOf(recovery.query(Query.unscoped())));
     recovery.deleteTasks(Query.taskScoped(Tasks.id(TASK2)));
     assertEquals(
         IScheduledTask.setFromBuilders(modified.getTasks()),
-        recovery.query(Query.unscoped()));
+        ImmutableSet.copyOf(recovery.query(Query.unscoped())));
     recovery.commit();
     transaction.getValue().apply(storeProvider);
 

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/storage/log/LogStorageTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/storage/log/LogStorageTest.java b/src/test/java/org/apache/aurora/scheduler/storage/log/LogStorageTest.java
index bf9479d..7ac0b58 100644
--- a/src/test/java/org/apache/aurora/scheduler/storage/log/LogStorageTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/storage/log/LogStorageTest.java
@@ -113,6 +113,8 @@ import org.easymock.Capture;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.apache.aurora.scheduler.base.TaskTestUtil.makeConfig;
+import static org.apache.aurora.scheduler.base.TaskTestUtil.makeTask;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
@@ -224,8 +226,7 @@ public class LogStorageTest extends EasyMockTest {
     schedulingService.doEvery(eq(SNAPSHOT_INTERVAL), capture(snapshotAction));
     Snapshot snapshotContents = new Snapshot()
         .setTimestamp(NOW)
-        .setTasks(ImmutableSet.of(
-            TaskTestUtil.makeTask("task_id", TaskTestUtil.JOB).newBuilder()));
+        .setTasks(ImmutableSet.of(makeTask("task_id", TaskTestUtil.JOB).newBuilder()));
     expect(snapshotStore.createSnapshot()).andReturn(snapshotContents);
     DeduplicatedSnapshot deduplicated =
         new SnapshotDeduplicatorImpl().deduplicate(snapshotContents);
@@ -273,17 +274,23 @@ public class LogStorageTest extends EasyMockTest {
     builder.add(createTransaction(Op.saveFrameworkId(new SaveFrameworkId("bob"))));
     storageUtil.schedulerStore.saveFrameworkId("bob");
 
-    SaveCronJob cronJob = new SaveCronJob().setJobConfig(new JobConfiguration());
+    JobConfiguration actualJob = new JobConfiguration().setTaskConfig(nonBackfilledConfig());
+    JobConfiguration expectedJob =
+        new JobConfiguration().setTaskConfig(makeConfig(JOB_KEY).newBuilder());
+    SaveCronJob cronJob = new SaveCronJob().setJobConfig(actualJob);
     builder.add(createTransaction(Op.saveCronJob(cronJob)));
-    storageUtil.jobStore.saveAcceptedJob(IJobConfiguration.build(cronJob.getJobConfig()));
+    storageUtil.jobStore.saveAcceptedJob(IJobConfiguration.build(expectedJob));
 
     RemoveJob removeJob = new RemoveJob(JOB_KEY.newBuilder());
     builder.add(createTransaction(Op.removeJob(removeJob)));
     storageUtil.jobStore.removeJob(JOB_KEY);
 
-    SaveTasks saveTasks = new SaveTasks(ImmutableSet.of(new ScheduledTask()));
+    ScheduledTask actualTask = makeTask("id", JOB_KEY).newBuilder();
+    actualTask.getAssignedTask().setTask(nonBackfilledConfig());
+    IScheduledTask expectedTask = makeTask("id", JOB_KEY);
+    SaveTasks saveTasks = new SaveTasks(ImmutableSet.of(actualTask));
     builder.add(createTransaction(Op.saveTasks(saveTasks)));
-    storageUtil.taskStore.saveTasks(IScheduledTask.setFromBuilders(saveTasks.getTasks()));
+    storageUtil.taskStore.saveTasks(ImmutableSet.of(expectedTask));
 
     RewriteTask rewriteTask = new RewriteTask("id1", new TaskConfig());
     builder.add(createTransaction(Op.rewriteTask(rewriteTask)));
@@ -326,12 +333,20 @@ public class LogStorageTest extends EasyMockTest {
     builder.add(createTransaction(Op.removeLock(removeLock)));
     storageUtil.lockStore.removeLock(ILockKey.build(removeLock.getLockKey()));
 
-    JobUpdate update = new JobUpdate().setSummary(
-        new JobUpdateSummary().setKey(UPDATE_ID.newBuilder()));
-    SaveJobUpdate saveUpdate = new SaveJobUpdate(update, "token");
+    JobUpdate actualUpdate = new JobUpdate()
+        .setSummary(new JobUpdateSummary().setKey(UPDATE_ID.newBuilder()))
+        .setInstructions(new JobUpdateInstructions()
+            .setInitialState(
+                ImmutableSet.of(new InstanceTaskConfig().setTask(nonBackfilledConfig())))
+            .setDesiredState(new InstanceTaskConfig().setTask(nonBackfilledConfig())));
+    JobUpdate expectedUpdate = actualUpdate.deepCopy();
+    expectedUpdate.getInstructions().getDesiredState().setTask(makeConfig(JOB_KEY).newBuilder());
+    expectedUpdate.getInstructions().getInitialState()
+        .forEach(e -> e.setTask(makeConfig(JOB_KEY).newBuilder()));
+    SaveJobUpdate saveUpdate = new SaveJobUpdate(actualUpdate, "token");
     builder.add(createTransaction(Op.saveJobUpdate(saveUpdate)));
     storageUtil.jobUpdateStore.saveJobUpdate(
-        IJobUpdate.build(saveUpdate.getJobUpdate()),
+        IJobUpdate.build(expectedUpdate),
         Optional.of(saveUpdate.getLockToken()));
 
     SaveJobUpdateEvent saveUpdateEvent =
@@ -371,6 +386,12 @@ public class LogStorageTest extends EasyMockTest {
     expect(stream.readAll()).andReturn(entryBuilder.build().iterator());
   }
 
+  private TaskConfig nonBackfilledConfig() {
+    TaskConfig config = makeConfig(JOB_KEY).newBuilder();
+    config.unsetResources();
+    return config;
+  }
+
   abstract class AbstractStorageFixture {
     private final AtomicBoolean runCalled = new AtomicBoolean(false);
 

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImplIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImplIT.java b/src/test/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImplIT.java
index ff9c1d0..05168ba 100644
--- a/src/test/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImplIT.java
+++ b/src/test/java/org/apache/aurora/scheduler/storage/log/SnapshotStoreImplIT.java
@@ -174,6 +174,15 @@ public class SnapshotStoreImplIT {
     assertEquals(makeComparable(snapshot1), makeComparable(snapshot2));
   }
 
+  @Test
+  public void testBackfill() {
+    setUpStore(false);
+    snapshotStore.applySnapshot(makeNonBackfilled());
+
+    Snapshot backfilled = snapshotStore.createSnapshot();
+    assertEquals(expected(), makeComparable(backfilled));
+  }
+
   private static final IScheduledTask TASK = TaskTestUtil.makeTask("id", JOB_KEY);
   private static final ITaskConfig TASK_CONFIG = TaskTestUtil.makeConfig(JOB_KEY);
   private static final IJobConfiguration CRON_JOB = IJobConfiguration.build(new JobConfiguration()
@@ -244,6 +253,21 @@ public class SnapshotStoreImplIT {
             new StoredJobUpdateDetails(UPDATE.newBuilder(), LOCK.getToken())));
   }
 
+  private Snapshot makeNonBackfilled() {
+    Snapshot snapshot = expected();
+    snapshot.getTasks().forEach(e -> e.getAssignedTask().getTask().unsetResources());
+    snapshot.getCronJobs()
+        .forEach(e -> e.getJobConfiguration().getTaskConfig().unsetResources());
+    snapshot.getJobUpdateDetails()
+        .forEach(e -> e.getDetails().getUpdate().getInstructions()
+            .getDesiredState().getTask().unsetResources());
+    snapshot.getJobUpdateDetails()
+        .forEach(e -> e.getDetails().getUpdate().getInstructions()
+            .getInitialState().forEach(i -> i.getTask().unsetResources()));
+
+    return snapshot;
+  }
+
   private void populateStore() {
     storage.write((NoResult.Quiet) store -> {
       store.getUnsafeTaskStore().saveTasks(ImmutableSet.of(TASK));

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/storage/log/ThriftBackfillTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/storage/log/ThriftBackfillTest.java b/src/test/java/org/apache/aurora/scheduler/storage/log/ThriftBackfillTest.java
new file mode 100644
index 0000000..cc039fb
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/storage/log/ThriftBackfillTest.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.storage.log;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.apache.aurora.gen.TaskConfig;
+import org.junit.Test;
+
+import static org.apache.aurora.gen.Resource.diskMb;
+import static org.apache.aurora.gen.Resource.namedPort;
+import static org.apache.aurora.gen.Resource.numCpus;
+import static org.apache.aurora.gen.Resource.ramMb;
+import static org.junit.Assert.assertEquals;
+
+public class ThriftBackfillTest {
+
+  @Test
+  public void testFieldsToSetNoPorts() {
+    TaskConfig config = new TaskConfig()
+        .setNumCpus(1.0)
+        .setRamMb(32)
+        .setDiskMb(64);
+
+    TaskConfig expected = config.deepCopy();
+    expected.setResources(ImmutableSet.of(numCpus(1.0), ramMb(32), diskMb(64)));
+
+    assertEquals(
+        expected,
+        ThriftBackfill.backfillTask(config));
+  }
+
+  @Test
+  public void testFieldsToSetWithPorts() {
+    TaskConfig config = new TaskConfig()
+        .setNumCpus(1.0)
+        .setRamMb(32)
+        .setDiskMb(64)
+        .setRequestedPorts(ImmutableSet.of("http"));
+
+    TaskConfig expected = config.deepCopy();
+    expected.setResources(ImmutableSet.of(numCpus(1.0), ramMb(32), diskMb(64), namedPort("http")));
+
+    assertEquals(
+        expected,
+        ThriftBackfill.backfillTask(config));
+  }
+
+  @Test
+  public void testSetToFieldsNoPorts() {
+    TaskConfig config = new TaskConfig()
+        .setResources(ImmutableSet.of(numCpus(1.0), ramMb(32), diskMb(64)));
+
+    TaskConfig expected = config.deepCopy()
+        .setNumCpus(1.0)
+        .setRamMb(32)
+        .setDiskMb(64);
+
+    assertEquals(
+        expected,
+        ThriftBackfill.backfillTask(config));
+  }
+
+  @Test
+  public void testSetToFieldsWithPorts() {
+    TaskConfig config = new TaskConfig()
+        .setResources(ImmutableSet.of(numCpus(1.0), ramMb(32), diskMb(64), namedPort("http")));
+
+    TaskConfig expected = config.deepCopy()
+        .setNumCpus(1.0)
+        .setRamMb(32)
+        .setDiskMb(64)
+        .setRequestedPorts(ImmutableSet.of("http"));
+
+    assertEquals(
+        expected,
+        ThriftBackfill.backfillTask(config));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMissingResourceThrows() {
+    TaskConfig config = new TaskConfig().setResources(ImmutableSet.of(numCpus(1.0), ramMb(32)));
+    ThriftBackfill.backfillTask(config);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/java/org/apache/aurora/scheduler/thrift/Fixtures.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/Fixtures.java b/src/test/java/org/apache/aurora/scheduler/thrift/Fixtures.java
index fd6a40c..54585a9 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/Fixtures.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/Fixtures.java
@@ -36,6 +36,7 @@ import org.apache.aurora.gen.JobSummaryResult;
 import org.apache.aurora.gen.JobUpdateKey;
 import org.apache.aurora.gen.LockKey;
 import org.apache.aurora.gen.MesosContainer;
+import org.apache.aurora.gen.Resource;
 import org.apache.aurora.gen.ResourceAggregate;
 import org.apache.aurora.gen.Response;
 import org.apache.aurora.gen.ResponseCode;
@@ -111,7 +112,11 @@ final class Fixtures {
         .setMaxTaskFailures(1)
         .setConstraints(ImmutableSet.of())
         .setMetadata(ImmutableSet.of())
-        .setContainer(Container.mesos(new MesosContainer()));
+        .setContainer(Container.mesos(new MesosContainer()))
+        .setResources(ImmutableSet.of(
+            Resource.numCpus(1),
+            Resource.ramMb(1024),
+            Resource.diskMb(1024)));
   }
 
   static TaskConfig nonProductionTask() {

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/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 50a1fdb..3a2b3f3 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImplTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/ReadOnlySchedulerImplTest.java
@@ -705,11 +705,11 @@ public class ReadOnlySchedulerImplTest extends EasyMockTest {
 
   @Test
   public void testGetJobUpdateDiffWithUpdateAdd() throws Exception {
-    TaskConfig task1 = defaultTask(false).setNumCpus(1.0);
-    TaskConfig task2 = defaultTask(false).setNumCpus(2.0);
-    TaskConfig task3 = defaultTask(false).setNumCpus(3.0);
-    TaskConfig task4 = defaultTask(false).setNumCpus(4.0);
-    TaskConfig task5 = defaultTask(false).setNumCpus(5.0);
+    TaskConfig task1 = defaultTask(false).setMaxTaskFailures(1);
+    TaskConfig task2 = defaultTask(false).setMaxTaskFailures(2);
+    TaskConfig task3 = defaultTask(false).setMaxTaskFailures(3);
+    TaskConfig task4 = defaultTask(false).setMaxTaskFailures(4);
+    TaskConfig task5 = defaultTask(false).setMaxTaskFailures(5);
 
     ImmutableSet.Builder<IScheduledTask> tasks = ImmutableSet.builder();
     makeTasks(0, 10, task1, tasks);
@@ -723,7 +723,7 @@ public class ReadOnlySchedulerImplTest extends EasyMockTest {
 
     control.replay();
 
-    TaskConfig newTask = defaultTask(false).setNumCpus(6.0);
+    TaskConfig newTask = defaultTask(false).setMaxTaskFailures(6);
     JobUpdateRequest request = new JobUpdateRequest()
         .setTaskConfig(newTask)
         .setInstanceCount(60)
@@ -746,9 +746,9 @@ public class ReadOnlySchedulerImplTest extends EasyMockTest {
 
   @Test
   public void testGetJobUpdateDiffWithUpdateRemove() throws Exception {
-    TaskConfig task1 = defaultTask(false).setNumCpus(1.0);
-    TaskConfig task2 = defaultTask(false).setNumCpus(2.0);
-    TaskConfig task3 = defaultTask(false).setNumCpus(3.0);
+    TaskConfig task1 = defaultTask(false).setMaxTaskFailures(1);
+    TaskConfig task2 = defaultTask(false).setMaxTaskFailures(2);
+    TaskConfig task3 = defaultTask(false).setMaxTaskFailures(3);
 
     ImmutableSet.Builder<IScheduledTask> tasks = ImmutableSet.builder();
     makeTasks(0, 10, task1, tasks);
@@ -761,7 +761,7 @@ public class ReadOnlySchedulerImplTest extends EasyMockTest {
     control.replay();
 
     JobUpdateRequest request = new JobUpdateRequest()
-        .setTaskConfig(defaultTask(false).setNumCpus(6.0))
+        .setTaskConfig(defaultTask(false).setMaxTaskFailures(6))
         .setInstanceCount(20)
         .setSettings(new JobUpdateSettings());
 

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/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 e6e79a0..acc68ae 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
@@ -59,6 +59,7 @@ import org.apache.aurora.gen.PulseJobUpdateResult;
 import org.apache.aurora.gen.QueryRecoveryResult;
 import org.apache.aurora.gen.Range;
 import org.apache.aurora.gen.ReadOnlyScheduler;
+import org.apache.aurora.gen.Resource;
 import org.apache.aurora.gen.ResourceAggregate;
 import org.apache.aurora.gen.Response;
 import org.apache.aurora.gen.ResponseDetail;
@@ -504,7 +505,11 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
         .setRequestedPorts(ImmutableSet.of())
         .setTaskLinks(ImmutableMap.of())
         .setConstraints(ImmutableSet.of())
-        .setMaxTaskFailures(0);
+        .setMaxTaskFailures(0)
+        .setResources(ImmutableSet.of(
+            Resource.numCpus(1.0),
+            Resource.ramMb(1024),
+            Resource.diskMb(1024)));
 
     lockManager.assertNotLocked(LOCK_KEY);
     storageUtil.expectTaskFetch(Query.jobScoped(JOB_KEY).active());
@@ -558,7 +563,6 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
         .setAssignedTask(new AssignedTask()
             .setInstanceId(instanceId)
             .setTask(populatedTask()
-                .setRamMb(5)
                 .setIsService(true)
                 .setExecutorConfig(new ExecutorConfig().setData(executorData)))));
   }

http://git-wip-us.apache.org/repos/asf/aurora/blob/ff6e05f0/src/test/python/apache/aurora/client/cli/test_inspect.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_inspect.py b/src/test/python/apache/aurora/client/cli/test_inspect.py
index 452699c..fedc16b 100644
--- a/src/test/python/apache/aurora/client/cli/test_inspect.py
+++ b/src/test/python/apache/aurora/client/cli/test_inspect.py
@@ -89,7 +89,10 @@ Process 'process':
       cmd = AuroraCommandLine()
       assert cmd.execute(['job', 'inspect', '--raw', 'west/bozo/test/hello', 'config.aurora']) == 0
       output = '\n'.join(mock_stdout)
-      assert output == str(job_config.job())
+      # It's impossible to assert string equivalence of two objects with nested un-hashable types.
+      # Given that the only product of --raw flag is the thrift representation of AuroraConfig
+      # it's enough to do a spot check here and let thrift.py tests validate the structure.
+      assert 'TaskConfig' in output
 
   # AURORA-990: Prevent regression of client passing invalid arguments to print_out.
   # Since print_out is the final layer before print(), there's not much else we can do than


Mime
View raw message