You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by id...@apache.org on 2014/10/27 18:36:41 UTC

[4/4] git commit: Introduce a shared filesytem isolator.

Introduce a shared filesytem isolator.

Isolator supports creating private copies of system directories, e.g.,
/tmp, for each container while sharing the host's filesystem.

Review: https://reviews.apache.org/r/25549


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

Branch: refs/heads/master
Commit: f511395e84b009881e10b46d2da20b673bf452cb
Parents: c18a50a
Author: Ian Downes <id...@twitter.com>
Authored: Wed Oct 1 11:05:19 2014 -0700
Committer: Ian Downes <id...@twitter.com>
Committed: Mon Oct 27 10:36:37 2014 -0700

----------------------------------------------------------------------
 include/mesos/mesos.proto                       |   6 +-
 src/Makefile.am                                 |   4 +
 src/common/parse.hpp                            |  14 +
 src/common/type_utils.hpp                       |   8 +
 .../isolators/filesystem/shared.cpp             | 263 +++++++++++++++++++
 .../isolators/filesystem/shared.hpp             |  75 ++++++
 src/slave/containerizer/linux_launcher.cpp      |  10 +-
 src/slave/containerizer/mesos/containerizer.cpp |  24 +-
 src/slave/flags.hpp                             |  22 ++
 src/slave/slave.cpp                             |  16 +-
 src/tests/isolator_tests.cpp                    | 176 +++++++++++++
 src/tests/mesos.hpp                             |   8 +
 12 files changed, 614 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/include/mesos/mesos.proto
----------------------------------------------------------------------
diff --git a/include/mesos/mesos.proto b/include/mesos/mesos.proto
index 6b93e90..168a7a8 100644
--- a/include/mesos/mesos.proto
+++ b/include/mesos/mesos.proto
@@ -837,7 +837,8 @@ message Volume {
   // Absolute path pointing to a directory or file in the container.
   required string container_path = 1;
 
-  // Absolute path pointing to a directory or file on the host.
+  // Absolute path pointing to a directory or file on the host or a path
+  // relative to the container work directory.
   optional string host_path = 2;
 
   enum Mode {
@@ -851,12 +852,13 @@ message Volume {
 
 /**
  * Describes a container configuration and allows extensible
- * configurations for different container implementation.
+ * configurations for different container implementations.
  */
 message ContainerInfo {
   // All container implementation types.
   enum Type {
     DOCKER = 1;
+    MESOS = 2;
   }
 
   message DockerInfo {

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 6820d8a..f177d87 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -343,6 +343,7 @@ if OS_LINUX
   libmesos_no_3rdparty_la_SOURCES += slave/containerizer/isolators/cgroups/cpushare.cpp
   libmesos_no_3rdparty_la_SOURCES += slave/containerizer/isolators/cgroups/mem.cpp
   libmesos_no_3rdparty_la_SOURCES += slave/containerizer/isolators/cgroups/perf_event.cpp
+  libmesos_no_3rdparty_la_SOURCES += slave/containerizer/isolators/filesystem/shared.cpp
   libmesos_no_3rdparty_la_SOURCES += slave/containerizer/linux_launcher.cpp
 else
   EXTRA_DIST += linux/cgroups.cpp
@@ -437,12 +438,15 @@ libmesos_no_3rdparty_la_SOURCES +=					\
 	slave/containerizer/isolator.hpp				\
 	slave/containerizer/launcher.hpp				\
 	slave/containerizer/linux_launcher.hpp				\
+	slave/containerizer/mesos/containerizer.hpp			\
+	slave/containerizer/mesos/launch.hpp				\
 	slave/containerizer/isolators/posix.hpp				\
 	slave/containerizer/isolators/cgroups/constants.hpp		\
 	slave/containerizer/isolators/cgroups/cpushare.hpp		\
 	slave/containerizer/isolators/cgroups/mem.hpp			\
 	slave/containerizer/isolators/cgroups/perf_event.hpp		\
 	slave/containerizer/mesos/containerizer.hpp			\
+	slave/containerizer/isolators/filesystem/shared.hpp		\
 	slave/containerizer/mesos/launch.hpp				\
 	tests/cluster.hpp						\
 	tests/containerizer.hpp						\

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/common/parse.hpp
----------------------------------------------------------------------
diff --git a/src/common/parse.hpp b/src/common/parse.hpp
index c9ca30f..ae581e5 100644
--- a/src/common/parse.hpp
+++ b/src/common/parse.hpp
@@ -68,6 +68,20 @@ inline Try<mesos::internal::Modules> parse(const std::string& value)
   return protobuf::parse<mesos::internal::Modules>(json.get());
 }
 
+
+template<>
+inline Try<mesos::ContainerInfo> parse(const std::string& value)
+{
+  // Convert from string or file to JSON.
+  Try<JSON::Object> json = parse<JSON::Object>(value);
+  if (json.isError()) {
+    return Error(json.error());
+  }
+
+  // Convert from JSON to Protobuf.
+  return protobuf::parse<mesos::ContainerInfo>(json.get());
+}
+
 } // namespace flags {
 
 #endif // __COMMON_PARSE_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/common/type_utils.hpp
----------------------------------------------------------------------
diff --git a/src/common/type_utils.hpp b/src/common/type_utils.hpp
index f16beb8..2d22db8 100644
--- a/src/common/type_utils.hpp
+++ b/src/common/type_utils.hpp
@@ -55,6 +55,14 @@ inline bool operator == (const ExecutorID& left, const ExecutorID& right)
 }
 
 
+inline std::ostream& operator << (
+    std::ostream& stream,
+    const ContainerInfo& containerInfo)
+{
+  return stream << containerInfo.DebugString();
+}
+
+
 inline bool operator == (const FrameworkID& left, const FrameworkID& right)
 {
   return left.value() == right.value();

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/slave/containerizer/isolators/filesystem/shared.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/isolators/filesystem/shared.cpp b/src/slave/containerizer/isolators/filesystem/shared.cpp
new file mode 100644
index 0000000..49510b2
--- /dev/null
+++ b/src/slave/containerizer/isolators/filesystem/shared.cpp
@@ -0,0 +1,263 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#include <set>
+
+#include "slave/containerizer/isolators/filesystem/shared.hpp"
+
+using namespace process;
+
+using std::list;
+using std::set;
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+SharedFilesystemIsolatorProcess::SharedFilesystemIsolatorProcess(
+    const Flags& _flags)
+  : flags(_flags) {}
+
+
+SharedFilesystemIsolatorProcess::~SharedFilesystemIsolatorProcess() {}
+
+
+Try<Isolator*> SharedFilesystemIsolatorProcess::create(const Flags& flags)
+{
+  Result<string> user = os::user();
+  if (!user.isSome()) {
+    return Error("Failed to determine user: " +
+                 (user.isError() ? user.error() : "username not found"));
+  }
+
+  if (user.get() != "root") {
+    return Error("SharedFilesystemIsolator requires root privileges");
+  }
+
+  process::Owned<IsolatorProcess> process(
+      new SharedFilesystemIsolatorProcess(flags));
+
+  return new Isolator(process);
+}
+
+
+Future<Nothing> SharedFilesystemIsolatorProcess::recover(
+    const list<state::RunState>& states)
+{
+  // There is nothing to recover because we do not keep any state and
+  // do not monitor filesystem usage or perform any action on cleanup.
+  return Nothing();
+}
+
+
+Future<Option<CommandInfo> > SharedFilesystemIsolatorProcess::prepare(
+    const ContainerID& containerId,
+    const ExecutorInfo& executorInfo,
+    const string& directory)
+{
+  if (executorInfo.has_container() &&
+      executorInfo.container().type() != ContainerInfo::MESOS) {
+    return Failure("Can only prepare filesystem for a MESOS container");
+  }
+
+  LOG(INFO) << "Preparing shared filesystem for container: "
+            << stringify(containerId);
+
+  if (!executorInfo.has_container()) {
+    // We don't consider this an error, there's just nothing to do so
+    // we return None.
+
+    return None();
+  }
+
+  // We don't support mounting to a container path which is a parent
+  // to another container path as this can mask entries. We'll keep
+  // track of all container paths so we can check this.
+  set<string> containerPaths;
+  containerPaths.insert(directory);
+
+  list<string> commands;
+
+  foreach (const Volume& volume, executorInfo.container().volumes()) {
+    // Because the filesystem is shared we require the container path
+    // already exist, otherwise containers can create arbitrary paths
+    // outside their sandbox.
+    if (!os::exists(volume.container_path())) {
+      return Failure("Volume with container path '" +
+                     volume.container_path() +
+                     "' must exist on host for shared filesystem isolator");
+    }
+
+    // Host path must be provided.
+    if (!volume.has_host_path()) {
+      return Failure("Volume with container path '" +
+                     volume.container_path() +
+                     "' must specify host path for shared filesystem isolator");
+    }
+
+    // Check we won't mask another volume.
+    // NOTE: Assuming here that the container path is absolute, see
+    // Volume protobuf.
+    // TODO(idownes): This test is unnecessarily strict and could be
+    // relaxed if mounts could be re-ordered.
+    foreach (const string& containerPath, containerPaths) {
+      if (strings::startsWith(volume.container_path(), containerPath)) {
+        return Failure("Cannot mount volume to '" +
+                        volume.container_path() +
+                        "' because it is under volume '" +
+                        containerPath +
+                        "'");
+      }
+
+      if (strings::startsWith(containerPath, volume.container_path())) {
+        return Failure("Cannot mount volume to '" +
+                        containerPath +
+                        "' because it is under volume '" +
+                        volume.container_path() +
+                        "'");
+      }
+    }
+    containerPaths.insert(volume.container_path());
+
+    // A relative host path will be created in the container's work
+    // directory, otherwise check it already exists.
+    string hostPath;
+    if (!strings::startsWith(volume.host_path(), "/")) {
+      hostPath = path::join(directory, volume.host_path());
+
+      // Do not support any relative components in the resulting path.
+      // There should not be any links in the work directory to
+      // resolve.
+      if (strings::contains(hostPath, "/./") ||
+          strings::contains(hostPath, "/../")) {
+        return Failure("Relative host path '" +
+                       hostPath +
+                       "' cannot contain relative components");
+      }
+
+      Try<Nothing> mkdir = os::mkdir(hostPath, true);
+      if (mkdir.isError()) {
+        return Failure("Failed to create host_path '" +
+                        hostPath +
+                        "' for mount to '" +
+                        volume.container_path() +
+                        "': " +
+                        mkdir.error());
+      }
+
+      // Set the ownership and permissions to match the container path
+      // as these are inherited from host path on bind mount.
+      struct stat stat;
+      if (::stat(volume.container_path().c_str(), &stat) < 0) {
+        return Failure("Failed to get permissions on '" +
+                        volume.container_path() + "'" +
+                        ": " + strerror(errno));
+      }
+
+      Try<Nothing> chmod = os::chmod(hostPath, stat.st_mode);
+      if (chmod.isError()) {
+        return Failure("Failed to chmod hostPath '" +
+                       hostPath +
+                       "': " +
+                       chmod.error());
+      }
+
+      Try<Nothing> chown = os::chown(stat.st_uid, stat.st_gid, hostPath, false);
+      if (chown.isError()) {
+        return Failure("Failed to chown hostPath '" +
+                       hostPath +
+                       "': " +
+                       chown.error());
+      }
+    } else {
+      hostPath = volume.host_path();
+
+      if (!os::exists(hostPath)) {
+        return Failure("Volume with container path '" +
+                      volume.container_path() +
+                      "' must have host path '" +
+                      hostPath +
+                      "' present on host for shared filesystem isolator");
+      }
+    }
+
+    commands.push_back("mount -n --bind " +
+                       hostPath +
+                       " " +
+                       volume.container_path());
+  }
+
+  CommandInfo command;
+  command.set_value(strings::join(" && ", commands));
+
+  return command;
+}
+
+
+Future<Nothing> SharedFilesystemIsolatorProcess::isolate(
+    const ContainerID& containerId,
+    pid_t pid)
+{
+  // No-op, isolation happens when unsharing the mount namespace.
+
+  return Nothing();
+}
+
+
+Future<Limitation> SharedFilesystemIsolatorProcess::watch(
+    const ContainerID& containerId)
+{
+  // No-op, for now.
+
+  return Future<Limitation>();
+}
+
+
+Future<Nothing> SharedFilesystemIsolatorProcess::update(
+    const ContainerID& containerId,
+    const Resources& resources)
+{
+  // No-op, nothing enforced.
+
+  return Nothing();
+}
+
+
+Future<ResourceStatistics> SharedFilesystemIsolatorProcess::usage(
+    const ContainerID& containerId)
+{
+  // No-op, no usage gathered.
+
+  return ResourceStatistics();
+}
+
+
+Future<Nothing> SharedFilesystemIsolatorProcess::cleanup(
+    const ContainerID& containerId)
+{
+  // Cleanup of mounts is done automatically done by the kernel when
+  // the mount namespace is destroyed after the last process
+  // terminates.
+
+  return Nothing();
+}
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/slave/containerizer/isolators/filesystem/shared.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/isolators/filesystem/shared.hpp b/src/slave/containerizer/isolators/filesystem/shared.hpp
new file mode 100644
index 0000000..75172d5
--- /dev/null
+++ b/src/slave/containerizer/isolators/filesystem/shared.hpp
@@ -0,0 +1,75 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#ifndef __SHARED_FILESYSTEM_ISOLATOR_HPP__
+#define __SHARED_FILESYSTEM_ISOLATOR_HPP__
+
+#include "slave/containerizer/isolator.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+// This isolator is to be used when all containers share the host's
+// filesystem.  It supports creating mounting "volumes" from the host
+// into each container's mount namespace. In particular, this can be
+// used to give each container a "private" system directory, such as
+// /tmp and /var/tmp.
+class SharedFilesystemIsolatorProcess : public IsolatorProcess
+{
+public:
+  static Try<Isolator*> create(const Flags& flags);
+
+  virtual ~SharedFilesystemIsolatorProcess();
+
+  virtual process::Future<Nothing> recover(
+      const std::list<state::RunState>& states);
+
+  virtual process::Future<Option<CommandInfo> > prepare(
+      const ContainerID& containerId,
+      const ExecutorInfo& executorInfo,
+      const std::string& directory);
+
+  virtual process::Future<Nothing> isolate(
+      const ContainerID& containerId,
+      pid_t pid);
+
+  virtual process::Future<Limitation> watch(
+      const ContainerID& containerId);
+
+  virtual process::Future<Nothing> update(
+      const ContainerID& containerId,
+      const Resources& resources);
+
+  virtual process::Future<ResourceStatistics> usage(
+      const ContainerID& containerId);
+
+  virtual process::Future<Nothing> cleanup(
+      const ContainerID& containerId);
+
+private:
+  SharedFilesystemIsolatorProcess(const Flags& flags);
+
+  const Flags flags;
+};
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __SHARED_FILESYSTEM_ISOLATOR_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/slave/containerizer/linux_launcher.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/linux_launcher.cpp b/src/slave/containerizer/linux_launcher.cpp
index 07ee643..7a5cdbb 100644
--- a/src/slave/containerizer/linux_launcher.cpp
+++ b/src/slave/containerizer/linux_launcher.cpp
@@ -91,8 +91,6 @@ Try<Launcher*> LinuxLauncher::create(const Flags& flags)
   LOG(INFO) << "Using " << hierarchy.get()
             << " as the freezer hierarchy for the Linux launcher";
 
-  // TODO(idownes): Inspect the isolation flag to determine namespaces
-  // to use.
   int namespaces = 0;
 
 #ifdef WITH_NETWORK_ISOLATOR
@@ -103,6 +101,10 @@ Try<Launcher*> LinuxLauncher::create(const Flags& flags)
   }
 #endif
 
+  if (strings::contains(flags.isolation, "filesystem/shared")) {
+    namespaces |= CLONE_NEWNS;
+  }
+
   return new LinuxLauncher(flags, namespaces, hierarchy.get());
 }
 
@@ -347,6 +349,10 @@ Try<pid_t> LinuxLauncher::fork(
 
 Future<Nothing> LinuxLauncher::destroy(const ContainerID& containerId)
 {
+  if (!pids.contains(containerId)) {
+    return Failure("Unknown container");
+  }
+
   pids.erase(containerId);
 
   return cgroups::destroy(

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/slave/containerizer/mesos/containerizer.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/containerizer.cpp b/src/slave/containerizer/mesos/containerizer.cpp
index ce92878..3fa249f 100644
--- a/src/slave/containerizer/mesos/containerizer.cpp
+++ b/src/slave/containerizer/mesos/containerizer.cpp
@@ -42,6 +42,7 @@
 #include "slave/containerizer/isolators/cgroups/cpushare.hpp"
 #include "slave/containerizer/isolators/cgroups/mem.hpp"
 #include "slave/containerizer/isolators/cgroups/perf_event.hpp"
+#include "slave/containerizer/isolators/filesystem/shared.hpp"
 #endif // __linux__
 #ifdef WITH_NETWORK_ISOLATOR
 #include "slave/containerizer/isolators/network/port_mapping.hpp"
@@ -102,6 +103,7 @@ Try<MesosContainerizer*> MesosContainerizer::create(
   creators["cgroups/cpu"] = &CgroupsCpushareIsolatorProcess::create;
   creators["cgroups/mem"] = &CgroupsMemIsolatorProcess::create;
   creators["cgroups/perf_event"] = &CgroupsPerfEventIsolatorProcess::create;
+  creators["filesystem/shared"] = &SharedFilesystemIsolatorProcess::create;
 #endif // __linux__
 #ifdef WITH_NETWORK_ISOLATOR
   creators["network/port_mapping"] = &PortMappingIsolatorProcess::create;
@@ -124,7 +126,13 @@ Try<MesosContainerizer*> MesosContainerizer::create(
         return Error(
             "Could not create isolator " + type + ": " + isolator.error());
       } else {
-        isolators.push_back(Owned<Isolator>(isolator.get()));
+        if (type == "filesystem/shared") {
+          // Filesystem isolator must be the first isolator used for prepare()
+          // so any volume mounts are performed before anything else runs.
+          isolators.insert(isolators.begin(), Owned<Isolator>(isolator.get()));
+        } else {
+          isolators.push_back(Owned<Isolator>(isolator.get()));
+        }
       }
     } else {
       return Error("Unknown or unsupported isolator: " + type);
@@ -135,7 +143,8 @@ Try<MesosContainerizer*> MesosContainerizer::create(
   // Determine which launcher to use based on the isolation flag.
   Try<Launcher*> launcher =
     (strings::contains(isolation, "cgroups") ||
-     strings::contains(isolation, "network/port_mapping"))
+     strings::contains(isolation, "network/port_mapping") ||
+     strings::contains(isolation, "filesystem/shared"))
     ? LinuxLauncher::create(flags)
     : PosixLauncher::create(flags);
 #else
@@ -384,9 +393,10 @@ Future<bool> MesosContainerizerProcess::launch(
     return Failure("Container already started");
   }
 
-  if (executorInfo.has_container()) {
-    // We return false as this containerizer does not support
-    // handling ContainerInfo.
+  // We support MESOS containers or ExecutorInfos with no
+  // ContainerInfo given.
+  if (executorInfo.has_container() &&
+      executorInfo.container().type() != ContainerInfo::MESOS) {
     return false;
   }
 
@@ -437,7 +447,7 @@ Future<bool> MesosContainerizerProcess::launch(
 {
   if (taskInfo.has_container()) {
     // We return false as this containerizer does not support
-    // handling ContainerInfo.
+    // handling TaskInfo::ContainerInfo.
     return false;
   }
 
@@ -950,7 +960,7 @@ void MesosContainerizerProcess::_destroy(
   // consider cleaning up here.
   if (!future.isReady()) {
     promises[containerId]->fail(
-        "Failed to destroy container: " +
+        "Failed to destroy container " + stringify(containerId) + ": " +
         (future.isFailed() ? future.failure() : "discarded future"));
 
     destroying.erase(containerId);

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/slave/flags.hpp
----------------------------------------------------------------------
diff --git a/src/slave/flags.hpp b/src/slave/flags.hpp
index bdaa5d5..621d0de 100644
--- a/src/slave/flags.hpp
+++ b/src/slave/flags.hpp
@@ -303,6 +303,27 @@ public:
         "sandbox is mapped to.\n",
         "/mnt/mesos/sandbox");
 
+    add(&Flags::default_container_info,
+        "default_container_info",
+        "JSON formatted ContainerInfo that will be included into\n"
+        "any ExecutorInfo that does not specify a ContainerInfo.\n"
+        "\n"
+        "See the ContainerInfo protobuf in mesos.proto for\n"
+        "the expected format.\n"
+        "\n"
+        "Example:\n"
+        "{\n"
+        "\"type\": \"MESOS\",\n"
+        "\"volumes\": [\n"
+        "  {\n"
+        "    \"host_path\": \"./.private/tmp\",\n"
+        "    \"container_path\": \"/tmp\",\n"
+        "    \"mode\": \"RW\"\n"
+        "  }\n"
+        " ]\n"
+        "}"
+        );
+
 #ifdef WITH_NETWORK_ISOLATOR
     add(&Flags::ephemeral_ports_per_container,
         "ephemeral_ports_per_container",
@@ -399,6 +420,7 @@ public:
   Option<std::string> default_container_image;
   std::string docker;
   std::string docker_sandbox_directory;
+  Option<ContainerInfo> default_container_info;
 #ifdef WITH_NETWORK_ISOLATOR
   uint16_t ephemeral_ports_per_container;
   Option<std::string> eth0_name;

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/slave/slave.cpp
----------------------------------------------------------------------
diff --git a/src/slave/slave.cpp b/src/slave/slave.cpp
index 5e7c107..96fb5f7 100644
--- a/src/slave/slave.cpp
+++ b/src/slave/slave.cpp
@@ -2701,10 +2701,24 @@ ExecutorInfo Slave::getExecutorInfo(
           "cpus:" + stringify(DEFAULT_EXECUTOR_CPUS) + ";" +
           "mem:" + stringify(DEFAULT_EXECUTOR_MEM.megabytes())).get());
 
+    // Add in any default ContainerInfo.
+    if (!executor.has_container() && flags.default_container_info.isSome()) {
+      executor.mutable_container()->CopyFrom(
+          flags.default_container_info.get());
+    }
+
     return executor;
   }
 
-  return task.executor();
+  ExecutorInfo executor = task.executor();
+
+  // Add in any default ContainerInfo.
+  if (!executor.has_container() && flags.default_container_info.isSome()) {
+    executor.mutable_container()->CopyFrom(
+        flags.default_container_info.get());
+  }
+
+  return executor;
 }
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/tests/isolator_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/isolator_tests.cpp b/src/tests/isolator_tests.cpp
index db7a58a..a0653e2 100644
--- a/src/tests/isolator_tests.cpp
+++ b/src/tests/isolator_tests.cpp
@@ -48,6 +48,7 @@
 #include "slave/containerizer/isolators/cgroups/cpushare.hpp"
 #include "slave/containerizer/isolators/cgroups/mem.hpp"
 #include "slave/containerizer/isolators/cgroups/perf_event.hpp"
+#include "slave/containerizer/isolators/filesystem/shared.hpp"
 #endif // __linux__
 #include "slave/containerizer/isolators/posix.hpp"
 
@@ -75,6 +76,7 @@ using mesos::internal::master::Master;
 using mesos::internal::slave::CgroupsCpushareIsolatorProcess;
 using mesos::internal::slave::CgroupsMemIsolatorProcess;
 using mesos::internal::slave::CgroupsPerfEventIsolatorProcess;
+using mesos::internal::slave::SharedFilesystemIsolatorProcess;
 #endif // __linux__
 using mesos::internal::slave::Isolator;
 using mesos::internal::slave::IsolatorProcess;
@@ -752,4 +754,178 @@ TEST_F(PerfEventIsolatorTest, ROOT_CGROUPS_Sample)
   delete isolator.get();
 }
 
+class SharedFilesystemIsolatorTest : public MesosTest {};
+
+
+// Test that a container can create a private view of a system
+// directory (/var/tmp). Check that a file written by a process inside
+// the container doesn't appear on the host filesystem but does appear
+// under the container's work directory.
+TEST_F(SharedFilesystemIsolatorTest, ROOT_RelativeVolume)
+{
+  slave::Flags flags = CreateSlaveFlags();
+  flags.isolation = "filesystem/shared";
+
+  Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags);
+  CHECK_SOME(isolator);
+
+  Try<Launcher*> launcher = LinuxLauncher::create(flags);
+  CHECK_SOME(launcher);
+
+  // Use /var/tmp so we don't mask the work directory (under /tmp).
+  const string containerPath = "/var/tmp";
+  ASSERT_TRUE(os::isdir(containerPath));
+
+  // Use a host path relative to the container work directory.
+  const string hostPath = strings::remove(containerPath, "/", strings::PREFIX);
+
+  ContainerInfo containerInfo;
+  containerInfo.set_type(ContainerInfo::MESOS);
+  containerInfo.add_volumes()->CopyFrom(
+      CREATE_VOLUME(containerPath, hostPath, Volume::RW));
+
+  ExecutorInfo executorInfo;
+  executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+  ContainerID containerId;
+  containerId.set_value(UUID::random().toString());
+
+  Future<Option<CommandInfo> > prepare =
+    isolator.get()->prepare(containerId, executorInfo, flags.work_dir);
+  AWAIT_READY(prepare);
+  ASSERT_SOME(prepare.get());
+
+  // The test will touch a file in container path.
+  const string file = path::join(containerPath, UUID::random().toString());
+  ASSERT_FALSE(os::exists(file));
+
+  // Manually run the isolator's preparation command first, then touch
+  // the file.
+  vector<string> args;
+  args.push_back("/bin/sh");
+  args.push_back("-x");
+  args.push_back("-c");
+  args.push_back(prepare.get().get().value() + " && touch " + file);
+
+  Try<pid_t> pid = launcher.get()->fork(
+      containerId,
+      "/bin/sh",
+      args,
+      Subprocess::FD(STDIN_FILENO),
+      Subprocess::FD(STDOUT_FILENO),
+      Subprocess::FD(STDERR_FILENO),
+      None(),
+      None(),
+      None());
+  ASSERT_SOME(pid);
+
+  // Set up the reaper to wait on the forked child.
+  Future<Option<int> > status = process::reap(pid.get());
+
+  AWAIT_READY(status);
+  EXPECT_SOME_EQ(0, status.get());
+
+  // Check the correct hierarchy was created under the container work
+  // directory.
+  string dir = "/";
+  foreach (const string& subdir, strings::tokenize(containerPath, "/")) {
+    dir = path::join(dir, subdir);
+
+    struct stat hostStat;
+    EXPECT_EQ(0, ::stat(dir.c_str(), &hostStat));
+
+    struct stat containerStat;
+    EXPECT_EQ(0,
+              ::stat(path::join(flags.work_dir, dir).c_str(), &containerStat));
+
+    EXPECT_EQ(hostStat.st_mode, containerStat.st_mode);
+    EXPECT_EQ(hostStat.st_uid, containerStat.st_uid);
+    EXPECT_EQ(hostStat.st_gid, containerStat.st_gid);
+  }
+
+  // Check it did *not* create a file in the host namespace.
+  EXPECT_FALSE(os::exists(file));
+
+  // Check it did create the file under the container's work directory
+  // on the host.
+  EXPECT_TRUE(os::exists(path::join(flags.work_dir, file)));
+
+  delete launcher.get();
+  delete isolator.get();
+}
+
+
+TEST_F(SharedFilesystemIsolatorTest, ROOT_AbsoluteVolume)
+{
+  slave::Flags flags = CreateSlaveFlags();
+  flags.isolation = "filesystem/shared";
+
+  Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags);
+  CHECK_SOME(isolator);
+
+  Try<Launcher*> launcher = LinuxLauncher::create(flags);
+  CHECK_SOME(launcher);
+
+  // We'll mount the absolute test work directory as /var/tmp in the
+  // container.
+  const string hostPath = flags.work_dir;
+  const string containerPath = "/var/tmp";
+
+  ContainerInfo containerInfo;
+  containerInfo.set_type(ContainerInfo::MESOS);
+  containerInfo.add_volumes()->CopyFrom(
+      CREATE_VOLUME(containerPath, hostPath, Volume::RW));
+
+  ExecutorInfo executorInfo;
+  executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+  ContainerID containerId;
+  containerId.set_value(UUID::random().toString());
+
+  Future<Option<CommandInfo> > prepare =
+    isolator.get()->prepare(containerId, executorInfo, flags.work_dir);
+  AWAIT_READY(prepare);
+  ASSERT_SOME(prepare.get());
+
+  // Test the volume mounting by touching a file in the container's
+  // /tmp, which should then be in flags.work_dir.
+  const string filename = UUID::random().toString();
+  ASSERT_FALSE(os::exists(path::join(containerPath, filename)));
+
+  vector<string> args;
+  args.push_back("/bin/sh");
+  args.push_back("-x");
+  args.push_back("-c");
+  args.push_back(prepare.get().get().value() +
+                 " && touch " +
+                 path::join(containerPath, filename));
+
+  Try<pid_t> pid = launcher.get()->fork(
+      containerId,
+      "/bin/sh",
+      args,
+      Subprocess::FD(STDIN_FILENO),
+      Subprocess::FD(STDOUT_FILENO),
+      Subprocess::FD(STDERR_FILENO),
+      None(),
+      None(),
+      None());
+  ASSERT_SOME(pid);
+
+  // Set up the reaper to wait on the forked child.
+  Future<Option<int> > status = process::reap(pid.get());
+
+  AWAIT_READY(status);
+  EXPECT_SOME_EQ(0, status.get());
+
+  // Check the file was created in flags.work_dir.
+  EXPECT_TRUE(os::exists(path::join(hostPath, filename)));
+
+  // Check it didn't get created in the host's view of containerPath.
+  EXPECT_FALSE(os::exists(path::join(containerPath, filename)));
+
+  delete launcher.get();
+  delete isolator.get();
+}
+
 #endif // __linux__

http://git-wip-us.apache.org/repos/asf/mesos/blob/f511395e/src/tests/mesos.hpp
----------------------------------------------------------------------
diff --git a/src/tests/mesos.hpp b/src/tests/mesos.hpp
index e36e138..c1d64a7 100644
--- a/src/tests/mesos.hpp
+++ b/src/tests/mesos.hpp
@@ -311,6 +311,14 @@ protected:
      commandInfo; })
 
 
+#define CREATE_VOLUME(containerPath, hostPath, mode)                  \
+      ({ Volume volume;                                               \
+         volume.set_container_path(containerPath);                    \
+         volume.set_host_path(hostPath);                              \
+         volume.set_mode(mode);                                       \
+         volume; })
+
+
 // TODO(bmahler): Refactor this to make the distinction between
 // command tasks and executor tasks clearer.
 inline TaskInfo createTask(