From 537d44fe1932c904fafd2f7dd7429ea0fb5f06a2 Mon Sep 17 00:00:00 2001 From: Tim Harper Date: Thu, 19 Jan 2017 19:07:22 +0100 Subject: [PATCH] Disable UnreachableStrategy for resident tasks Summary: Require disabled for resident tasks. Fixes #5163. Partially addresses Test Plan: create resident task. Make it get lost. Ensure that it doesn't come go inactive. Reviewers: aquamatthias, jdef, meichstedt, jenkins Reviewed By: aquamatthias, jdef, meichstedt, jenkins Subscribers: jdef, marathon-team Differential Revision: https://phabricator.mesosphere.com/D488 --- .../api/v2/types/unreachableStrategy.raml | 15 ++-- src/main/java/mesosphere/marathon/Protos.java | 76 ++++++------------- src/main/proto/marathon.proto | 5 +- .../marathon/api/v2/json/Formats.scala | 11 ++- .../marathon/core/instance/Instance.scala | 68 ++++++++--------- .../instance/update/InstanceUpdater.scala | 21 +++-- .../launcher/impl/InstanceOpFactoryImpl.scala | 3 +- .../marathon/core/pod/PodDefinition.scala | 2 +- .../impl/ExpungeOverdueLostTasksActor.scala | 25 +++--- .../raml/UnreachableStrategyConversion.scala | 22 ++++-- .../marathon/state/AppDefinition.scala | 17 ++++- .../marathon/state/UnreachableStrategy.scala | 56 +++++++++----- .../mesosphere/marathon/state/Volume.scala | 2 +- .../marathon/api/v2/PodsResourceTest.scala | 20 +++-- .../v2/json/AppDefinitionFormatsTest.scala | 19 +++-- .../marathon/api/v2/json/AppUpdateTest.scala | 2 +- .../AppDefinitionValidationTest.scala | 2 +- .../api/validation/RunSpecValidatorTest.scala | 3 +- .../core/appinfo/TaskCountsTest.scala | 11 ++- .../core/appinfo/TaskLifeTimeTest.scala | 7 +- .../core/appinfo/TaskStatsByVersionTest.scala | 8 +- .../appinfo/impl/AppInfoBaseDataTest.scala | 4 +- .../core/health/HealthCheckTest.scala | 2 +- .../impl/HealthCheckWorkerActorTest.scala | 6 +- .../core/instance/InstanceFormatTest.scala | 49 ++++++------ .../core/instance/InstanceStateTest.scala | 16 ++-- .../marathon/core/instance/InstanceTest.scala | 9 ++- .../core/instance/TestInstanceBuilder.scala | 5 +- .../instance/update/InstanceUpdaterTest.scala | 12 +-- .../ExpungeOverdueLostTasksActorTest.scala | 46 ++++++----- .../steps/PostToEventStreamStepImplTest.scala | 6 +- .../TaskUnreachableIntegrationTest.scala | 4 +- .../raml/PodStatusConversionTest.scala | 3 +- .../UnreachableStrategyConversionTest.scala | 10 +-- .../marathon/state/AppDefinitionTest.scala | 2 +- .../state/UnreachableStrategyTest.scala | 31 +++++--- .../tasks/InstanceOpFactoryImplTest.scala | 4 +- .../marathon/test/MarathonTestHelper.scala | 3 +- 38 files changed, 347 insertions(+), 260 deletions(-) diff --git a/docs/docs/rest-api/public/api/v2/types/unreachableStrategy.raml b/docs/docs/rest-api/public/api/v2/types/unreachableStrategy.raml index ec71ad9b918..120ca1f2aea 100644 --- a/docs/docs/rest-api/public/api/v2/types/unreachableStrategy.raml +++ b/docs/docs/rest-api/public/api/v2/types/unreachableStrategy.raml @@ -1,6 +1,10 @@ #%RAML 1.0 Library types: - UnreachableStrategy: + UnreachableStrategy: (UnreachableDisabled | UnreachableEnabled) + UnreachableDisabled: + type: string + enum: [ disabled ] + UnreachableEnabled: type: object properties: inactiveAfterSeconds?: @@ -13,8 +17,7 @@ types: as inactive. This will trigger a new instance launch. The original task is not expunged yet. Must be less than expungeAfterSeconds. - The default value is set to 5 minutes for ephemeral tasks (300 seconds). - The default value is set to 1 hour for resident tasks (3600 seconds). + The default value is set to 5 minutes (300 seconds). expungeAfterSeconds?: type: integer @@ -26,8 +29,4 @@ types: it will be killed if it ever comes back. Instances are usually marked as unreachable before they are expunged but they don't have to. This value is required to be greater than inactiveAfterSeconds. - The default value is set to 10 minutes for ephemeral tasks (600 seconds). - The default value is set to 7 days for resident tasks (604800 seconds). - - If the instance has any persistent volumes associated with it, then they will be destroyed and associated data - will be deleted. + The default value is set to 10 minutes (600 seconds). diff --git a/src/main/java/mesosphere/marathon/Protos.java b/src/main/java/mesosphere/marathon/Protos.java index 89dc2470a04..ce638434e9b 100644 --- a/src/main/java/mesosphere/marathon/Protos.java +++ b/src/main/java/mesosphere/marathon/Protos.java @@ -7949,12 +7949,6 @@ public final boolean isInitialized() { return false; } } - if (hasUnreachableStrategy()) { - if (!getUnreachableStrategy().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } memoizedIsInitialized = 1; return true; } @@ -9103,12 +9097,6 @@ public final boolean isInitialized() { return false; } } - if (hasUnreachableStrategy()) { - if (!getUnreachableStrategy().isInitialized()) { - - return false; - } - } return true; } @@ -12921,9 +12909,9 @@ public mesosphere.marathon.Protos.UnreachableStrategyOrBuilder getUnreachableStr public interface UnreachableStrategyOrBuilder extends com.google.protobuf.MessageOrBuilder { - // required uint64 inactiveAfterSeconds = 1 [default = 900]; + // optional uint64 inactiveAfterSeconds = 1 [default = 900]; /** - * required uint64 inactiveAfterSeconds = 1 [default = 900]; + * optional uint64 inactiveAfterSeconds = 1 [default = 900]; * *
      * 15 minutes
@@ -12931,7 +12919,7 @@ public interface UnreachableStrategyOrBuilder
      */
     boolean hasInactiveAfterSeconds();
     /**
-     * required uint64 inactiveAfterSeconds = 1 [default = 900];
+     * optional uint64 inactiveAfterSeconds = 1 [default = 900];
      *
      * 
      * 15 minutes
@@ -12939,9 +12927,9 @@ public interface UnreachableStrategyOrBuilder
      */
     long getInactiveAfterSeconds();
 
-    // required uint64 expungeAfterSeconds = 2 [default = 604800];
+    // optional uint64 expungeAfterSeconds = 2 [default = 604800];
     /**
-     * required uint64 expungeAfterSeconds = 2 [default = 604800];
+     * optional uint64 expungeAfterSeconds = 2 [default = 604800];
      *
      * 
      * 7 days
@@ -12949,7 +12937,7 @@ public interface UnreachableStrategyOrBuilder
      */
     boolean hasExpungeAfterSeconds();
     /**
-     * required uint64 expungeAfterSeconds = 2 [default = 604800];
+     * optional uint64 expungeAfterSeconds = 2 [default = 604800];
      *
      * 
      * 7 days
@@ -13058,11 +13046,11 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     private int bitField0_;
-    // required uint64 inactiveAfterSeconds = 1 [default = 900];
+    // optional uint64 inactiveAfterSeconds = 1 [default = 900];
     public static final int INACTIVEAFTERSECONDS_FIELD_NUMBER = 1;
     private long inactiveAfterSeconds_;
     /**
-     * required uint64 inactiveAfterSeconds = 1 [default = 900];
+     * optional uint64 inactiveAfterSeconds = 1 [default = 900];
      *
      * 
      * 15 minutes
@@ -13072,7 +13060,7 @@ public boolean hasInactiveAfterSeconds() {
       return ((bitField0_ & 0x00000001) == 0x00000001);
     }
     /**
-     * required uint64 inactiveAfterSeconds = 1 [default = 900];
+     * optional uint64 inactiveAfterSeconds = 1 [default = 900];
      *
      * 
      * 15 minutes
@@ -13082,11 +13070,11 @@ public long getInactiveAfterSeconds() {
       return inactiveAfterSeconds_;
     }
 
-    // required uint64 expungeAfterSeconds = 2 [default = 604800];
+    // optional uint64 expungeAfterSeconds = 2 [default = 604800];
     public static final int EXPUNGEAFTERSECONDS_FIELD_NUMBER = 2;
     private long expungeAfterSeconds_;
     /**
-     * required uint64 expungeAfterSeconds = 2 [default = 604800];
+     * optional uint64 expungeAfterSeconds = 2 [default = 604800];
      *
      * 
      * 7 days
@@ -13096,7 +13084,7 @@ public boolean hasExpungeAfterSeconds() {
       return ((bitField0_ & 0x00000002) == 0x00000002);
     }
     /**
-     * required uint64 expungeAfterSeconds = 2 [default = 604800];
+     * optional uint64 expungeAfterSeconds = 2 [default = 604800];
      *
      * 
      * 7 days
@@ -13115,14 +13103,6 @@ public final boolean isInitialized() {
       byte isInitialized = memoizedIsInitialized;
       if (isInitialized != -1) return isInitialized == 1;
 
-      if (!hasInactiveAfterSeconds()) {
-        memoizedIsInitialized = 0;
-        return false;
-      }
-      if (!hasExpungeAfterSeconds()) {
-        memoizedIsInitialized = 0;
-        return false;
-      }
       memoizedIsInitialized = 1;
       return true;
     }
@@ -13336,14 +13316,6 @@ public Builder mergeFrom(mesosphere.marathon.Protos.UnreachableStrategy other) {
       }
 
       public final boolean isInitialized() {
-        if (!hasInactiveAfterSeconds()) {
-
-          return false;
-        }
-        if (!hasExpungeAfterSeconds()) {
-
-          return false;
-        }
         return true;
       }
 
@@ -13366,10 +13338,10 @@ public Builder mergeFrom(
       }
       private int bitField0_;
 
-      // required uint64 inactiveAfterSeconds = 1 [default = 900];
+      // optional uint64 inactiveAfterSeconds = 1 [default = 900];
       private long inactiveAfterSeconds_ = 900L;
       /**
-       * required uint64 inactiveAfterSeconds = 1 [default = 900];
+       * optional uint64 inactiveAfterSeconds = 1 [default = 900];
        *
        * 
        * 15 minutes
@@ -13379,7 +13351,7 @@ public boolean hasInactiveAfterSeconds() {
         return ((bitField0_ & 0x00000001) == 0x00000001);
       }
       /**
-       * required uint64 inactiveAfterSeconds = 1 [default = 900];
+       * optional uint64 inactiveAfterSeconds = 1 [default = 900];
        *
        * 
        * 15 minutes
@@ -13389,7 +13361,7 @@ public long getInactiveAfterSeconds() {
         return inactiveAfterSeconds_;
       }
       /**
-       * required uint64 inactiveAfterSeconds = 1 [default = 900];
+       * optional uint64 inactiveAfterSeconds = 1 [default = 900];
        *
        * 
        * 15 minutes
@@ -13402,7 +13374,7 @@ public Builder setInactiveAfterSeconds(long value) {
         return this;
       }
       /**
-       * required uint64 inactiveAfterSeconds = 1 [default = 900];
+       * optional uint64 inactiveAfterSeconds = 1 [default = 900];
        *
        * 
        * 15 minutes
@@ -13415,10 +13387,10 @@ public Builder clearInactiveAfterSeconds() {
         return this;
       }
 
-      // required uint64 expungeAfterSeconds = 2 [default = 604800];
+      // optional uint64 expungeAfterSeconds = 2 [default = 604800];
       private long expungeAfterSeconds_ = 604800L;
       /**
-       * required uint64 expungeAfterSeconds = 2 [default = 604800];
+       * optional uint64 expungeAfterSeconds = 2 [default = 604800];
        *
        * 
        * 7 days
@@ -13428,7 +13400,7 @@ public boolean hasExpungeAfterSeconds() {
         return ((bitField0_ & 0x00000002) == 0x00000002);
       }
       /**
-       * required uint64 expungeAfterSeconds = 2 [default = 604800];
+       * optional uint64 expungeAfterSeconds = 2 [default = 604800];
        *
        * 
        * 7 days
@@ -13438,7 +13410,7 @@ public long getExpungeAfterSeconds() {
         return expungeAfterSeconds_;
       }
       /**
-       * required uint64 expungeAfterSeconds = 2 [default = 604800];
+       * optional uint64 expungeAfterSeconds = 2 [default = 604800];
        *
        * 
        * 7 days
@@ -13451,7 +13423,7 @@ public Builder setExpungeAfterSeconds(long value) {
         return this;
       }
       /**
-       * required uint64 expungeAfterSeconds = 2 [default = 604800];
+       * optional uint64 expungeAfterSeconds = 2 [default = 604800];
        *
        * 
        * 7 days
@@ -43799,8 +43771,8 @@ public Builder setSecretIdBytes(
       "nce\022\033\n\023taskKillGracePeriod\030\037 \001(\003\022E\n\023unre" +
       "achableStrategy\030  \001(\0132(.mesosphere.marat" +
       "hon.UnreachableStrategy\"]\n\023UnreachableSt" +
-      "rategy\022!\n\024inactiveAfterSeconds\030\001 \002(\004:\00390" +
-      "0\022#\n\023expungeAfterSeconds\030\002 \002(\004:\006604800\"\024" +
+      "rategy\022!\n\024inactiveAfterSeconds\030\001 \001(\004:\00390" +
+      "0\022#\n\023expungeAfterSeconds\030\002 \001(\004:\006604800\"\024" +
       "\n\004Json\022\014\n\004json\030\001 \002(\t\"\035\n\rResourceRoles\022\014\n" +
       "\004role\030\001 \003(\t\"\346\t\n\014MarathonTask\022\n\n\002id\030\001 \002(\t" +
       "\022\025\n\rOBSOLETE_host\030\002 \001(\t\022\r\n\005ports\030\003 \003(\r\022-" +
diff --git a/src/main/proto/marathon.proto b/src/main/proto/marathon.proto
index f19107c8e8a..2a246b4de64 100644
--- a/src/main/proto/marathon.proto
+++ b/src/main/proto/marathon.proto
@@ -118,8 +118,9 @@ message ServiceDefinition {
 }
 
 message UnreachableStrategy {
-  required uint64 inactiveAfterSeconds = 1 [default = 900 ]; // 15 minutes
-  required uint64 expungeAfterSeconds = 2 [default = 604800 ]; // 7 days
+  // UnreachableDisabled is represented by both of these fields missing
+  optional uint64 inactiveAfterSeconds = 1 [default = 900 ]; // 15 minutes
+  optional uint64 expungeAfterSeconds = 2 [default = 604800 ]; // 7 days
 }
 
 // we serialize PodDefinition and Instances as json, only required for legacy content
diff --git a/src/main/scala/mesosphere/marathon/api/v2/json/Formats.scala b/src/main/scala/mesosphere/marathon/api/v2/json/Formats.scala
index 97e8d732982..c0dd8b30a0f 100644
--- a/src/main/scala/mesosphere/marathon/api/v2/json/Formats.scala
+++ b/src/main/scala/mesosphere/marathon/api/v2/json/Formats.scala
@@ -15,8 +15,7 @@ import mesosphere.marathon.core.pod.PodDefinition
 import mesosphere.marathon.core.readiness.ReadinessCheck
 import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.state.NetworkInfo
-import mesosphere.marathon.raml.{ Pod, Raml, Resources, UnreachableStrategy, KillSelection }
-import mesosphere.marathon.state
+import mesosphere.marathon.raml.{ Pod, Raml, Resources, KillSelection }
 import mesosphere.marathon.state._
 import mesosphere.marathon.upgrade.DeploymentManager.DeploymentStepInfo
 import mesosphere.marathon.upgrade._
@@ -1010,7 +1009,7 @@ trait AppAndGroupFormats {
             dependencies: Set[PathId],
             maybePorts: Option[Seq[Int]],
             upgradeStrategy: Option[UpgradeStrategy],
-            unreachableStrategy: Option[UnreachableStrategy],
+            unreachableStrategy: Option[raml.UnreachableStrategy],
             killSelection: Option[KillSelection],
             labels: Map[String, String],
             acceptedResourceRoles: Set[String],
@@ -1039,7 +1038,7 @@ trait AppAndGroupFormats {
             (__ \ "dependencies").readNullable[Set[PathId]].withDefault(AppDefinition.DefaultDependencies) ~
             (__ \ "ports").readNullable[Seq[Int]](uniquePorts) ~
             (__ \ "upgradeStrategy").readNullable[UpgradeStrategy] ~
-            (__ \ "unreachableStrategy").readNullable[UnreachableStrategy] ~
+            (__ \ "unreachableStrategy").readNullable[raml.UnreachableStrategy] ~
             (__ \ "killSelection").readNullable[KillSelection] ~
             (__ \ "labels").readNullable[Map[String, String]].withDefault(AppDefinition.Labels.Default) ~
             (__ \ "acceptedResourceRoles").readNullable[Set[String]](nonEmpty).withDefault(Set.empty[String]) ~
@@ -1081,7 +1080,7 @@ trait AppAndGroupFormats {
               }
           }
 
-          def defaultUnreachableStrategy = state.UnreachableStrategy.default(app.persistentVolumes.nonEmpty)
+          def defaultUnreachableStrategy = UnreachableStrategy.default(app.persistentVolumes.nonEmpty)
 
           app.copy(
             fetch = fetch,
@@ -1368,7 +1367,7 @@ trait AppAndGroupFormats {
     (__ \ "container").readNullable[Container] ~
     (__ \ "healthChecks").readNullable[Set[HealthCheck]] ~
     (__ \ "dependencies").readNullable[Set[PathId]] ~
-    (__ \ "unreachableStrategy").readNullable[UnreachableStrategy] ~
+    (__ \ "unreachableStrategy").readNullable[raml.UnreachableStrategy] ~
     (__ \ "killSelection").readNullable[KillSelection]
   ) ((id, cmd, args, user, env, instances, cpus, mem, disk, gpus, executor, constraints, storeUrls, requirePorts,
       backoffSeconds, backoffFactor, maxLaunchDelaySeconds, container, healthChecks, dependencies, unreachableStrategy,
diff --git a/src/main/scala/mesosphere/marathon/core/instance/Instance.scala b/src/main/scala/mesosphere/marathon/core/instance/Instance.scala
index 94064063a0a..3987f10970f 100644
--- a/src/main/scala/mesosphere/marathon/core/instance/Instance.scala
+++ b/src/main/scala/mesosphere/marathon/core/instance/Instance.scala
@@ -7,9 +7,10 @@ import com.fasterxml.uuid.{ EthernetAddress, Generators }
 import mesosphere.marathon.core.condition.Condition
 import mesosphere.marathon.core.instance.Instance.{ AgentInfo, InstanceState }
 import mesosphere.marathon.core.task.Task
-import mesosphere.marathon.state.{ MarathonState, PathId, Timestamp, UnreachableStrategy }
+import mesosphere.marathon.state.{ MarathonState, PathId, Timestamp, UnreachableStrategy, UnreachableDisabled, UnreachableEnabled }
 import mesosphere.marathon.stream._
 import mesosphere.mesos.Placed
+import mesosphere.marathon.raml.Raml
 import org.apache._
 import org.apache.mesos.Protos.Attribute
 import play.api.libs.json._
@@ -26,7 +27,7 @@ case class Instance(
     state: InstanceState,
     tasksMap: Map[Task.Id, Task],
     runSpecVersion: Timestamp,
-    unreachableStrategy: UnreachableStrategy = UnreachableStrategy.defaultEphemeral) extends MarathonState[Protos.Json, Instance] with Placed {
+    unreachableStrategy: UnreachableStrategy) extends MarathonState[Protos.Json, Instance] with Placed {
 
   val runSpecId: PathId = instanceId.runSpecId
   val isLaunched: Boolean = state.condition.isActive
@@ -70,18 +71,6 @@ object Instance {
 
   import mesosphere.marathon.api.v2.json.Formats.TimestampFormat
 
-  @SuppressWarnings(Array("LooksLikeInterpolatedString"))
-  def apply(): Instance = {
-    // required for legacy store, remove when legacy storage is removed.
-    new Instance(
-      // need to provide an Id that passes the regex parser but would never overlap with a user-specified value
-      Instance.Id("$none.marathon-0"),
-      AgentInfo("", None, Nil),
-      InstanceState(Condition.Unknown, Timestamp.zero, activeSince = None, healthy = None),
-      Map.empty[Task.Id, Task],
-      Timestamp.zero)
-  }
-
   def instancesById(tasks: Seq[Instance]): Map[Instance.Id, Instance] =
     tasks.map(task => task.instanceId -> task)(collection.breakOut)
 
@@ -133,12 +122,12 @@ object Instance {
       maybeOldState: Option[InstanceState],
       newTaskMap: Map[Task.Id, Task],
       now: Timestamp,
-      unreachableInactiveAfter: FiniteDuration = 5.minutes): InstanceState = {
+      unreachableStrategy: UnreachableStrategy): InstanceState = {
 
       val tasks = newTaskMap.values
 
       // compute the new instance condition
-      val condition = conditionFromTasks(tasks, now, unreachableInactiveAfter)
+      val condition = conditionFromTasks(tasks, now, unreachableStrategy)
 
       val active: Option[Timestamp] = activeSince(tasks)
 
@@ -152,14 +141,16 @@ object Instance {
     /**
       * @return condition for instance with tasks.
       */
-    def conditionFromTasks(tasks: Iterable[Task], now: Timestamp, unreachableInactiveAfter: FiniteDuration): Condition = {
+    def conditionFromTasks(tasks: Iterable[Task], now: Timestamp, unreachableStrategy: UnreachableStrategy): Condition = {
       if (tasks.isEmpty) {
         Condition.Unknown
       } else {
         // The smallest Condition according to conditionOrdering is the condition for the whole instance.
         tasks.view.map(_.status.condition).minBy(conditionHierarchy) match {
-          case Condition.Unreachable if shouldBecomeInactive(tasks, now, unreachableInactiveAfter) => Condition.UnreachableInactive
-          case condition => condition
+          case Condition.Unreachable if shouldBecomeInactive(tasks, now, unreachableStrategy) =>
+            Condition.UnreachableInactive
+          case condition =>
+            condition
         }
       }
     }
@@ -177,9 +168,12 @@ object Instance {
     /**
       * @return if one of tasks has been UnreachableInactive for more than unreachableInactiveAfter.
       */
-    def shouldBecomeInactive(tasks: Iterable[Task], now: Timestamp, unreachableInactiveAfter: FiniteDuration): Boolean = {
-      tasks.exists(_.isUnreachableExpired(now, unreachableInactiveAfter))
-    }
+    def shouldBecomeInactive(tasks: Iterable[Task], now: Timestamp, unreachableStrategy: UnreachableStrategy): Boolean =
+      unreachableStrategy match {
+        case UnreachableDisabled => false
+        case unreachableEnabled: UnreachableEnabled =>
+          tasks.exists(_.isUnreachableExpired(now, unreachableEnabled.inactiveAfter))
+      }
   }
 
   private[this] def isRunningUnhealthy(task: Task): Boolean = {
@@ -313,14 +307,22 @@ object Instance {
     }
   }
 
-  implicit val unreachableStrategyFormat: Format[UnreachableStrategy] = Json.format[UnreachableStrategy]
-
   implicit val agentFormat: Format[AgentInfo] = Json.format[AgentInfo]
   implicit val idFormat: Format[Instance.Id] = Json.format[Instance.Id]
   implicit val instanceConditionFormat: Format[Condition] = Json.format[Condition]
   implicit val instanceStateFormat: Format[InstanceState] = Json.format[InstanceState]
 
-  implicit val instanceJsonWrites: Writes[Instance] = Json.writes[Instance]
+  implicit val instanceJsonWrites: Writes[Instance] = {
+    (
+      (__ \ "instanceId").write[Instance.Id] ~
+      (__ \ "agentInfo").write[AgentInfo] ~
+      (__ \ "tasksMap").write[Map[Task.Id, Task]] ~
+      (__ \ "runSpecVersion").write[Timestamp] ~
+      (__ \ "state").write[InstanceState] ~
+      (__ \ "unreachableStrategy").write[raml.UnreachableStrategy]
+    ) { (i) => (i.instanceId, i.agentInfo, i.tasksMap, i.runSpecVersion, i.state, Raml.toRaml(i.unreachableStrategy)) }
+  }
+
   implicit val unreachableStrategyReads: Reads[Instance] = {
     (
       (__ \ "instanceId").read[Instance.Id] ~
@@ -328,9 +330,10 @@ object Instance {
       (__ \ "tasksMap").read[Map[Task.Id, Task]] ~
       (__ \ "runSpecVersion").read[Timestamp] ~
       (__ \ "state").read[InstanceState] ~
-      (__ \ "unreachableStrategy").readNullable[UnreachableStrategy]
+      (__ \ "unreachableStrategy").readNullable[raml.UnreachableStrategy]
     ) { (instanceId, agentInfo, tasksMap, runSpecVersion, state, maybeUnreachableStrategy) =>
-        val unreachableStrategy = maybeUnreachableStrategy.getOrElse(UnreachableStrategy.defaultEphemeral)
+        val unreachableStrategy = maybeUnreachableStrategy.
+          map(Raml.fromRaml(_)).getOrElse(UnreachableStrategy.default())
         new Instance(instanceId, agentInfo, state, tasksMap, runSpecVersion, unreachableStrategy)
       }
   }
@@ -362,18 +365,11 @@ object Instance {
   * @param tasksMap a map of one key/value pair consisting of the actual task
   * @param runSpecVersion the version of the task related runSpec
   */
-class LegacyAppInstance(
-  instanceId: Instance.Id,
-  agentInfo: Instance.AgentInfo,
-  state: InstanceState,
-  tasksMap: Map[Task.Id, Task],
-  runSpecVersion: Timestamp) extends Instance(instanceId, agentInfo, state, tasksMap, runSpecVersion)
-
 object LegacyAppInstance {
-  def apply(task: Task, agentInfo: AgentInfo, unreachableStrategy: UnreachableStrategy = UnreachableStrategy.defaultEphemeral): Instance = {
+  def apply(task: Task, agentInfo: AgentInfo, unreachableStrategy: UnreachableStrategy): Instance = {
     val since = task.status.startedAt.getOrElse(task.status.stagedAt)
     val tasksMap = Map(task.taskId -> task)
-    val state = Instance.InstanceState(None, tasksMap, since)
+    val state = Instance.InstanceState(None, tasksMap, since, unreachableStrategy)
 
     new Instance(task.taskId.instanceId, agentInfo, state, tasksMap, task.runSpecVersion, unreachableStrategy)
   }
diff --git a/src/main/scala/mesosphere/marathon/core/instance/update/InstanceUpdater.scala b/src/main/scala/mesosphere/marathon/core/instance/update/InstanceUpdater.scala
index 0662c16928c..601917f705d 100644
--- a/src/main/scala/mesosphere/marathon/core/instance/update/InstanceUpdater.scala
+++ b/src/main/scala/mesosphere/marathon/core/instance/update/InstanceUpdater.scala
@@ -6,7 +6,7 @@ import mesosphere.marathon.core.instance.Instance
 import mesosphere.marathon.core.instance.update.InstanceUpdateOperation.{ LaunchEphemeral, LaunchOnReservation, MesosUpdate, Reserve }
 import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.update.{ TaskUpdateEffect, TaskUpdateOperation }
-import mesosphere.marathon.state.Timestamp
+import mesosphere.marathon.state.{ Timestamp, UnreachableEnabled }
 
 /**
   * Provides methods that apply a given [[InstanceUpdateOperation]]
@@ -18,7 +18,7 @@ object InstanceUpdater extends StrictLogging {
     val updatedTasks = instance.tasksMap.updated(updatedTask.taskId, updatedTask)
     instance.copy(
       tasksMap = updatedTasks,
-      state = Instance.InstanceState(Some(instance.state), updatedTasks, now, instance.unreachableStrategy.inactiveAfter))
+      state = Instance.InstanceState(Some(instance.state), updatedTasks, now, instance.unreachableStrategy))
   }
 
   private[marathon] def launchEphemeral(op: LaunchEphemeral, now: Timestamp): InstanceUpdateEffect = {
@@ -48,11 +48,22 @@ object InstanceUpdater extends StrictLogging {
           }
 
         // We might still become UnreachableInactive.
-        case TaskUpdateEffect.Noop if op.condition == Condition.Unreachable && instance.state.condition != Condition.UnreachableInactive =>
+        case TaskUpdateEffect.Noop if op.condition == Condition.Unreachable &&
+          instance.state.condition != Condition.UnreachableInactive =>
           val updated: Instance = updatedInstance(instance, task, now)
           if (updated.state.condition == Condition.UnreachableInactive) {
-            logger.info(s"${updated.instanceId} is updated to UnreachableInactive after being Unreachable for more than ${updated.unreachableStrategy.inactiveAfter.toSeconds} seconds.")
-            val events = eventsGenerator.events(updated, Some(task), now, previousCondition = Some(instance.state.condition))
+            updated.unreachableStrategy match {
+              case u: UnreachableEnabled =>
+                logger.info(
+                  s"${updated.instanceId} is updated to UnreachableInactive after being Unreachable for more than ${u.inactiveAfter.toSeconds} seconds.")
+              case _ =>
+                // We shouldn't get here
+                logger.error(
+                  s"${updated.instanceId} is updated to UnreachableInactive in spite of there being no UnreachableStrategy")
+
+            }
+            val events = eventsGenerator.events(
+              updated, Some(task), now, previousCondition = Some(instance.state.condition))
             InstanceUpdateEffect.Update(updated, oldState = Some(instance), events)
           } else {
             InstanceUpdateEffect.Noop(instance.instanceId)
diff --git a/src/main/scala/mesosphere/marathon/core/launcher/impl/InstanceOpFactoryImpl.scala b/src/main/scala/mesosphere/marathon/core/launcher/impl/InstanceOpFactoryImpl.scala
index a5ae6282b90..e647f313d95 100644
--- a/src/main/scala/mesosphere/marathon/core/launcher/impl/InstanceOpFactoryImpl.scala
+++ b/src/main/scala/mesosphere/marathon/core/launcher/impl/InstanceOpFactoryImpl.scala
@@ -262,7 +262,8 @@ class InstanceOpFactoryImpl(
         healthy = None
       ),
       tasksMap = Map(task.taskId -> task),
-      runSpecVersion = runSpec.version
+      runSpecVersion = runSpec.version,
+      unreachableStrategy = runSpec.unreachableStrategy
     )
     val stateOp = InstanceUpdateOperation.Reserve(instance)
     taskOperationFactory.reserveAndCreateVolumes(frameworkId, stateOp, resourceMatch.resources, localVolumes)
diff --git a/src/main/scala/mesosphere/marathon/core/pod/PodDefinition.scala b/src/main/scala/mesosphere/marathon/core/pod/PodDefinition.scala
index febf3b3e57b..701f1f89639 100644
--- a/src/main/scala/mesosphere/marathon/core/pod/PodDefinition.scala
+++ b/src/main/scala/mesosphere/marathon/core/pod/PodDefinition.scala
@@ -117,6 +117,6 @@ object PodDefinition {
   val DefaultNetworks = Seq.empty[Network]
   val DefaultBackoffStrategy = BackoffStrategy()
   val DefaultUpgradeStrategy = AppDefinition.DefaultUpgradeStrategy
-  val DefaultUnreachableStrategy = UnreachableStrategy.defaultEphemeral
+  val DefaultUnreachableStrategy = UnreachableStrategy.default(resident = false)
 
 }
diff --git a/src/main/scala/mesosphere/marathon/core/task/jobs/impl/ExpungeOverdueLostTasksActor.scala b/src/main/scala/mesosphere/marathon/core/task/jobs/impl/ExpungeOverdueLostTasksActor.scala
index 0f44312b638..727cd331c3b 100644
--- a/src/main/scala/mesosphere/marathon/core/task/jobs/impl/ExpungeOverdueLostTasksActor.scala
+++ b/src/main/scala/mesosphere/marathon/core/task/jobs/impl/ExpungeOverdueLostTasksActor.scala
@@ -11,7 +11,7 @@ import mesosphere.marathon.core.instance.update.InstanceUpdateOperation
 import mesosphere.marathon.core.task.jobs.TaskJobsConfig
 import mesosphere.marathon.core.task.tracker.{ InstanceTracker, TaskStateOpProcessor }
 import mesosphere.marathon.core.task.tracker.InstanceTracker.SpecInstances
-import mesosphere.marathon.state.{ PathId, Timestamp }
+import mesosphere.marathon.state.{ PathId, Timestamp, UnreachableEnabled, UnreachableDisabled }
 
 /**
   * Business logic of overdue tasks actor.
@@ -33,15 +33,20 @@ trait ExpungeOverdueLostTasksActorLogic {
   }
 
   /**
-    * @return instances that have been UnreachableInactive according to the RunSpec definition.
+    * @return instances that should be expunged according to the RunSpec definition.
     */
-  def filterOverdueUnreachableInactive(instances: Map[PathId, SpecInstances], now: Timestamp) =
-    instances.values.flatMap(_.instances)
-      .withFilter(_.isUnreachableInactive)
-      .withFilter { instance =>
-        val unreachableExpungeAfter = instance.unreachableStrategy.expungeAfter
-        instance.tasksMap.valuesIterator.exists(_.isUnreachableExpired(now, unreachableExpungeAfter))
-      }
+  def filterUnreachableForExpunge(instances: Map[PathId, SpecInstances], now: Timestamp) =
+    instances.values.
+      flatMap(_.instances).
+      withFilter { i => shouldExpunge(i, now) }
+
+  private[impl] def shouldExpunge(instance: Instance, now: Timestamp): Boolean = instance.unreachableStrategy match {
+    case UnreachableDisabled =>
+      false
+    case unreachableEnabled: UnreachableEnabled =>
+      instance.isUnreachableInactive &&
+        instance.tasksMap.valuesIterator.exists(_.isUnreachableExpired(now, unreachableEnabled.expungeAfter))
+  }
 }
 
 class ExpungeOverdueLostTasksActor(
@@ -70,7 +75,7 @@ class ExpungeOverdueLostTasksActor(
   override def receive: Receive = {
     case Tick => instanceTracker.instancesBySpec() pipeTo self
     case InstanceTracker.InstancesBySpec(instances) =>
-      filterOverdueUnreachableInactive(instances, clock.now()).foreach(triggerExpunge)
+      filterUnreachableForExpunge(instances, clock.now()).foreach(triggerExpunge)
   }
 }
 
diff --git a/src/main/scala/mesosphere/marathon/raml/UnreachableStrategyConversion.scala b/src/main/scala/mesosphere/marathon/raml/UnreachableStrategyConversion.scala
index 031f050adaf..ebfc05493ad 100644
--- a/src/main/scala/mesosphere/marathon/raml/UnreachableStrategyConversion.scala
+++ b/src/main/scala/mesosphere/marathon/raml/UnreachableStrategyConversion.scala
@@ -8,16 +8,22 @@ import scala.concurrent.duration._
   */
 trait UnreachableStrategyConversion {
 
-  implicit val ramlUnreachableStrategyRead = Reads[UnreachableStrategy, state.UnreachableStrategy] { strategy =>
-    state.UnreachableStrategy(
-      inactiveAfter = strategy.inactiveAfterSeconds.seconds,
-      expungeAfter = strategy.expungeAfterSeconds.seconds)
+  implicit val ramlUnreachableStrategyRead = Reads[UnreachableStrategy, state.UnreachableStrategy] {
+    case strategy: UnreachableEnabled =>
+      state.UnreachableEnabled(
+        inactiveAfter = strategy.inactiveAfterSeconds.seconds,
+        expungeAfter = strategy.expungeAfterSeconds.seconds)
+    case _: UnreachableDisabled =>
+      state.UnreachableDisabled
   }
 
-  implicit val ramlUnreachableStrategyWrite = Writes[state.UnreachableStrategy, UnreachableStrategy]{ strategy =>
-    UnreachableStrategy(
-      inactiveAfterSeconds = strategy.inactiveAfter.toSeconds,
-      expungeAfterSeconds = strategy.expungeAfter.toSeconds)
+  implicit val ramlUnreachableStrategyWrite = Writes[state.UnreachableStrategy, UnreachableStrategy]{
+    case strategy: state.UnreachableEnabled =>
+      UnreachableEnabled(
+        inactiveAfterSeconds = strategy.inactiveAfter.toSeconds,
+        expungeAfterSeconds = strategy.expungeAfter.toSeconds)
+    case state.UnreachableDisabled =>
+      UnreachableDisabled("disabled")
   }
 }
 
diff --git a/src/main/scala/mesosphere/marathon/state/AppDefinition.scala b/src/main/scala/mesosphere/marathon/state/AppDefinition.scala
index 0255e2b6c0b..d1c611dcc6d 100644
--- a/src/main/scala/mesosphere/marathon/state/AppDefinition.scala
+++ b/src/main/scala/mesosphere/marathon/state/AppDefinition.scala
@@ -221,7 +221,11 @@ case class AppDefinition(
       if (proto.getPortsCount > 0) PortDefinitions(proto.getPortsList.map(_.intValue)(collection.breakOut): _*)
       else proto.getPortDefinitionsList.map(PortDefinitionSerializer.fromProto).to[Seq]
 
-    val unreachableStrategy = if (proto.hasUnreachableStrategy) UnreachableStrategy.fromProto(proto.getUnreachableStrategy) else UnreachableStrategy.defaultEphemeral
+    val unreachableStrategy =
+      if (proto.hasUnreachableStrategy)
+        UnreachableStrategy.fromProto(proto.getUnreachableStrategy)
+      else
+        UnreachableStrategy.default(residencyOption.isDefined)
 
     AppDefinition(
       id = PathId(proto.getId),
@@ -467,7 +471,7 @@ object AppDefinition extends GeneralPurposeCombinators {
 
   val DefaultSecrets = Map.empty[String, Secret]
 
-  val DefaultUnreachableStrategy = UnreachableStrategy.defaultEphemeral
+  val DefaultUnreachableStrategy = UnreachableStrategy.default(resident = false)
 
   object Labels {
     val Default = Map.empty[String, String]
@@ -656,6 +660,14 @@ object AppDefinition extends GeneralPurposeCombinators {
         appDef.healthChecks.count(_.isInstanceOf[MesosCommandHealthCheck])) <= 1
     }
 
+  private[state] val requireUnreachableDisabledForResidentTasks =
+    isTrue[AppDefinition]("unreachableStrategy must be disabled for resident tasks") { app =>
+      if (app.isResident)
+        app.unreachableStrategy == UnreachableDisabled
+      else
+        true
+    }
+
   private def validBasicAppDefinition(enabledFeatures: Set[String]) = validator[AppDefinition] { appDef =>
     appDef.upgradeStrategy is valid
     appDef.container.each is valid(Container.validContainer(enabledFeatures))
@@ -682,6 +694,7 @@ object AppDefinition extends GeneralPurposeCombinators {
     appDef must complyWithResidencyRules
     appDef must complyWithSingleInstanceLabelRules
     appDef must complyWithUpgradeStrategyRules
+    appDef should requireUnreachableDisabledForResidentTasks
     appDef.constraints.each must complyWithConstraintRules
     appDef.ipAddress must optional(complyWithIpAddressRules(appDef))
     appDef.unreachableStrategy is valid
diff --git a/src/main/scala/mesosphere/marathon/state/UnreachableStrategy.scala b/src/main/scala/mesosphere/marathon/state/UnreachableStrategy.scala
index b053dadd027..cb03ee6636c 100644
--- a/src/main/scala/mesosphere/marathon/state/UnreachableStrategy.scala
+++ b/src/main/scala/mesosphere/marathon/state/UnreachableStrategy.scala
@@ -1,17 +1,27 @@
 package mesosphere.marathon
 package state
 
+import com.wix.accord._
 import com.wix.accord.dsl._
 
 import scala.concurrent.duration._
 import mesosphere.marathon.Protos
 
+sealed trait UnreachableStrategy {
+  def toProto: Protos.UnreachableStrategy
+}
+
+case object UnreachableDisabled extends UnreachableStrategy {
+  val toProto: Protos.UnreachableStrategy =
+    Protos.UnreachableStrategy.getDefaultInstance
+}
+
 /**
   * Defines the time outs for unreachable tasks.
   */
-case class UnreachableStrategy(
-    inactiveAfter: FiniteDuration = UnreachableStrategy.DefaultEphemeralInactiveAfter,
-    expungeAfter: FiniteDuration = UnreachableStrategy.DefaultEphemeralExpungeAfter) {
+case class UnreachableEnabled(
+    inactiveAfter: FiniteDuration = UnreachableEnabled.DefaultInactiveAfter,
+    expungeAfter: FiniteDuration = UnreachableEnabled.DefaultExpungeAfter) extends UnreachableStrategy {
 
   def toProto: Protos.UnreachableStrategy =
     Protos.UnreachableStrategy.newBuilder.
@@ -19,28 +29,38 @@ case class UnreachableStrategy(
       setInactiveAfterSeconds(inactiveAfter.toSeconds).
       build
 }
+object UnreachableEnabled {
+  val DefaultInactiveAfter: FiniteDuration = 5.minutes
+  val DefaultExpungeAfter: FiniteDuration = 10.minutes
+  val default = UnreachableEnabled()
 
-object UnreachableStrategy {
-  val DefaultEphemeralInactiveAfter: FiniteDuration = 5.minutes
-  val DefaultEphemeralExpungeAfter: FiniteDuration = 10.minutes
-  val DefaultResidentInactiveAfter: FiniteDuration = 1.hour
-  val DefaultResidentExpungeAfter: FiniteDuration = 7.days
-
-  val defaultEphemeral = UnreachableStrategy(DefaultEphemeralInactiveAfter, DefaultEphemeralExpungeAfter)
-  val defaultResident = UnreachableStrategy(DefaultResidentInactiveAfter, DefaultResidentExpungeAfter)
-
-  implicit val unreachableStrategyValidator = validator[UnreachableStrategy] { strategy =>
+  implicit val unreachableEnabledValidator = validator[UnreachableEnabled] { strategy =>
     strategy.inactiveAfter should be >= 1.second
     strategy.inactiveAfter should be < strategy.expungeAfter
   }
+}
+
+object UnreachableStrategy {
 
-  def default(resident: Boolean): UnreachableStrategy = {
-    if (resident) defaultResident else defaultEphemeral
+  def default(resident: Boolean = false): UnreachableStrategy = {
+    if (resident) UnreachableDisabled else UnreachableEnabled.default
   }
 
   def fromProto(unreachableStrategyProto: Protos.UnreachableStrategy): UnreachableStrategy = {
-    UnreachableStrategy(
-      inactiveAfter = unreachableStrategyProto.getInactiveAfterSeconds.seconds,
-      expungeAfter = unreachableStrategyProto.getExpungeAfterSeconds.seconds)
+    if (unreachableStrategyProto.hasInactiveAfterSeconds && unreachableStrategyProto.hasExpungeAfterSeconds)
+      UnreachableEnabled(
+        inactiveAfter = unreachableStrategyProto.getInactiveAfterSeconds.seconds,
+        expungeAfter = unreachableStrategyProto.getExpungeAfterSeconds.seconds)
+    else
+      UnreachableDisabled
+  }
+
+  implicit val unreachableStrategyValidator = new Validator[UnreachableStrategy] {
+    def apply(strategy: UnreachableStrategy): Result = strategy match {
+      case UnreachableDisabled =>
+        Success
+      case unreachableEnabled: UnreachableEnabled =>
+        validate(unreachableEnabled)
+    }
   }
 }
diff --git a/src/main/scala/mesosphere/marathon/state/Volume.scala b/src/main/scala/mesosphere/marathon/state/Volume.scala
index fe6c2405153..836048fe6ab 100644
--- a/src/main/scala/mesosphere/marathon/state/Volume.scala
+++ b/src/main/scala/mesosphere/marathon/state/Volume.scala
@@ -6,7 +6,7 @@ import java.util.regex.Pattern
 import com.wix.accord._
 import com.wix.accord.dsl._
 import mesosphere.marathon.Protos.Constraint
-import mesosphere.marathon.api.v2.Validation.{ oneOf, _ }
+import mesosphere.marathon.api.v2.Validation._
 import mesosphere.marathon.core.externalvolume.ExternalVolumes
 import mesosphere.marathon.stream._
 import org.apache.mesos.Protos.Resource.DiskInfo.Source
diff --git a/src/test/scala/mesosphere/marathon/api/v2/PodsResourceTest.scala b/src/test/scala/mesosphere/marathon/api/v2/PodsResourceTest.scala
index 1804226e75f..07e61b50695 100644
--- a/src/test/scala/mesosphere/marathon/api/v2/PodsResourceTest.scala
+++ b/src/test/scala/mesosphere/marathon/api/v2/PodsResourceTest.scala
@@ -22,7 +22,7 @@ import mesosphere.marathon.metrics.Metrics
 import mesosphere.marathon.plugin.auth.{ Authenticator, Authorizer }
 import mesosphere.marathon.raml.{ ExecutorResources, FixedPodScalingPolicy, NetworkMode, Pod, Raml }
 import mesosphere.marathon.state.PathId._
-import mesosphere.marathon.state.Timestamp
+import mesosphere.marathon.state.{ Timestamp, UnreachableStrategy }
 import mesosphere.marathon.test.Mockito
 import mesosphere.marathon.upgrade.DeploymentPlan
 import mesosphere.marathon.util.SemanticVersion
@@ -309,8 +309,13 @@ class PodsResourceTest extends AkkaUnitTest with Mockito {
         "attempting to kill a single instance" in {
           implicit val killer = mock[TaskKiller]
           val f = Fixture()
-          val instance = Instance(Instance.Id.forRunSpec("/id1".toRootPath), Instance.AgentInfo("", None, Nil),
-            InstanceState(Condition.Running, Timestamp.now(), Some(Timestamp.now()), None), Map.empty, runSpecVersion = Timestamp.now())
+          val instance = Instance(
+            Instance.Id.forRunSpec("/id1".toRootPath), Instance.AgentInfo("", None, Nil),
+            InstanceState(Condition.Running, Timestamp.now(), Some(Timestamp.now()), None),
+            Map.empty,
+            runSpecVersion = Timestamp.now(),
+            unreachableStrategy = UnreachableStrategy.default()
+          )
           killer.kill(any, any, any)(any) returns Future.successful(Seq(instance))
           val response = f.podsResource.killInstance("/id", instance.instanceId.toString, f.auth.request)
           withClue(s"response body: ${response.getEntity}") {
@@ -323,9 +328,14 @@ class PodsResourceTest extends AkkaUnitTest with Mockito {
           implicit val killer = mock[TaskKiller]
           val instances = Seq(
             Instance(Instance.Id.forRunSpec("/id1".toRootPath), Instance.AgentInfo("", None, Nil),
-              InstanceState(Condition.Running, Timestamp.now(), Some(Timestamp.now()), None), Map.empty, runSpecVersion = Timestamp.now()),
+              InstanceState(Condition.Running, Timestamp.now(), Some(Timestamp.now()), None), Map.empty,
+              runSpecVersion = Timestamp.now(),
+              unreachableStrategy = UnreachableStrategy.default()
+            ),
             Instance(Instance.Id.forRunSpec("/id1".toRootPath), Instance.AgentInfo("", None, Nil),
-              InstanceState(Condition.Running, Timestamp.now(), Some(Timestamp.now()), None), Map.empty, runSpecVersion = Timestamp.now()))
+              InstanceState(Condition.Running, Timestamp.now(), Some(Timestamp.now()), None), Map.empty,
+              runSpecVersion = Timestamp.now(),
+              unreachableStrategy = UnreachableStrategy.default()))
 
           val f = Fixture()
 
diff --git a/src/test/scala/mesosphere/marathon/api/v2/json/AppDefinitionFormatsTest.scala b/src/test/scala/mesosphere/marathon/api/v2/json/AppDefinitionFormatsTest.scala
index 3b7f915d189..8a935f61657 100644
--- a/src/test/scala/mesosphere/marathon/api/v2/json/AppDefinitionFormatsTest.scala
+++ b/src/test/scala/mesosphere/marathon/api/v2/json/AppDefinitionFormatsTest.scala
@@ -478,7 +478,17 @@ class AppDefinitionFormatsTest
     (json \ "secrets" \ "secret3" \ "source").as[String] should equal("/foo2")
   }
 
-  test("FromJSON should parse unreachable instance strategy") {
+  test("FromJSON should parse unreachable disabled instance strategy") {
+    val appDef = Json.parse(
+      """{
+        |  "id": "test",
+        |  "unreachableStrategy": "disabled"
+        |}""".stripMargin).as[AppDefinition]
+
+    appDef.unreachableStrategy should be(UnreachableDisabled)
+  }
+
+  test("FromJSON should parse unreachable enabled instance strategy") {
     val appDef = Json.parse(
       """{
         |  "id": "test",
@@ -488,12 +498,11 @@ class AppDefinitionFormatsTest
         |  }
         |}""".stripMargin).as[AppDefinition]
 
-    appDef.unreachableStrategy.inactiveAfter should be(10.minutes)
-    appDef.unreachableStrategy.expungeAfter should be(20.minutes)
+    appDef.unreachableStrategy should be(UnreachableEnabled(inactiveAfter = 10.minutes, expungeAfter = 20.minutes))
   }
 
-  test("ToJSON should serialize unreachable instance strategy") {
-    val strategy = UnreachableStrategy(6.minutes, 12.minutes)
+  test("ToJSON should serialize unreachable instance strategy") in {
+    val strategy = UnreachableEnabled(6.minutes, 12.minutes)
     val appDef = AppDefinition(id = PathId("test"), unreachableStrategy = strategy)
 
     val json = Json.toJson(appDef)
diff --git a/src/test/scala/mesosphere/marathon/api/v2/json/AppUpdateTest.scala b/src/test/scala/mesosphere/marathon/api/v2/json/AppUpdateTest.scala
index cf071be8b02..5e9001ea5a9 100644
--- a/src/test/scala/mesosphere/marathon/api/v2/json/AppUpdateTest.scala
+++ b/src/test/scala/mesosphere/marathon/api/v2/json/AppUpdateTest.scala
@@ -144,7 +144,7 @@ class AppUpdateTest extends MarathonSpec with Matchers {
           ports = Seq(Port(name = "http", number = 80, protocol = "tcp"))
         )
       )),
-      unreachableStrategy = Some(UnreachableStrategy(998.seconds, 999.seconds))
+      unreachableStrategy = Some(UnreachableEnabled(998.seconds, 999.seconds))
     )
     JsonTestHelper.assertSerializationRoundtripWorks(update1)
   }
diff --git a/src/test/scala/mesosphere/marathon/api/validation/AppDefinitionValidationTest.scala b/src/test/scala/mesosphere/marathon/api/validation/AppDefinitionValidationTest.scala
index 2368bdd3faa..4772fc98f0a 100644
--- a/src/test/scala/mesosphere/marathon/api/validation/AppDefinitionValidationTest.scala
+++ b/src/test/scala/mesosphere/marathon/api/validation/AppDefinitionValidationTest.scala
@@ -27,7 +27,7 @@ class AppDefinitionValidationTest extends UnitTest with ResultMatchers {
         val app = AppDefinition(
           id = PathId("/test"),
           cmd = Some("sleep 1000"),
-          unreachableStrategy = UnreachableStrategy(0.second))
+          unreachableStrategy = UnreachableEnabled(0.seconds))
 
         val expectedViolation = GroupViolationMatcher(description = "unreachableStrategy", constraint = "is invalid")
         validator(app) should failWith(expectedViolation)
diff --git a/src/test/scala/mesosphere/marathon/api/validation/RunSpecValidatorTest.scala b/src/test/scala/mesosphere/marathon/api/validation/RunSpecValidatorTest.scala
index a28b0002f08..1a78bec021d 100644
--- a/src/test/scala/mesosphere/marathon/api/validation/RunSpecValidatorTest.scala
+++ b/src/test/scala/mesosphere/marathon/api/validation/RunSpecValidatorTest.scala
@@ -764,7 +764,8 @@ class RunSpecValidatorTest extends MarathonSpec with Matchers with GivenWhenThen
         id = PathId(id),
         cmd = Some("test"),
         container = Some(Container.Mesos(volumes)),
-        residency = Some(Residency(123, Protos.ResidencyDefinition.TaskLostBehavior.RELAUNCH_AFTER_TIMEOUT))
+        residency = Some(Residency(123, Protos.ResidencyDefinition.TaskLostBehavior.RELAUNCH_AFTER_TIMEOUT)),
+        unreachableStrategy = UnreachableStrategy.default(resident = true)
       )
     }
     val vol1 = persistentVolume("foo")
diff --git a/src/test/scala/mesosphere/marathon/core/appinfo/TaskCountsTest.scala b/src/test/scala/mesosphere/marathon/core/appinfo/TaskCountsTest.scala
index feb20c36d96..fcf7ca3f60f 100644
--- a/src/test/scala/mesosphere/marathon/core/appinfo/TaskCountsTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/appinfo/TaskCountsTest.scala
@@ -6,13 +6,13 @@ import mesosphere.marathon.core.instance.Instance.AgentInfo
 import mesosphere.marathon.core.instance.{ Instance, LegacyAppInstance, TestTaskBuilder }
 import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.state.NetworkInfoPlaceholder
-import mesosphere.marathon.state.{ PathId, Timestamp }
-import mesosphere.marathon.test.{ MarathonSpec, Mockito }
+import mesosphere.marathon.state.{ PathId, Timestamp, UnreachableStrategy }
+import mesosphere.marathon.test.MarathonSpec
 import org.scalatest.{ GivenWhenThen, Matchers }
 
 import scala.collection.immutable.Seq
 
-class TaskCountsTest extends MarathonSpec with GivenWhenThen with Mockito with Matchers {
+class TaskCountsTest extends MarathonSpec with GivenWhenThen with Matchers {
   import mesosphere.marathon.core.appinfo.Fixture.TaskImplicits
 
   test("count no tasks") {
@@ -186,7 +186,10 @@ class TaskCountsTest extends MarathonSpec with GivenWhenThen with Mockito with M
 
 object Fixture {
   implicit class TaskImplicits(val task: Task) extends AnyVal {
-    def toInstance: Instance = LegacyAppInstance(task, AgentInfo(host = "host", agentId = Some("agent"), attributes = Nil))
+    def toInstance: Instance = LegacyAppInstance(
+      task, AgentInfo(host = "host", agentId = Some("agent"), attributes = Nil),
+      unreachableStrategy = UnreachableStrategy.default(resident = task.reservationWithVolumes.nonEmpty)
+    )
   }
 }
 
diff --git a/src/test/scala/mesosphere/marathon/core/appinfo/TaskLifeTimeTest.scala b/src/test/scala/mesosphere/marathon/core/appinfo/TaskLifeTimeTest.scala
index ed165ba13f7..7157b0d3a9b 100644
--- a/src/test/scala/mesosphere/marathon/core/appinfo/TaskLifeTimeTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/appinfo/TaskLifeTimeTest.scala
@@ -5,7 +5,7 @@ import mesosphere.marathon.core.base.ConstantClock
 import mesosphere.marathon.core.instance.Instance.AgentInfo
 import mesosphere.marathon.core.instance.{ Instance, LegacyAppInstance, TestTaskBuilder }
 import mesosphere.marathon.core.task.Task
-import mesosphere.marathon.state.{ PathId, Timestamp }
+import mesosphere.marathon.state.{ PathId, Timestamp, UnreachableStrategy }
 import mesosphere.marathon.test.{ MarathonSpec, Mockito }
 import org.scalatest.{ GivenWhenThen, Matchers }
 
@@ -18,13 +18,14 @@ class TaskLifeTimeTest extends MarathonSpec with Mockito with GivenWhenThen with
   }
 
   private[this] def stagedInstance(): Instance = {
-    LegacyAppInstance(TestTaskBuilder.Helper.stagedTask(newTaskId()), agentInfo)
+    LegacyAppInstance(TestTaskBuilder.Helper.stagedTask(newTaskId()), agentInfo, UnreachableStrategy.default())
   }
 
   private[this] def runningInstanceWithLifeTime(lifeTimeSeconds: Double): Instance = {
     LegacyAppInstance(
       TestTaskBuilder.Helper.runningTask(newTaskId(), startedAt = (now.millis - lifeTimeSeconds * 1000.0).round),
-      agentInfo
+      agentInfo,
+      UnreachableStrategy.default()
     )
   }
 
diff --git a/src/test/scala/mesosphere/marathon/core/appinfo/TaskStatsByVersionTest.scala b/src/test/scala/mesosphere/marathon/core/appinfo/TaskStatsByVersionTest.scala
index cdf28979316..ab325dd8fac 100644
--- a/src/test/scala/mesosphere/marathon/core/appinfo/TaskStatsByVersionTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/appinfo/TaskStatsByVersionTest.scala
@@ -5,7 +5,7 @@ import mesosphere.marathon.core.base.ConstantClock
 import mesosphere.marathon.core.health.Health
 import mesosphere.marathon.core.instance.Instance.AgentInfo
 import mesosphere.marathon.core.task.Task
-import mesosphere.marathon.state.{ PathId, Timestamp, VersionInfo }
+import mesosphere.marathon.state.{ PathId, Timestamp, UnreachableStrategy, VersionInfo }
 import mesosphere.marathon.core.instance.{ Instance, LegacyAppInstance, TestTaskBuilder }
 import mesosphere.marathon.test.MarathonSpec
 import org.scalatest.{ GivenWhenThen, Matchers }
@@ -103,6 +103,10 @@ class TaskStatsByVersionTest extends MarathonSpec with GivenWhenThen with Matche
   private[this] def runningInstanceStartedAt(version: Timestamp, startingDelay: FiniteDuration): Instance = {
     val startedAt = (version + startingDelay).millis
     val agentInfo = AgentInfo(host = "host", agentId = Some("agent"), attributes = Nil)
-    LegacyAppInstance(TestTaskBuilder.Helper.runningTask(newTaskId(), appVersion = version, startedAt = startedAt), agentInfo)
+    LegacyAppInstance(
+      TestTaskBuilder.Helper.runningTask(newTaskId(), appVersion = version, startedAt = startedAt),
+      agentInfo,
+      unreachableStrategy = UnreachableStrategy.default()
+    )
   }
 }
diff --git a/src/test/scala/mesosphere/marathon/core/appinfo/impl/AppInfoBaseDataTest.scala b/src/test/scala/mesosphere/marathon/core/appinfo/impl/AppInfoBaseDataTest.scala
index 6ccfe3300e3..7fba546a76b 100644
--- a/src/test/scala/mesosphere/marathon/core/appinfo/impl/AppInfoBaseDataTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/appinfo/impl/AppInfoBaseDataTest.scala
@@ -416,7 +416,8 @@ class AppInfoBaseDataTest extends FunTest with GroupCreation {
       agentInfo = Instance.AgentInfo("", None, Nil),
       state = InstanceState(None, tasks, f.clock.now()),
       tasksMap = tasks,
-      runSpecVersion = pod.version)
+      runSpecVersion = pod.version,
+      unreachableStrategy = UnreachableStrategy.default())
   }
 
   test("pod statuses xref the correct spec versions") {
@@ -467,5 +468,4 @@ class AppInfoBaseDataTest extends FunTest with GroupCreation {
 
     maybeStatus3 should be ('empty)
   }
-
 }
diff --git a/src/test/scala/mesosphere/marathon/core/health/HealthCheckTest.scala b/src/test/scala/mesosphere/marathon/core/health/HealthCheckTest.scala
index aad556ea6a6..36318a6efd2 100644
--- a/src/test/scala/mesosphere/marathon/core/health/HealthCheckTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/health/HealthCheckTest.scala
@@ -298,7 +298,7 @@ class HealthCheckTest extends MarathonSpec {
       val hostPorts = Seq(4321)
       t.copy(status = t.status.copy(networkInfo = NetworkInfo(hostName, hostPorts, ipAddresses = Nil)))
     }
-    val instance = LegacyAppInstance(task, agentInfo)
+    val instance = LegacyAppInstance(task, agentInfo, unreachableStrategy = UnreachableStrategy.default())
 
     assert(check.effectivePort(app, instance) == 4321)
   }
diff --git a/src/test/scala/mesosphere/marathon/core/health/impl/HealthCheckWorkerActorTest.scala b/src/test/scala/mesosphere/marathon/core/health/impl/HealthCheckWorkerActorTest.scala
index 517a4113d49..6ca3b28b08d 100644
--- a/src/test/scala/mesosphere/marathon/core/health/impl/HealthCheckWorkerActorTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/health/impl/HealthCheckWorkerActorTest.scala
@@ -9,7 +9,7 @@ import mesosphere.marathon.core.instance.Instance.AgentInfo
 import mesosphere.marathon.core.instance.{ LegacyAppInstance, TestTaskBuilder }
 import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.state.NetworkInfo
-import mesosphere.marathon.state.{ AppDefinition, PathId }
+import mesosphere.marathon.state.{ AppDefinition, PathId, UnreachableStrategy }
 import mesosphere.marathon.test.{ MarathonActorSupport, MarathonSpec }
 import org.scalatest.Matchers
 
@@ -43,7 +43,7 @@ class HealthCheckWorkerActorTest
       val hostPorts = Seq(socketPort)
       t.copy(status = t.status.copy(networkInfo = NetworkInfo(hostName, hostPorts, ipAddresses = Nil)))
     }
-    val instance = LegacyAppInstance(task, agentInfo)
+    val instance = LegacyAppInstance(task, agentInfo, unreachableStrategy = UnreachableStrategy.default())
 
     val ref = TestActorRef[HealthCheckWorkerActor](Props(classOf[HealthCheckWorkerActor]))
     ref ! HealthCheckJob(app, instance, MarathonTcpHealthCheck(portIndex = Some(PortReference(0))))
@@ -73,7 +73,7 @@ class HealthCheckWorkerActorTest
       val hostPorts = Seq(socketPort)
       t.copy(status = t.status.copy(networkInfo = NetworkInfo(hostName, hostPorts, ipAddresses = Nil)))
     }
-    val instance = LegacyAppInstance(task, agentInfo)
+    val instance = LegacyAppInstance(task, agentInfo, unreachableStrategy = UnreachableStrategy.default())
 
     val ref = TestActorRef[HealthCheckWorkerActor](Props(classOf[HealthCheckWorkerActor]))
     ref ! HealthCheckJob(app, instance, MarathonTcpHealthCheck(portIndex = Some(PortReference(0))))
diff --git a/src/test/scala/mesosphere/marathon/core/instance/InstanceFormatTest.scala b/src/test/scala/mesosphere/marathon/core/instance/InstanceFormatTest.scala
index 49ded13d542..de5d3535281 100644
--- a/src/test/scala/mesosphere/marathon/core/instance/InstanceFormatTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/instance/InstanceFormatTest.scala
@@ -2,42 +2,45 @@ package mesosphere.marathon
 package core.instance
 
 import mesosphere.UnitTest
-import mesosphere.marathon.state.UnreachableStrategy
+import mesosphere.marathon.state.{ UnreachableStrategy, UnreachableDisabled, UnreachableEnabled }
 import play.api.libs.json._
 
 import scala.concurrent.duration._
 
 class InstanceFormatTest extends UnitTest {
-
   import Instance._
 
-  "Instance.unreachableStrategyFormat" should {
-    "parse a proper JSON" in {
-      val json = Json.parse("""{ "inactiveAfter": 1, "expungeAfter": 2 }""")
-      json.as[UnreachableStrategy].inactiveAfter should be(1.second)
-      json.as[UnreachableStrategy].expungeAfter should be(2.seconds)
-    }
+  val template = Json.parse(
+    """
+      |{
+      |  "instanceId": { "idString": "app.instance-1337" },
+      |  "tasksMap": {},
+      |  "runSpecVersion": "2015-01-01",
+      |  "agentInfo": { "host": "localhost", "attributes": [] },
+      |  "state": { "since": "2015-01-01", "condition": { "str": "Running" } }
+      |}""".stripMargin).as[JsObject]
+
+  "Instance.instanceFormat" should {
+    "parse a valid unreachable strategy" in {
+      val json = template ++ Json.obj(
+        "unreachableStrategy" -> Json.obj(
+          "inactiveAfterSeconds" -> 1, "expungeAfterSeconds" -> 2))
+      val instance = json.as[Instance]
 
-    "not parse a JSON with empty fields" in {
-      val json = Json.parse("""{ "unreachableExpungeAfter": 2 }""")
-      a[JsResultException] should be thrownBy { json.as[UnreachableStrategy] }
+      instance.unreachableStrategy shouldBe (UnreachableEnabled(inactiveAfter = 1.second, expungeAfter = 2.seconds))
     }
 
-  }
+    "parse a disabled unreachable strategy" in {
+      val json = template ++ Json.obj("unreachableStrategy" -> "disabled")
+      val instance = json.as[Instance]
+
+      instance.unreachableStrategy shouldBe (UnreachableDisabled)
+    }
 
-  "Instance.instanceFormat" should {
     "fill UnreachableStrategy with defaults if empty" in {
-      val json = Json.parse(
-        """{ "instanceId": { "idString": "app.instance-1337" },
-          |  "tasksMap": {},
-          |  "runSpecVersion": "2015-01-01",
-          |  "agentInfo": { "host": "localhost", "attributes": [] },
-          |  "state": { "since": "2015-01-01", "condition": { "str": "Running" } }
-          |}""".stripMargin)
-      val instance = json.as[Instance]
+      val instance = template.as[Instance]
 
-      instance.unreachableStrategy.inactiveAfter should be(UnreachableStrategy.DefaultEphemeralInactiveAfter)
-      instance.unreachableStrategy.expungeAfter should be(UnreachableStrategy.DefaultEphemeralExpungeAfter)
+      instance.unreachableStrategy shouldBe (UnreachableStrategy.default(resident = false))
     }
   }
 }
diff --git a/src/test/scala/mesosphere/marathon/core/instance/InstanceStateTest.scala b/src/test/scala/mesosphere/marathon/core/instance/InstanceStateTest.scala
index f335b685d3a..824bc026ae3 100644
--- a/src/test/scala/mesosphere/marathon/core/instance/InstanceStateTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/instance/InstanceStateTest.scala
@@ -6,7 +6,7 @@ import mesosphere.marathon.core.base.ConstantClock
 import mesosphere.marathon.core.condition.Condition
 import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.bus.MesosTaskStatusTestHelper
-import mesosphere.marathon.state.Timestamp
+import mesosphere.marathon.state.{ Timestamp, UnreachableEnabled, UnreachableStrategy }
 import mesosphere.marathon.state.PathId._
 import org.scalatest.prop.TableDrivenPropertyChecks
 
@@ -29,7 +29,7 @@ class InstanceStateTest extends UnitTest with TableDrivenPropertyChecks {
             task.taskId -> ephemeralTask.copy(status = newStatus)
         }(collection.breakOut)
 
-      val state = Instance.InstanceState(None, tasks, f.clock.now())
+      val state = Instance.InstanceState(None, tasks, f.clock.now(), UnreachableStrategy.default())
 
       "set the oldest task timestamp as the activeSince timestamp" in { state.activeSince should be(Some(f.clock.now - 1.hour)) }
       "set the instance condition to running" in { state.condition should be(Condition.Running) }
@@ -39,7 +39,7 @@ class InstanceStateTest extends UnitTest with TableDrivenPropertyChecks {
       val f = new Fixture
 
       val tasks: Map[Task.Id, Task] = f.tasks(Condition.Staging, Condition.Staging)
-      val state = Instance.InstanceState(None, tasks, f.clock.now())
+      val state = Instance.InstanceState(None, tasks, f.clock.now(), UnreachableStrategy.default())
 
       "not set the activeSince timestamp" in { state.activeSince should not be 'defined }
       "set the instance condition to staging" in { state.condition should be(Condition.Staging) }
@@ -59,7 +59,7 @@ class InstanceStateTest extends UnitTest with TableDrivenPropertyChecks {
             task.taskId -> ephemeralTask.copy(status = newStatus)
         }(collection.breakOut)
 
-      val state = Instance.InstanceState(None, tasks, f.clock.now())
+      val state = Instance.InstanceState(None, tasks, f.clock.now(), UnreachableStrategy.default())
 
       "set the activeSince timestamp to the one from running" in { state.activeSince should be(Some(f.clock.now - 1.hour)) }
       "set the instance condition to staging" in { state.condition should be(Condition.Staging) }
@@ -79,7 +79,7 @@ class InstanceStateTest extends UnitTest with TableDrivenPropertyChecks {
             task.taskId -> ephemeralTask.copy(status = newStatus)
         }(collection.breakOut)
 
-      val state = Instance.InstanceState(None, tasks, f.clock.now())
+      val state = Instance.InstanceState(None, tasks, f.clock.now(), UnreachableStrategy.default())
 
       "set the activeSince timestamp to the one from running" in { state.activeSince should be(Some(f.clock.now - 1.hour)) }
       "set the instance condition to unreachable" in { state.condition should be(Condition.Unreachable) }
@@ -121,7 +121,8 @@ class InstanceStateTest extends UnitTest with TableDrivenPropertyChecks {
 
         val tasks = f.tasks(conditions).values
 
-        val actualCondition = Instance.InstanceState.conditionFromTasks(tasks, f.clock.now, 5.minutes)
+        val actualCondition = Instance.InstanceState.conditionFromTasks(
+          tasks, f.clock.now, UnreachableEnabled(5.minutes))
 
         s"return condition $expected" in { actualCondition should be(expected) }
       }
@@ -132,7 +133,8 @@ class InstanceStateTest extends UnitTest with TableDrivenPropertyChecks {
   it should {
     "return Unknown for an empty task list" in {
       val f = new Fixture()
-      val result = Instance.InstanceState.conditionFromTasks(Iterable.empty, f.clock.now(), 5.minutes)
+      val result = Instance.InstanceState.conditionFromTasks(
+        Iterable.empty, f.clock.now(), UnreachableEnabled(5.minutes))
 
       result should be(Condition.Unknown)
     }
diff --git a/src/test/scala/mesosphere/marathon/core/instance/InstanceTest.scala b/src/test/scala/mesosphere/marathon/core/instance/InstanceTest.scala
index 5b21fe083d3..9a653736194 100644
--- a/src/test/scala/mesosphere/marathon/core/instance/InstanceTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/instance/InstanceTest.scala
@@ -8,7 +8,7 @@ import mesosphere.marathon.core.condition.Condition._
 import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.bus.MesosTaskStatusTestHelper
 import mesosphere.marathon.state.PathId._
-import mesosphere.marathon.state.Timestamp
+import mesosphere.marathon.state.{ Timestamp, UnreachableStrategy }
 import org.scalatest.prop.TableDrivenPropertyChecks
 
 class InstanceTest extends UnitTest with TableDrivenPropertyChecks {
@@ -39,7 +39,7 @@ class InstanceTest extends UnitTest with TableDrivenPropertyChecks {
 
       s"$from and tasks become ${withTasks.mkString(", ")}" should {
 
-        val status = Instance.InstanceState(Some(instance.state), tasks, f.clock.now())
+        val status = Instance.InstanceState(Some(instance.state), tasks, f.clock.now(), UnreachableStrategy.default())
 
         s"change to $to" in {
           status.condition should be(to)
@@ -115,8 +115,9 @@ class InstanceTest extends UnitTest with TableDrivenPropertyChecks {
     def instanceWith(condition: Condition, conditions: Seq[Condition]): (Instance, Map[Task.Id, Task]) = {
       val currentTasks = tasks(conditions.map(_ => condition))
       val newTasks = tasks(conditions)
-      val state = Instance.InstanceState(None, currentTasks, Timestamp.now())
-      val instance = Instance(Instance.Id.forRunSpec(id), agentInfo, state, currentTasks, runSpecVersion = Timestamp.now())
+      val state = Instance.InstanceState(None, currentTasks, Timestamp.now(), UnreachableStrategy.default())
+      val instance = Instance(Instance.Id.forRunSpec(id), agentInfo, state, currentTasks,
+        runSpecVersion = Timestamp.now(), UnreachableStrategy.default())
       (instance, newTasks)
     }
   }
diff --git a/src/test/scala/mesosphere/marathon/core/instance/TestInstanceBuilder.scala b/src/test/scala/mesosphere/marathon/core/instance/TestInstanceBuilder.scala
index 655ec9cd2e7..bf4d29b06ad 100644
--- a/src/test/scala/mesosphere/marathon/core/instance/TestInstanceBuilder.scala
+++ b/src/test/scala/mesosphere/marathon/core/instance/TestInstanceBuilder.scala
@@ -6,7 +6,7 @@ import mesosphere.marathon.core.instance.update.{ InstanceUpdateOperation, Insta
 import mesosphere.marathon.core.pod.MesosContainer
 import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.state.NetworkInfoPlaceholder
-import mesosphere.marathon.state.{ PathId, Timestamp }
+import mesosphere.marathon.state.{ PathId, Timestamp, UnreachableStrategy }
 import org.apache.mesos
 
 import scala.collection.immutable.Seq
@@ -123,7 +123,8 @@ object TestInstanceBuilder {
     agentInfo = TestInstanceBuilder.defaultAgentInfo,
     state = InstanceState(Condition.Created, now, None, healthy = None),
     tasksMap = Map.empty,
-    runSpecVersion = version
+    runSpecVersion = version,
+    UnreachableStrategy.default()
   )
 
   private val defaultAgentInfo = Instance.AgentInfo(host = "host.some", agentId = None, attributes = Seq.empty)
diff --git a/src/test/scala/mesosphere/marathon/core/instance/update/InstanceUpdaterTest.scala b/src/test/scala/mesosphere/marathon/core/instance/update/InstanceUpdaterTest.scala
index 7da08b5534d..92ea1191c53 100644
--- a/src/test/scala/mesosphere/marathon/core/instance/update/InstanceUpdaterTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/instance/update/InstanceUpdaterTest.scala
@@ -12,7 +12,7 @@ import mesosphere.marathon.core.task.Task
 import mesosphere.marathon.core.task.bus.{ MesosTaskStatusTestHelper, TaskStatusUpdateTestHelper }
 import mesosphere.marathon.core.task.state.NetworkInfoPlaceholder
 import mesosphere.marathon.raml.Resources
-import mesosphere.marathon.state.{ PathId, UnreachableStrategy }
+import mesosphere.marathon.state.{ PathId, UnreachableEnabled, UnreachableStrategy }
 import org.apache.mesos.Protos.TaskState.TASK_UNREACHABLE
 
 import scala.concurrent.duration._
@@ -63,7 +63,7 @@ class InstanceUpdaterTest extends UnitTest {
 
     "processing an expired unreachable" should {
       val f = new Fixture
-      val unreachableInactiveAfter = f.instance.unreachableStrategy.inactiveAfter
+      val unreachableInactiveAfter = f.instance.unreachableStrategy.asInstanceOf[UnreachableEnabled].inactiveAfter
       val newMesosStatus = MesosTaskStatusTestHelper.unreachable(f.taskId, since = f.clock.now())
 
       // Forward time to expire unreachable status
@@ -192,7 +192,7 @@ class InstanceUpdaterTest extends UnitTest {
       val unreachableStatus = f.taskStatus.copy(startedAt = None, condition = Condition.Unreachable, mesosStatus = Some(mesosTaskStatus))
       val unreachableTask = f.task.copy(status = unreachableStatus)
       val unreachableState = f.instanceState.copy(condition = Condition.Unreachable)
-      val unreachableStrategy = UnreachableStrategy(inactiveAfter = 30.minutes, expungeAfter = 1.hour)
+      val unreachableStrategy = UnreachableEnabled(inactiveAfter = 30.minutes, expungeAfter = 1.hour)
       val unreachableInstance = f.instance.copy(
         tasksMap = Map(f.taskId -> unreachableTask),
         state = unreachableState,
@@ -218,7 +218,7 @@ class InstanceUpdaterTest extends UnitTest {
       val unreachableStatus = f.taskStatus.copy(startedAt = None, condition = Condition.Unreachable, mesosStatus = Some(mesosTaskStatus))
       val unreachableTask = f.task.copy(status = unreachableStatus)
       val unreachableInactiveState = f.instanceState.copy(condition = Condition.UnreachableInactive)
-      val unreachableStrategy = UnreachableStrategy(inactiveAfter = 1.minute, expungeAfter = 1.hour)
+      val unreachableStrategy = UnreachableEnabled(inactiveAfter = 1.minute, expungeAfter = 1.hour)
       val unreachableInactiveInstance = f.instance.copy(
         tasksMap = Map(f.taskId -> unreachableTask),
         state = unreachableInactiveState,
@@ -305,6 +305,8 @@ class InstanceUpdaterTest extends UnitTest {
       networkInfo = NetworkInfoPlaceholder()
     )
     val task = Task.LaunchedEphemeral(taskId, runSpecVersion = clock.now(), status = taskStatus)
-    val instance = Instance(Instance.Id("foobar.instance-baz"), agentInfo, instanceState, Map(taskId -> task), clock.now())
+    val instance = Instance(
+      Instance.Id("foobar.instance-baz"), agentInfo, instanceState, Map(taskId -> task), clock.now(),
+      UnreachableStrategy.default())
   }
 }
diff --git a/src/test/scala/mesosphere/marathon/core/task/jobs/ExpungeOverdueLostTasksActorTest.scala b/src/test/scala/mesosphere/marathon/core/task/jobs/ExpungeOverdueLostTasksActorTest.scala
index 541f464c481..79a5cbe790a 100644
--- a/src/test/scala/mesosphere/marathon/core/task/jobs/ExpungeOverdueLostTasksActorTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/task/jobs/ExpungeOverdueLostTasksActorTest.scala
@@ -13,7 +13,7 @@ import mesosphere.marathon.core.task.jobs.impl.{ ExpungeOverdueLostTasksActor, E
 import mesosphere.marathon.core.task.tracker.InstanceTracker.InstancesBySpec
 import mesosphere.marathon.core.task.tracker.{ InstanceTracker, TaskStateOpProcessor }
 import mesosphere.marathon.state.PathId._
-import mesosphere.marathon.state.{ Timestamp, UnreachableStrategy }
+import mesosphere.marathon.state.{ Timestamp, UnreachableEnabled, UnreachableDisabled, UnreachableStrategy }
 import mesosphere.marathon.test.MarathonTestHelper
 import org.scalatest.prop.TableDrivenPropertyChecks
 
@@ -27,7 +27,7 @@ class ExpungeOverdueLostTasksActorTest extends AkkaUnitTest with TableDrivenProp
     val config = MarathonTestHelper.defaultConfig(maxTasksPerOffer = 10)
     val stateOpProcessor: TaskStateOpProcessor = mock[TaskStateOpProcessor]
     val taskTracker: InstanceTracker = mock[InstanceTracker]
-    val strategy = UnreachableStrategy(5.minutes, 10.minutes)
+    val fiveTen = UnreachableEnabled(inactiveAfter = 5.minutes, expungeAfter = 10.minutes)
   }
 
   def withActor(testCode: (Fixture, ActorRef) => Any): Unit = {
@@ -60,26 +60,32 @@ class ExpungeOverdueLostTasksActorTest extends AkkaUnitTest with TableDrivenProp
     // format: OFF
     // Different task configuration with startedAt, status since and condition values. Expunge indicates whether an
     // expunge is expected or not.
+    import f.fiveTen
+    val disabled = UnreachableDisabled
     val taskCases = Table(
-      ("name",             "startedAt",    "since",                                                 "condition",                   "expunge"),
-      ("running",          Timestamp.zero, Timestamp.zero,                                          Condition.Running,             false    ),
-      ("expired inactive", Timestamp.zero, f.clock.now - f.strategy.expungeAfter - 1.minute, Condition.UnreachableInactive, true     ),
-      ("unreachable",      Timestamp.zero, f.clock.now - f.strategy.inactiveAfter,           Condition.Unreachable,         false    )
+      ("name",             "startedAt",    "since",                                       "unreachableStrategy", "condition",                   "expunge"),
+      ("running",          Timestamp.zero, Timestamp.zero,                                fiveTen,               Condition.Running,             false    ),
+      ("expired inactive", Timestamp.zero, f.clock.now - fiveTen.expungeAfter - 1.minute, fiveTen,               Condition.UnreachableInactive, true     ),
+      ("unreachable",      Timestamp.zero, f.clock.now - 5.minutes,                       fiveTen,               Condition.Unreachable,         false    ),
+      ("expired disabled", Timestamp.zero, f.clock.now - 365.days,                        disabled,              Condition.Unreachable,         false    )
     )
     // format: ON
 
     forAll(taskCases) { (name: String, startedAt: Timestamp, since: Timestamp, condition: Condition, expunge: Boolean) =>
       s"filtering $name task since $since" should {
         val instance: Instance = (condition match {
-          case Condition.Unreachable => TestInstanceBuilder.newBuilder("/unreachable".toPath).addTaskUnreachable(since = since).getInstance()
-          case Condition.UnreachableInactive => TestInstanceBuilder.newBuilder("/unreachable".toPath).addTaskUnreachableInactive(since = since).getInstance()
-          case _ => TestInstanceBuilder.newBuilder("/running".toPath).addTaskRunning(startedAt = startedAt).getInstance()
-        }).copy(unreachableStrategy = f.strategy)
+          case Condition.Unreachable =>
+            TestInstanceBuilder.newBuilder("/unreachable".toPath).addTaskUnreachable(since = since).getInstance()
+          case Condition.UnreachableInactive =>
+            TestInstanceBuilder.newBuilder("/unreachable".toPath).addTaskUnreachableInactive(since = since).getInstance()
+          case _ =>
+            TestInstanceBuilder.newBuilder("/running".toPath).addTaskRunning(startedAt = startedAt).getInstance()
+        }).copy(unreachableStrategy = unreachableStrategy)
         val instances = InstancesBySpec.forInstances(instance).instancesMap
 
-        val filterForExpunge = businessLogic.filterOverdueUnreachableInactive(instances, f.clock.now()).map(identity)
+        val filterForExpunge = businessLogic.filterUnreachableForExpunge(instances, f.clock.now()).map(identity)
 
-        s"${if (!expunge) "not" else ""} select it for expunge" in { filterForExpunge.nonEmpty should be(expunge) }
+        s"${if (!expunge) "not " else ""}select it for expunge" in { filterForExpunge.nonEmpty should be(expunge) }
       }
     }
 
@@ -117,10 +123,10 @@ class ExpungeOverdueLostTasksActorTest extends AkkaUnitTest with TableDrivenProp
     "checking two running tasks" should withActor { (f: Fixture, checkActor: ActorRef) =>
       val running1 = TestInstanceBuilder.newBuilder("/running1".toPath).addTaskRunning(startedAt = Timestamp.zero)
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
       val running2 = TestInstanceBuilder.newBuilder("/running2".toPath).addTaskRunning(startedAt = Timestamp.zero)
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
 
       f.taskTracker.instancesBySpec()(any[ExecutionContext]) returns Future.successful(InstancesBySpec.forInstances(running1, running2))
 
@@ -132,10 +138,10 @@ class ExpungeOverdueLostTasksActorTest extends AkkaUnitTest with TableDrivenProp
     "checking one inactive Unreachable and one running task" should withActor { (f: Fixture, checkActor: ActorRef) =>
       val running = TestInstanceBuilder.newBuilder("/running".toPath).addTaskRunning(startedAt = Timestamp.zero)
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
       val unreachable = TestInstanceBuilder.newBuilder("/unreachable".toPath).addTaskUnreachableInactive(since = Timestamp.zero)
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
 
       f.taskTracker.instancesBySpec()(any[ExecutionContext]) returns Future.successful(InstancesBySpec.forInstances(running, unreachable))
 
@@ -152,10 +158,10 @@ class ExpungeOverdueLostTasksActorTest extends AkkaUnitTest with TableDrivenProp
     "checking two inactive Unreachable tasks and one is overdue" should withActor { (f: Fixture, checkActor: ActorRef) =>
       val unreachable1 = TestInstanceBuilder.newBuilder("/unreachable1".toPath).addTaskUnreachableInactive(since = Timestamp.zero)
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
       val unreachable2 = TestInstanceBuilder.newBuilder("/unreachable2".toPath).addTaskUnreachableInactive(since = f.clock.now())
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
 
       f.taskTracker.instancesBySpec()(any[ExecutionContext]) returns Future.successful(InstancesBySpec.forInstances(unreachable1, unreachable2))
 
@@ -173,10 +179,10 @@ class ExpungeOverdueLostTasksActorTest extends AkkaUnitTest with TableDrivenProp
       // Note that both won't have unreachable time set.
       val unreachable1 = TestInstanceBuilder.newBuilder("/unreachable1".toPath).addTaskLost(since = Timestamp.zero)
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
       val unreachable2 = TestInstanceBuilder.newBuilder("/unreachable2".toPath).addTaskLost(since = f.clock.now())
         .getInstance()
-        .copy(unreachableStrategy = f.strategy)
+        .copy(unreachableStrategy = f.fiveTen)
 
       f.taskTracker.instancesBySpec()(any[ExecutionContext]) returns Future.successful(InstancesBySpec.forInstances(unreachable1, unreachable2))
 
diff --git a/src/test/scala/mesosphere/marathon/core/task/update/impl/steps/PostToEventStreamStepImplTest.scala b/src/test/scala/mesosphere/marathon/core/task/update/impl/steps/PostToEventStreamStepImplTest.scala
index e8bd68b1469..af271c6caa4 100644
--- a/src/test/scala/mesosphere/marathon/core/task/update/impl/steps/PostToEventStreamStepImplTest.scala
+++ b/src/test/scala/mesosphere/marathon/core/task/update/impl/steps/PostToEventStreamStepImplTest.scala
@@ -9,6 +9,7 @@ import mesosphere.marathon.core.event.{ InstanceHealthChanged, MarathonEvent }
 import mesosphere.marathon.core.instance.Instance.InstanceState
 import mesosphere.marathon.core.instance.update._
 import mesosphere.marathon.core.instance.Instance
+import mesosphere.marathon.state.UnreachableStrategy
 
 import scala.collection.immutable.Seq
 
@@ -107,7 +108,10 @@ class PostToEventStreamStepImplTest extends UnitTest {
 
     val agentInfo = Instance.AgentInfo("localhost", None, Seq.empty)
     val instanceState = InstanceState(Condition.Running, clock.now(), Some(clock.now()), healthy = None)
-    val instance = Instance(Instance.Id("foobar.instance-baz"), agentInfo, instanceState, Map.empty, clock.now())
+    val instance = Instance(
+      Instance.Id("foobar.instance-baz"), agentInfo, instanceState, Map.empty, clock.now(),
+      UnreachableStrategy.default()
+    )
     val eventStream = mock[EventStream]
 
     val step = new PostToEventStreamStepImpl(eventStream)
diff --git a/src/test/scala/mesosphere/marathon/integration/TaskUnreachableIntegrationTest.scala b/src/test/scala/mesosphere/marathon/integration/TaskUnreachableIntegrationTest.scala
index 0ab1a1c16a7..5d1f1bde50e 100644
--- a/src/test/scala/mesosphere/marathon/integration/TaskUnreachableIntegrationTest.scala
+++ b/src/test/scala/mesosphere/marathon/integration/TaskUnreachableIntegrationTest.scala
@@ -40,7 +40,7 @@ class TaskUnreachableIntegrationTest extends AkkaIntegrationFunTest with Embedde
 
   test("A task unreachable update will trigger a replacement task") {
     Given("a new app with proper timeouts")
-    val strategy = UnreachableStrategy(10.seconds, 5.minutes)
+    val strategy = UnreachableEnabled(inactiveAfter = 10.seconds, expungeAfter = 5.minutes)
     val app = appProxy(testBasePath / "unreachable", "v1", instances = 1, healthCheck = None).copy(unreachableStrategy = strategy)
     waitForDeployment(marathon.createAppV2(app))
     val task = waitForTasks(app.id, 1).head
@@ -89,7 +89,7 @@ class TaskUnreachableIntegrationTest extends AkkaIntegrationFunTest with Embedde
     // start both slaves
     mesosCluster.agents.foreach(_.start())
 
-    val strategy = UnreachableStrategy(5.minutes, 10.minutes)
+    val strategy = UnreachableEnabled(inactiveAfter = 5.minutes, expungeAfter = 10.minutes)
     val app = appProxy(testBasePath / "regression", "v1", instances = 2, healthCheck = None)
       .copy(constraints = Set(constraint), unreachableStrategy = strategy)
 
diff --git a/src/test/scala/mesosphere/marathon/raml/PodStatusConversionTest.scala b/src/test/scala/mesosphere/marathon/raml/PodStatusConversionTest.scala
index 023324b2460..412bce1a8f1 100644
--- a/src/test/scala/mesosphere/marathon/raml/PodStatusConversionTest.scala
+++ b/src/test/scala/mesosphere/marathon/raml/PodStatusConversionTest.scala
@@ -482,7 +482,8 @@ object PodStatusConversionTest {
           )
         )
       ).map(t => t.taskId -> t)(collection.breakOut),
-      runSpecVersion = pod.version
+      runSpecVersion = pod.version,
+      unreachableStrategy = state.UnreachableStrategy.default()
     )
 
     InstanceFixture(since, agentInfo, taskIds, instance)
diff --git a/src/test/scala/mesosphere/marathon/raml/UnreachableStrategyConversionTest.scala b/src/test/scala/mesosphere/marathon/raml/UnreachableStrategyConversionTest.scala
index 0a852d19767..4b071cd45ad 100644
--- a/src/test/scala/mesosphere/marathon/raml/UnreachableStrategyConversionTest.scala
+++ b/src/test/scala/mesosphere/marathon/raml/UnreachableStrategyConversionTest.scala
@@ -10,20 +10,20 @@ class UnreachableStrategyConversionTest extends UnitTest {
 
   "UnreachableStrategyConversion" should {
     "read from RAML" in {
-      val raml = UnreachableStrategy()
+      val raml = UnreachableEnabled()
 
       val result: state.UnreachableStrategy = UnreachableStrategyConversion.ramlUnreachableStrategyRead(raml)
 
-      result.inactiveAfter should be(state.UnreachableStrategy.DefaultEphemeralInactiveAfter)
-      result.expungeAfter should be(state.UnreachableStrategy.DefaultEphemeralExpungeAfter)
+      result shouldBe (state.UnreachableStrategy.default(resident = false))
     }
   }
 
   it should {
     "write to RAML" in {
-      val strategy = state.UnreachableStrategy(10.minutes, 20.minutes)
+      val strategy = state.UnreachableEnabled(inactiveAfter = 10.minutes, expungeAfter = 20.minutes)
 
-      val raml: UnreachableStrategy = UnreachableStrategyConversion.ramlUnreachableStrategyWrite(strategy)
+      val raml = UnreachableStrategyConversion.ramlUnreachableStrategyWrite(strategy).
+        asInstanceOf[UnreachableEnabled]
 
       raml.inactiveAfterSeconds should be(600)
       raml.expungeAfterSeconds should be(1200)
diff --git a/src/test/scala/mesosphere/marathon/state/AppDefinitionTest.scala b/src/test/scala/mesosphere/marathon/state/AppDefinitionTest.scala
index e9f8806b200..9726fbb7590 100644
--- a/src/test/scala/mesosphere/marathon/state/AppDefinitionTest.scala
+++ b/src/test/scala/mesosphere/marathon/state/AppDefinitionTest.scala
@@ -268,7 +268,7 @@ class AppDefinitionTest extends MarathonSpec with Matchers {
         "three" -> "ccc"
       ),
       versionInfo = fullVersion,
-      unreachableStrategy = UnreachableStrategy(998.seconds, 999.seconds)
+      unreachableStrategy = UnreachableEnabled(inactiveAfter = 998.seconds, expungeAfter = 999.seconds)
     )
     val result1 = AppDefinition(id = runSpecId).mergeFromProto(app1.toProto)
     assert(result1 == app1)
diff --git a/src/test/scala/mesosphere/marathon/state/UnreachableStrategyTest.scala b/src/test/scala/mesosphere/marathon/state/UnreachableStrategyTest.scala
index 1935dd9fcf8..41bc9fa5f2e 100644
--- a/src/test/scala/mesosphere/marathon/state/UnreachableStrategyTest.scala
+++ b/src/test/scala/mesosphere/marathon/state/UnreachableStrategyTest.scala
@@ -10,28 +10,41 @@ class UnreachableStrategyTest extends UnitTest with ResultMatchers {
 
   "UnreachableStrategy.unreachableStrategyValidator" should {
     "validate default strategy" in {
-      val strategy = UnreachableStrategy()
+      val strategy = UnreachableEnabled()
       UnreachableStrategy.unreachableStrategyValidator(strategy) shouldBe aSuccess
     }
 
     "validate with other parameters successfully" in {
-      val strategy = UnreachableStrategy(13.minutes, 37.minutes)
+      val strategy = UnreachableEnabled(13.minutes, 37.minutes)
       UnreachableStrategy.unreachableStrategyValidator(strategy) shouldBe aSuccess
     }
 
-    "fail with invalid time until inactive" in {
-      val strategy = UnreachableStrategy(inactiveAfter = 0.second)
-      UnreachableStrategy.unreachableStrategyValidator(strategy) should failWith("inactiveAfter" -> "got 0 seconds, expected 1 second or more")
+    "sees disabled as valid" in {
+      val strategy = UnreachableDisabled
+      UnreachableStrategy.unreachableStrategyValidator(strategy) shouldBe aSuccess
     }
 
     "fail when time until expunge is smaller" in {
-      val strategy = UnreachableStrategy(inactiveAfter = 2.seconds, expungeAfter = 1.second)
-      UnreachableStrategy.unreachableStrategyValidator(strategy) should failWith("inactiveAfter" -> "got 2 seconds, expected less than 1 second")
+      val strategy = UnreachableEnabled(inactiveAfter = 2.seconds, expungeAfter = 1.second)
+      UnreachableStrategy.unreachableStrategyValidator(strategy) should failWith(
+        "inactiveAfter" -> "got 2 seconds, expected less than 1 second")
     }
 
     "fail when time until expunge is equal to time until inactive" in {
-      val strategy = UnreachableStrategy(inactiveAfter = 2.seconds, expungeAfter = 2.seconds)
-      UnreachableStrategy.unreachableStrategyValidator(strategy) should failWith("inactiveAfter" -> "got 2 seconds, expected less than 2 seconds")
+      val strategy = UnreachableEnabled(inactiveAfter = 2.seconds, expungeAfter = 2.seconds)
+      UnreachableStrategy.unreachableStrategyValidator(strategy) should failWith(
+        "inactiveAfter" -> "got 2 seconds, expected less than 2 seconds")
     }
   }
+
+  "toProto" should {
+    Seq(
+      UnreachableDisabled,
+      UnreachableEnabled(inactiveAfter = 10.seconds, expungeAfter = 20.seconds)).foreach { unreachableStrategy =>
+
+        s"round trip serializes ${unreachableStrategy}" in {
+          UnreachableStrategy.fromProto(unreachableStrategy.toProto) shouldBe (unreachableStrategy)
+        }
+      }
+  }
 }
diff --git a/src/test/scala/mesosphere/marathon/tasks/InstanceOpFactoryImplTest.scala b/src/test/scala/mesosphere/marathon/tasks/InstanceOpFactoryImplTest.scala
index b3060779f19..f2b10c6453e 100644
--- a/src/test/scala/mesosphere/marathon/tasks/InstanceOpFactoryImplTest.scala
+++ b/src/test/scala/mesosphere/marathon/tasks/InstanceOpFactoryImplTest.scala
@@ -62,7 +62,9 @@ class InstanceOpFactoryImplTest extends MarathonSpec with GivenWhenThen with Moc
       attributes = Vector.empty
     )
 
-    val expectedInstance = Instance(expectedTaskId.instanceId, expectedAgentInfo, instance.state, Map(expectedTaskId -> expectedTask), runSpecVersion = app.version)
+    val expectedInstance = Instance(
+      expectedTaskId.instanceId, expectedAgentInfo, instance.state, Map(expectedTaskId -> expectedTask),
+      runSpecVersion = app.version, app.unreachableStrategy)
     assert(matched.instanceOp.stateOp == InstanceUpdateOperation.LaunchEphemeral(expectedInstance))
   }
 
diff --git a/src/test/scala/mesosphere/marathon/test/MarathonTestHelper.scala b/src/test/scala/mesosphere/marathon/test/MarathonTestHelper.scala
index 01d75d4bdfe..c168afeb421 100644
--- a/src/test/scala/mesosphere/marathon/test/MarathonTestHelper.scala
+++ b/src/test/scala/mesosphere/marathon/test/MarathonTestHelper.scala
@@ -338,7 +338,8 @@ object MarathonTestHelper {
     agentInfo = Instance.AgentInfo("", None, Nil),
     state = InstanceState(Condition.Created, since = clock.now(), None, healthy = None),
     tasksMap = Map.empty[Task.Id, Task],
-    runSpecVersion = clock.now()
+    runSpecVersion = clock.now(),
+    UnreachableStrategy.default()
   )
 
   def createTaskTracker(