You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ji...@apache.org on 2015/09/17 00:54:35 UTC

[1/6] mesos git commit: Moved files to prepare for unifying provisioners.

Repository: mesos
Updated Branches:
  refs/heads/master c68c3bd58 -> 76861c527


http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/tests/containerizer/appc_provisioner_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/appc_provisioner_tests.cpp b/src/tests/containerizer/appc_provisioner_tests.cpp
deleted file mode 100644
index 8fee7ac..0000000
--- a/src/tests/containerizer/appc_provisioner_tests.cpp
+++ /dev/null
@@ -1,415 +0,0 @@
-/**
- * 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 <string>
-
-#include <mesos/slave/isolator.hpp>
-
-#include <process/gtest.hpp>
-
-#include <stout/gtest.hpp>
-#include <stout/json.hpp>
-#include <stout/os.hpp>
-#include <stout/path.hpp>
-#include <stout/stringify.hpp>
-#include <stout/uuid.hpp>
-
-#include "slave/containerizer/provisioner.hpp"
-
-#include "slave/containerizer/provisioners/appc/spec.hpp"
-#include "slave/containerizer/provisioners/appc/store.hpp"
-
-#include "tests/utils.hpp"
-
-using std::list;
-using std::string;
-using std::vector;
-
-using namespace process;
-
-using namespace mesos::internal::slave::appc;
-
-using mesos::internal::slave::Fetcher;
-using mesos::internal::slave::Provisioner;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-class AppcProvisionerTest : public TemporaryDirectoryTest {};
-
-
-TEST_F(AppcProvisionerTest, ValidateImageManifest)
-{
-  JSON::Value manifest = JSON::parse(
-      "{"
-      "  \"acKind\": \"ImageManifest\","
-      "  \"acVersion\": \"0.6.1\","
-      "  \"name\": \"foo.com/bar\","
-      "  \"labels\": ["
-      "    {"
-      "      \"name\": \"version\","
-      "      \"value\": \"1.0.0\""
-      "    },"
-      "    {"
-      "      \"name\": \"arch\","
-      "      \"value\": \"amd64\""
-      "    },"
-      "    {"
-      "      \"name\": \"os\","
-      "      \"value\": \"linux\""
-      "    }"
-      "  ],"
-      "  \"annotations\": ["
-      "    {"
-      "      \"name\": \"created\","
-      "      \"value\": \"1438983392\""
-      "    }"
-      "  ]"
-      "}").get();
-
-  EXPECT_SOME(spec::parse(stringify(manifest)));
-
-  // Incorrect acKind for image manifest.
-  manifest = JSON::parse(
-      "{"
-      "  \"acKind\": \"PodManifest\","
-      "  \"acVersion\": \"0.6.1\","
-      "  \"name\": \"foo.com/bar\""
-      "}").get();
-
-  EXPECT_ERROR(spec::parse(stringify(manifest)));
-}
-
-
-TEST_F(AppcProvisionerTest, ValidateLayout)
-{
-  string image = os::getcwd();
-
-  JSON::Value manifest = JSON::parse(
-      "{"
-      "  \"acKind\": \"ImageManifest\","
-      "  \"acVersion\": \"0.6.1\","
-      "  \"name\": \"foo.com/bar\""
-      "}").get();
-
-  ASSERT_SOME(os::write(path::join(image, "manifest"), stringify(manifest)));
-
-  // Missing rootfs.
-  EXPECT_SOME(spec::validateLayout(image));
-
-  ASSERT_SOME(os::mkdir(path::join(image, "rootfs", "tmp")));
-  ASSERT_SOME(os::write(path::join(image, "rootfs", "tmp", "test"), "test"));
-
-  EXPECT_NONE(spec::validateLayout(image));
-}
-
-
-TEST_F(AppcProvisionerTest, StoreRecover)
-{
-  // Create store.
-  slave::Flags flags;
-  flags.appc_store_dir = path::join(os::getcwd(), "store");
-  Try<Owned<Store>> store = Store::create(flags);
-  ASSERT_SOME(store);
-
-  // Create a simple image in the store:
-  // <store>
-  // |--images
-  //    |--<id>
-  //       |--manifest
-  //       |--rootfs/tmp/test
-  JSON::Value manifest = JSON::parse(
-      "{"
-      "  \"acKind\": \"ImageManifest\","
-      "  \"acVersion\": \"0.6.1\","
-      "  \"name\": \"foo.com/bar\","
-      "  \"labels\": ["
-      "    {"
-      "      \"name\": \"version\","
-      "      \"value\": \"1.0.0\""
-      "    },"
-      "    {"
-      "      \"name\": \"arch\","
-      "      \"value\": \"amd64\""
-      "    },"
-      "    {"
-      "      \"name\": \"os\","
-      "      \"value\": \"linux\""
-      "    }"
-      "  ],"
-      "  \"annotations\": ["
-      "    {"
-      "      \"name\": \"created\","
-      "      \"value\": \"1438983392\""
-      "    }"
-      "  ]"
-      "}").get();
-
-  // The 'imageId' below has the correct format but it's not computed by
-  // hashing the tarball of the image. It's OK here as we assume
-  // the images under 'images' have passed such check when they are
-  // downloaded and validated.
-  string imageId =
-    "sha512-e77d96aa0240eedf134b8c90baeaf76dca8e78691836301d7498c84020446042e"
-    "797b296d6ab296e0954c2626bfb264322ebeb8f447dac4fac6511ea06bc61f0";
-
-  string imagePath = path::join(flags.appc_store_dir, "images", imageId);
-
-  ASSERT_SOME(os::mkdir(path::join(imagePath, "rootfs", "tmp")));
-  ASSERT_SOME(
-      os::write(path::join(imagePath, "rootfs", "tmp", "test"), "test"));
-  ASSERT_SOME(
-      os::write(path::join(imagePath, "manifest"), stringify(manifest)));
-
-  // Recover the image from disk.
-  AWAIT_READY(store.get()->recover());
-
-  Image image;
-  image.mutable_appc()->set_name("foo.com/bar");
-  Future<vector<string>> layers = store.get()->get(image.appc());
-  AWAIT_READY(layers);
-
-  EXPECT_EQ(1u, layers.get().size());
-  ASSERT_SOME(os::realpath(imagePath));
-  EXPECT_EQ(
-      os::realpath(path::join(imagePath, "rootfs")).get(),
-      layers.get().front());
-}
-
-
-#ifdef __linux__
-// This test verifies that the provisioner can provision an rootfs from an
-// image that is already put into the store directory.
-TEST_F(AppcProvisionerTest, ROOT_Provision)
-{
-  // Create provisioner.
-  slave::Flags flags;
-  flags.appc_store_dir = path::join(os::getcwd(), "store");
-  flags.appc_provisioner_backend = "bind";
-  flags.provisioners = "appc";
-  flags.work_dir = "work_dir";
-
-  Fetcher fetcher;
-  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners =
-    Provisioner::create(flags, &fetcher);
-  ASSERT_SOME(provisioners);
-  ASSERT_TRUE(provisioners.get().contains(Image::APPC));
-
-  // Create a simple image in the store:
-  // <store>
-  // |--images
-  //    |--<id>
-  //       |--manifest
-  //       |--rootfs/tmp/test
-  JSON::Value manifest = JSON::parse(
-      "{"
-      "  \"acKind\": \"ImageManifest\","
-      "  \"acVersion\": \"0.6.1\","
-      "  \"name\": \"foo.com/bar\","
-      "  \"labels\": ["
-      "    {"
-      "      \"name\": \"version\","
-      "      \"value\": \"1.0.0\""
-      "    },"
-      "    {"
-      "      \"name\": \"arch\","
-      "      \"value\": \"amd64\""
-      "    },"
-      "    {"
-      "      \"name\": \"os\","
-      "      \"value\": \"linux\""
-      "    }"
-      "  ],"
-      "  \"annotations\": ["
-      "    {"
-      "      \"name\": \"created\","
-      "      \"value\": \"1438983392\""
-      "    }"
-      "  ]"
-      "}").get();
-
-  // The 'imageId' below has the correct format but it's not computed by
-  // hashing the tarball of the image. It's OK here as we assume
-  // the images under 'images' have passed such check when they are
-  // downloaded and validated.
-  string imageId =
-    "sha512-e77d96aa0240eedf134b8c90baeaf76dca8e78691836301d7498c84020446042e"
-    "797b296d6ab296e0954c2626bfb264322ebeb8f447dac4fac6511ea06bc61f0";
-
-  string imagePath = path::join(flags.appc_store_dir, "images", imageId);
-
-  ASSERT_SOME(os::mkdir(path::join(imagePath, "rootfs", "tmp")));
-  ASSERT_SOME(
-      os::write(path::join(imagePath, "rootfs", "tmp", "test"), "test"));
-  ASSERT_SOME(
-      os::write(path::join(imagePath, "manifest"), stringify(manifest)));
-
-  // Recover. This is when the image in the store is loaded.
-  AWAIT_READY(provisioners.get()[Image::APPC]->recover({}, {}));
-
-  // Simulate a task that requires an image.
-  Image image;
-  image.mutable_appc()->set_name("foo.com/bar");
-
-  ContainerID containerId;
-  containerId.set_value("12345");
-
-  Future<string> rootfs =
-    provisioners.get()[Image::APPC]->provision(containerId, image);
-  AWAIT_READY(rootfs);
-
-  string containerDir = path::join(
-      flags.work_dir,
-      "provisioners",
-      stringify(Image::APPC),
-      "containers",
-      containerId.value());
-
-  Try<list<string>> rootfses = os::ls(path::join(
-      containerDir,
-      "backends",
-      flags.appc_provisioner_backend,
-      "rootfses"));
-
-  ASSERT_SOME(rootfses);
-
-  // Verify that the rootfs is successfully provisioned.
-  EXPECT_EQ(1u, rootfses.get().size());
-  EXPECT_EQ(rootfses.get().front(), Path(rootfs.get()).basename());
-
-  Future<bool> destroy = provisioners.get()[Image::APPC]->destroy(containerId);
-  AWAIT_READY(destroy);
-
-  // One rootfs is destroyed.
-  EXPECT_TRUE(destroy.get());
-
-  // The container directory is successfully cleaned up.
-  EXPECT_FALSE(os::exists(containerDir));
-}
-#endif // __linux__
-
-
-// This test verifies that a provisioner can recover the rootfs provisioned
-// by a previous provisioner and then destroy it. Note that we use the copy
-// backend in this test so Linux is not required.
-TEST_F(AppcProvisionerTest, Recover)
-{
-  // Create provisioner.
-  slave::Flags flags;
-  flags.appc_store_dir = path::join(os::getcwd(), "store");
-  flags.appc_provisioner_backend = "copy";
-  flags.provisioners = "appc";
-  flags.work_dir = "work_dir";
-
-  Fetcher fetcher;
-  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners1 =
-    Provisioner::create(flags, &fetcher);
-  ASSERT_SOME(provisioners1);
-  ASSERT_TRUE(provisioners1.get().contains(Image::APPC));
-
-  // Create a simple image in the store:
-  // <store>
-  // |--images
-  //    |--<id>
-  //       |--manifest
-  //       |--rootfs/tmp/test
-  JSON::Value manifest = JSON::parse(
-      "{"
-      "  \"acKind\": \"ImageManifest\","
-      "  \"acVersion\": \"0.6.1\","
-      "  \"name\": \"foo.com/bar\""
-      "}").get();
-
-  // The 'imageId' below has the correct format but it's not computed by
-  // hashing the tarball of the image. It's OK here as we assume
-  // the images under 'images' have passed such check when they are
-  // downloaded and validated.
-  string imageId =
-    "sha512-e77d96aa0240eedf134b8c90baeaf76dca8e78691836301d7498c84020446042e"
-    "797b296d6ab296e0954c2626bfb264322ebeb8f447dac4fac6511ea06bc61f0";
-
-  string imagePath = path::join(flags.appc_store_dir, "images", imageId);
-
-  ASSERT_SOME(os::mkdir(path::join(imagePath, "rootfs", "tmp")));
-  ASSERT_SOME(
-      os::write(path::join(imagePath, "rootfs", "tmp", "test"), "test"));
-  ASSERT_SOME(
-      os::write(path::join(imagePath, "manifest"), stringify(manifest)));
-
-  // Recover. This is when the image in the store is loaded.
-  AWAIT_READY(provisioners1.get()[Image::APPC]->recover({}, {}));
-
-  Image image;
-  image.mutable_appc()->set_name("foo.com/bar");
-
-  ContainerID containerId;
-  containerId.set_value(UUID::random().toString());
-
-  Future<string> rootfs =
-    provisioners1.get()[Image::APPC]->provision(containerId, image);
-  AWAIT_READY(rootfs);
-
-  // Create a new provisioner to recover the state from the container.
-  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners2 =
-    Provisioner::create(flags, &fetcher);
-  ASSERT_SOME(provisioners2);
-  ASSERT_TRUE(provisioners2.get().contains(Image::APPC));
-
-  mesos::slave::ContainerState state;
-
-  // Here we are using an ExecutorInfo in the ContainerState without a
-  // ContainerInfo. This is the situation where the Image is specified via
-  // --default_container_info so it's not part of the recovered ExecutorInfo.
-  state.mutable_container_id()->CopyFrom(containerId);
-
-  AWAIT_READY(provisioners2.get()[Image::APPC]->recover({state}, {}));
-
-  // It's possible for the user to provision two different rootfses
-  // from the same image.
-  AWAIT_READY(provisioners2.get()[Image::APPC]->provision(containerId, image));
-
-  string containerDir = path::join(
-      flags.work_dir,
-      "provisioners",
-      stringify(Image::APPC),
-      "containers",
-      containerId.value());
-
-  Try<list<string>> rootfses = os::ls(path::join(
-      containerDir,
-      "backends",
-      flags.appc_provisioner_backend,
-      "rootfses"));
-
-  ASSERT_SOME(rootfses);
-
-  // Verify that the rootfs is successfully provisioned.
-  EXPECT_EQ(2u, rootfses.get().size());
-
-  Future<bool> destroy = provisioners2.get()[Image::APPC]->destroy(containerId);
-  AWAIT_READY(destroy);
-  EXPECT_TRUE(destroy.get());
-
-  // The container directory is successfully cleaned up.
-  EXPECT_FALSE(os::exists(containerDir));
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/tests/containerizer/docker_provisioner_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/docker_provisioner_tests.cpp b/src/tests/containerizer/docker_provisioner_tests.cpp
deleted file mode 100644
index a3ccbc0..0000000
--- a/src/tests/containerizer/docker_provisioner_tests.cpp
+++ /dev/null
@@ -1,688 +0,0 @@
-/**
- * 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 <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <stout/duration.hpp>
-
-#include <process/address.hpp>
-#include <process/clock.hpp>
-#include <process/future.hpp>
-#include <process/gmock.hpp>
-#include <process/owned.hpp>
-#include <process/socket.hpp>
-#include <process/subprocess.hpp>
-
-#include <process/ssl/gtest.hpp>
-
-#include "slave/containerizer/provisioners/docker/registry_client.hpp"
-#include "slave/containerizer/provisioners/docker/token_manager.hpp"
-
-#include "tests/mesos.hpp"
-
-using std::map;
-using std::string;
-using std::vector;
-
-using namespace mesos::internal::slave::docker::registry;
-
-using process::Clock;
-using process::Future;
-using process::Owned;
-
-using process::network::Socket;
-
-using ManifestResponse = RegistryClient::ManifestResponse;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-/**
- * Provides token operations and defaults.
- */
-class TokenHelper {
-protected:
-  const string hdrBase64 = base64::encode(
-    "{ \
-      \"alg\":\"ES256\", \
-      \"typ\":\"JWT\", \
-      \"x5c\":[\"test\"] \
-    }");
-
-  string getClaimsBase64() const
-  {
-    return base64::encode(claimsJsonString);
-  }
-
-  string getTokenString() const
-  {
-    return  hdrBase64 + "." + getClaimsBase64() + "." + signBase64;
-  }
-
-  string getDefaultTokenString()
-  {
-    // Construct response and send(server side).
-    const double expirySecs = Clock::now().secs() + Days(365).secs();
-
-    claimsJsonString =
-      "{\"access\" \
-        :[ \
-        { \
-          \"type\":\"repository\", \
-            \"name\":\"library/busybox\", \
-            \"actions\":[\"pull\"]}], \
-            \"aud\":\"registry.docker.io\", \
-            \"exp\":" + stringify(expirySecs) + ", \
-            \"iat\":1438887168, \
-            \"iss\":\"auth.docker.io\", \
-            \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
-            \"nbf\":1438887166, \
-            \"sub\":\"\" \
-        }";
-
-    return getTokenString();
-  }
-
-  const string signBase64 = base64::encode("{\"\"}");
-  string claimsJsonString;
-};
-
-
-/**
- * Fixture for testing TokenManager component.
- */
-class RegistryTokenTest : public TokenHelper, public ::testing::Test
-{};
-
-
-// Tests JSON Web Token parsing for a valid token string.
-TEST_F(RegistryTokenTest, ValidToken)
-{
-  const double expirySecs = Clock::now().secs() + Days(365).secs();
-
-  claimsJsonString =
-    "{\"access\" \
-      :[ \
-        { \
-          \"type\":\"repository\", \
-          \"name\":\"library/busybox\", \
-          \"actions\":[\"pull\"]}], \
-          \"aud\":\"registry.docker.io\", \
-          \"exp\":" + stringify(expirySecs) + ", \
-          \"iat\":1438887168, \
-          \"iss\":\"auth.docker.io\", \
-          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
-          \"nbf\":1438887166, \
-          \"sub\":\"\" \
-         }";
-
-  Try<Token> token = Token::create(getTokenString());
-
-  ASSERT_SOME(token);
-}
-
-
-// Tests JSON Web Token parsing for a token string with expiration date in the
-// past.
-TEST_F(RegistryTokenTest, ExpiredToken)
-{
-  const double expirySecs = Clock::now().secs() - Days(365).secs();
-
-  claimsJsonString =
-    "{\"access\" \
-      :[ \
-        { \
-          \"type\":\"repository\", \
-          \"name\":\"library/busybox\", \
-          \"actions\":[\"pull\"]}], \
-          \"aud\":\"registry.docker.io\", \
-          \"exp\":" + stringify(expirySecs) + ", \
-          \"iat\":1438887166, \
-          \"iss\":\"auth.docker.io\", \
-          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
-          \"nbf\":1438887166, \
-          \"sub\":\"\" \
-         }";
-
-  Try<Token> token = Token::create(getTokenString());
-
-  EXPECT_ERROR(token);
-}
-
-
-// Tests JSON Web Token parsing for a token string with no expiration date.
-TEST_F(RegistryTokenTest, NoExpiration)
-{
-  claimsJsonString =
-    "{\"access\" \
-      :[ \
-        { \
-          \"type\":\"repository\", \
-          \"name\":\"library/busybox\", \
-          \"actions\":[\"pull\"]}], \
-          \"aud\":\"registry.docker.io\", \
-          \"iat\":1438887166, \
-          \"iss\":\"auth.docker.io\", \
-          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
-          \"nbf\":1438887166, \
-          \"sub\":\"\" \
-      }";
-
-  const Try<Token> token = Token::create(getTokenString());
-
-  ASSERT_SOME(token);
-}
-
-
-// Tests JSON Web Token parsing for a token string with not-before date in the
-// future.
-TEST_F(RegistryTokenTest, NotBeforeInFuture)
-{
-  const double expirySecs = Clock::now().secs() + Days(365).secs();
-  const double nbfSecs = Clock::now().secs() + Days(7).secs();
-
-  claimsJsonString =
-    "{\"access\" \
-      :[ \
-        { \
-          \"type\":\"repository\", \
-          \"name\":\"library/busybox\", \
-          \"actions\":[\"pull\"]}], \
-          \"aud\":\"registry.docker.io\", \
-          \"exp\":" + stringify(expirySecs) + ", \
-          \"iat\":1438887166, \
-          \"iss\":\"auth.docker.io\", \
-          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
-          \"nbf\":" + stringify(nbfSecs) + ", \
-          \"sub\":\"\" \
-         }";
-
-  const Try<Token> token = Token::create(getTokenString());
-
-  ASSERT_SOME(token);
-  ASSERT_EQ(token.get().isValid(), false);
-}
-
-
-#ifdef USE_SSL_SOCKET
-
-// Test suite for docker registry tests.
-class RegistryClientTest : public virtual SSLTest, public TokenHelper
-{
-protected:
-  RegistryClientTest() {}
-
-  static void SetUpTestCase()
-  {
-    SSLTest::SetUpTestCase();
-
-    if (os::mkdir(RegistryClientTest::OUTPUT_DIR).isError()) {
-      SSLTest::cleanup_directories();
-      ABORT("Could not create temporary directory: " +
-          RegistryClientTest::OUTPUT_DIR);
-    }
-  }
-
-  static void TearDownTestCase()
-  {
-    SSLTest::TearDownTestCase();
-
-    os::rmdir(RegistryClientTest::OUTPUT_DIR);
-  }
-
-  static const string OUTPUT_DIR;
-};
-
-const string RegistryClientTest::OUTPUT_DIR = "output_dir";
-
-// Tests TokenManager for a simple token request.
-TEST_F(RegistryClientTest, SimpleGetToken)
-{
-  Try<Socket> server = setup_server({
-      {"SSL_ENABLED", "true"},
-      {"SSL_KEY_FILE", key_path().value},
-      {"SSL_CERT_FILE", certificate_path().value}});
-
-  ASSERT_SOME(server);
-  ASSERT_SOME(server.get().address());
-  ASSERT_SOME(server.get().address().get().hostname());
-
-  Future<Socket> socket = server.get().accept();
-
-  // Create URL from server hostname and port.
-  const process::http::URL url(
-      "https",
-      server.get().address().get().hostname().get(),
-      server.get().address().get().port);
-
-  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url);
-  ASSERT_SOME(tokenMgr);
-
-  Future<Token> token =
-    tokenMgr.get()->getToken(
-        "registry.docker.io",
-        "repository:library/busybox:pull",
-        None());
-
-  AWAIT_ASSERT_READY(socket);
-
-  // Construct response and send(server side).
-  const double expirySecs = Clock::now().secs() + Days(365).secs();
-
-  claimsJsonString =
-    "{\"access\" \
-      :[ \
-        { \
-          \"type\":\"repository\", \
-          \"name\":\"library/busybox\", \
-          \"actions\":[\"pull\"]}], \
-          \"aud\":\"registry.docker.io\", \
-          \"exp\":" + stringify(expirySecs) + ", \
-          \"iat\":1438887168, \
-          \"iss\":\"auth.docker.io\", \
-          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
-          \"nbf\":1438887166, \
-          \"sub\":\"\" \
-         }";
-
-  const string tokenString(getTokenString());
-  const string tokenResponse = "{\"token\":\"" + tokenString + "\"}";
-
-  const string buffer =
-    string("HTTP/1.1 200 OK\r\n") +
-    "Content-Length : " +
-    stringify(tokenResponse.length()) + "\r\n" +
-    "\r\n" +
-    tokenResponse;
-
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer));
-
-  AWAIT_ASSERT_READY(token);
-  ASSERT_EQ(token.get().raw, tokenString);
-}
-
-
-// Tests TokenManager for bad token response from server.
-TEST_F(RegistryClientTest, BadTokenResponse)
-{
-  Try<Socket> server = setup_server({
-      {"SSL_ENABLED", "true"},
-      {"SSL_KEY_FILE", key_path().value},
-      {"SSL_CERT_FILE", certificate_path().value}});
-
-  ASSERT_SOME(server);
-  ASSERT_SOME(server.get().address());
-  ASSERT_SOME(server.get().address().get().hostname());
-
-  Future<Socket> socket = server.get().accept();
-
-  // Create URL from server hostname and port.
-  const process::http::URL url(
-      "https",
-      server.get().address().get().hostname().get(),
-      server.get().address().get().port);
-
-  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url);
-  ASSERT_SOME(tokenMgr);
-
-  Future<Token> token =
-    tokenMgr.get()->getToken(
-        "registry.docker.io",
-        "repository:library/busybox:pull",
-        None());
-
-  AWAIT_ASSERT_READY(socket);
-
-  const string tokenString("bad token");
-  const string tokenResponse = "{\"token\":\"" + tokenString + "\"}";
-
-  const string buffer =
-    string("HTTP/1.1 200 OK\r\n") +
-    "Content-Length : " +
-    stringify(tokenResponse.length()) + "\r\n" +
-    "\r\n" +
-    tokenResponse;
-
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer));
-
-  AWAIT_FAILED(token);
-}
-
-
-// Tests TokenManager for request to invalid server.
-TEST_F(RegistryClientTest, BadTokenServerAddress)
-{
-  // Create an invalid URL with current time.
-  const process::http::URL url("https", stringify(Clock::now().secs()), 0);
-
-  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url);
-  ASSERT_SOME(tokenMgr);
-
-  Future<Token> token =
-    tokenMgr.get()->getToken(
-        "registry.docker.io",
-        "repository:library/busybox:pull",
-        None());
-
-  AWAIT_FAILED(token);
-}
-
-
-// Tests docker registry's getManifest API.
-TEST_F(RegistryClientTest, SimpleGetManifest)
-{
-  Try<Socket> server = setup_server({
-      {"SSL_ENABLED", "true"},
-      {"SSL_KEY_FILE", key_path().value},
-      {"SSL_CERT_FILE", certificate_path().value}});
-
-  ASSERT_SOME(server);
-  ASSERT_SOME(server.get().address());
-  ASSERT_SOME(server.get().address().get().hostname());
-
-  Future<Socket> socket = server.get().accept();
-
-  const process::http::URL url(
-      "https",
-      server.get().address().get().hostname().get(),
-      server.get().address().get().port);
-
-  Try<Owned<RegistryClient>> registryClient =
-    RegistryClient::create(url, url, None());
-
-  ASSERT_SOME(registryClient);
-
-  Future<ManifestResponse> manifestResponseFuture =
-    registryClient.get()->getManifest("library/busybox", "latest", None());
-
-  const string unauthResponseHeaders = "Www-Authenticate: Bearer"
-    " realm=\"https://auth.docker.io/token\","
-    "service=" + stringify(server.get().address().get()) + ","
-    "scope=\"repository:library/busybox:pull\"";
-
-  const string unauthHttpResponse =
-    string("HTTP/1.1 401 Unauthorized\r\n") +
-    unauthResponseHeaders + "\r\n" +
-    "\r\n";
-
-  AWAIT_ASSERT_READY(socket);
-
-  // Send 401 Unauthorized response for a manifest request.
-  Future<string> manifestHttpRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(manifestHttpRequestFuture);
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse));
-
-  // Token response.
-  socket = server.get().accept();
-  AWAIT_ASSERT_READY(socket);
-
-  Future<string> tokenRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(tokenRequestFuture);
-
-  const string tokenResponse =
-    "{\"token\":\"" + getDefaultTokenString() + "\"}";
-
-  const string tokenHttpResponse =
-    string("HTTP/1.1 200 OK\r\n") +
-    "Content-Length : " +
-    stringify(tokenResponse.length()) + "\r\n" +
-    "\r\n" +
-    tokenResponse;
-
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse));
-
-  // Manifest response.
-  socket = server.get().accept();
-  AWAIT_ASSERT_READY(socket);
-
-  manifestHttpRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(manifestHttpRequestFuture);
-
-  const string manifestResponse = " \
-    { \
-      \"schemaVersion\": 1, \
-      \"name\": \"library/busybox\", \
-      \"tag\": \"latest\",  \
-      \"architecture\": \"amd64\",  \
-      \"fsLayers\": [ \
-        { \
-          \"blobSum\": \
-  \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\"  \
-        },  \
-        { \
-          \"blobSum\": \
-  \"sha256:1db09adb5ddd7f1a07b6d585a7db747a51c7bd17418d47e91f901bdf420abd66\"  \
-        },  \
-        { \
-          \"blobSum\": \
-  \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\"  \
-        } \
-      ],  \
-       \"signatures\": [  \
-          { \
-             \"header\": {  \
-                \"jwk\": {  \
-                   \"crv\": \"P-256\",  \
-                   \"kid\": \
-           \"OOI5:SI3T:LC7D:O7DX:FY6S:IAYW:WDRN:VQEM:BCFL:OIST:Q3LO:GTQQ\",  \
-                   \"kty\": \"EC\", \
-                   \"x\": \"J2N5ePGhlblMI2cdsR6NrAG_xbNC_X7s1HRtk5GXvzM\", \
-                   \"y\": \"Idr-tEBjnNnfq6_71aeXBi3Z9ah_rrE209l4wiaohk0\" \
-                },  \
-                \"alg\": \"ES256\"  \
-             }, \
-             \"signature\": \
-\"65vq57TakC_yperuhfefF4uvTbKO2L45gYGDs5bIEgOEarAs7_"
-"4dbEV5u-W7uR8gF6EDKfowUCmTq3a5vEOJ3w\", \
-       \"protected\": \
-       \"eyJmb3JtYXRMZW5ndGgiOjUwNTgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS"
-       "0wOC0xMVQwMzo0Mjo1OVoifQ\"  \
-          } \
-       ]  \
-    }";
-
-  const string manifestHttpResponse =
-    string("HTTP/1.1 200 OK\r\n") +
-    "Content-Length : " +
-    stringify(manifestResponse.length()) + "\r\n" +
-    "Docker-Content-Digest: "
-    "sha256:df9e13f36d2d5b30c16bfbf2a6110c45ebed0bfa1ea42d357651bc6c736d5322"
-    + "\r\n" +
-    "\r\n" +
-    manifestResponse;
-
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(manifestHttpResponse));
-
-  AWAIT_ASSERT_READY(manifestResponseFuture);
-}
-
-
-// Tests docker registry's getBlob API.
-TEST_F(RegistryClientTest, SimpleGetBlob)
-{
-  Try<Socket> server = setup_server({
-      {"SSL_ENABLED", "true"},
-      {"SSL_KEY_FILE", key_path().value},
-      {"SSL_CERT_FILE", certificate_path().value}});
-
-  ASSERT_SOME(server);
-  ASSERT_SOME(server.get().address());
-  ASSERT_SOME(server.get().address().get().hostname());
-
-  Future<Socket> socket = server.get().accept();
-
-  const process::http::URL url(
-      "https",
-      server.get().address().get().hostname().get(),
-      server.get().address().get().port);
-
-  Try<Owned<RegistryClient>> registryClient =
-    RegistryClient::create(url, url, None());
-
-  ASSERT_SOME(registryClient);
-
-  const Path blobPath(RegistryClientTest::OUTPUT_DIR + "/blob");
-
-  Future<size_t> resultFuture =
-    registryClient.get()->getBlob(
-        "/blob",
-        "digest",
-        blobPath,
-        None(),
-        None());
-
-  const string unauthResponseHeaders = "WWW-Authenticate: Bearer"
-    " realm=\"https://auth.docker.io/token\","
-    "service=" + stringify(server.get().address().get()) + ","
-    "scope=\"repository:library/busybox:pull\"";
-
-  const string unauthHttpResponse =
-    string("HTTP/1.1 401 Unauthorized\r\n") +
-    unauthResponseHeaders + "\r\n" +
-    "\r\n";
-
-  AWAIT_ASSERT_READY(socket);
-
-  // Send 401 Unauthorized response.
-  Future<string> blobHttpRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(blobHttpRequestFuture);
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse));
-
-  // Send token response.
-  socket = server.get().accept();
-  AWAIT_ASSERT_READY(socket);
-
-  Future<string> tokenRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(tokenRequestFuture);
-
-  const string tokenResponse =
-    "{\"token\":\"" + getDefaultTokenString() + "\"}";
-
-  const string tokenHttpResponse =
-    string("HTTP/1.1 200 OK\r\n") +
-    "Content-Length : " +
-    stringify(tokenResponse.length()) + "\r\n" +
-    "\r\n" +
-    tokenResponse;
-
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse));
-
-  // Send redirect.
-  socket = server.get().accept();
-  AWAIT_ASSERT_READY(socket);
-
-  blobHttpRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(blobHttpRequestFuture);
-
-  const string redirectHttpResponse =
-    string("HTTP/1.1 307 Temporary Redirect\r\n") +
-    "Location: https://" +
-    stringify(server.get().address().get()) + "\r\n" +
-    "\r\n";
-
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(redirectHttpResponse));
-
-  // Finally send blob response.
-  socket = server.get().accept();
-  AWAIT_ASSERT_READY(socket);
-
-  blobHttpRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(blobHttpRequestFuture);
-
-  const string blobResponse = stringify(Clock::now());
-
-  const string blobHttpResponse =
-    string("HTTP/1.1 200 OK\r\n") +
-    "Content-Length : " +
-    stringify(blobResponse.length()) + "\r\n" +
-    "\r\n" +
-    blobResponse;
-
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(blobHttpResponse));
-
-  AWAIT_ASSERT_READY(resultFuture);
-
-  Try<string> blob = os::read(blobPath);
-  ASSERT_SOME(blob);
-  ASSERT_EQ(blob.get(), blobResponse);
-}
-
-
-TEST_F(RegistryClientTest, BadRequest)
-{
-  Try<Socket> server = setup_server({
-      {"SSL_ENABLED", "true"},
-      {"SSL_KEY_FILE", key_path().value},
-      {"SSL_CERT_FILE", certificate_path().value}});
-
-  ASSERT_SOME(server);
-  ASSERT_SOME(server.get().address());
-  ASSERT_SOME(server.get().address().get().hostname());
-
-  Future<Socket> socket = server.get().accept();
-
-  const process::http::URL url(
-      "https",
-      server.get().address().get().hostname().get(),
-      server.get().address().get().port);
-
-  Try<Owned<RegistryClient>> registryClient =
-    RegistryClient::create(url, url, None());
-
-  ASSERT_SOME(registryClient);
-
-  const Path blobPath(RegistryClientTest::OUTPUT_DIR + "/blob");
-
-  Future<size_t> resultFuture =
-    registryClient.get()->getBlob(
-        "/blob",
-        "digest",
-        blobPath,
-        None(),
-        None());
-
-  const string badRequestResponse =
-    "{\"errors\": [{\"message\": \"Error1\" }, {\"message\": \"Error2\"}]}";
-
-  const string badRequestHttpResponse =
-    string("HTTP/1.1 400 Bad Request\r\n") +
-    "Content-Length : " + stringify(badRequestResponse.length()) + "\r\n" +
-    "\r\n" +
-    badRequestResponse;
-
-  AWAIT_ASSERT_READY(socket);
-
-  // Send 400 Bad Request.
-  Future<string> blobHttpRequestFuture = Socket(socket.get()).recv();
-  AWAIT_ASSERT_READY(blobHttpRequestFuture);
-  AWAIT_ASSERT_READY(Socket(socket.get()).send(badRequestHttpResponse));
-
-  AWAIT_FAILED(resultFuture);
-
-  ASSERT_TRUE(strings::contains(resultFuture.failure(), "Error1"));
-  ASSERT_TRUE(strings::contains(resultFuture.failure(), "Error2"));
-}
-
-#endif // USE_SSL_SOCKET
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/tests/containerizer/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/provisioner.hpp b/src/tests/containerizer/provisioner.hpp
index a26b813..3ae2400 100644
--- a/src/tests/containerizer/provisioner.hpp
+++ b/src/tests/containerizer/provisioner.hpp
@@ -26,7 +26,7 @@
 #include <stout/hashmap.hpp>
 #include <stout/stringify.hpp>
 
-#include "slave/containerizer/provisioner.hpp"
+#include "slave/containerizer/provisioner/provisioner.hpp"
 
 #include "tests/containerizer/rootfs.hpp"
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/tests/containerizer/provisioner_appc_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/provisioner_appc_tests.cpp b/src/tests/containerizer/provisioner_appc_tests.cpp
new file mode 100644
index 0000000..3318557
--- /dev/null
+++ b/src/tests/containerizer/provisioner_appc_tests.cpp
@@ -0,0 +1,415 @@
+/**
+ * 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 <string>
+
+#include <mesos/slave/isolator.hpp>
+
+#include <process/gtest.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/json.hpp>
+#include <stout/os.hpp>
+#include <stout/path.hpp>
+#include <stout/stringify.hpp>
+#include <stout/uuid.hpp>
+
+#include "slave/containerizer/provisioner/provisioner.hpp"
+
+#include "slave/containerizer/provisioner/appc/spec.hpp"
+#include "slave/containerizer/provisioner/appc/store.hpp"
+
+#include "tests/utils.hpp"
+
+using std::list;
+using std::string;
+using std::vector;
+
+using namespace process;
+
+using namespace mesos::internal::slave::appc;
+
+using mesos::internal::slave::Fetcher;
+using mesos::internal::slave::Provisioner;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class AppcProvisionerTest : public TemporaryDirectoryTest {};
+
+
+TEST_F(AppcProvisionerTest, ValidateImageManifest)
+{
+  JSON::Value manifest = JSON::parse(
+      "{"
+      "  \"acKind\": \"ImageManifest\","
+      "  \"acVersion\": \"0.6.1\","
+      "  \"name\": \"foo.com/bar\","
+      "  \"labels\": ["
+      "    {"
+      "      \"name\": \"version\","
+      "      \"value\": \"1.0.0\""
+      "    },"
+      "    {"
+      "      \"name\": \"arch\","
+      "      \"value\": \"amd64\""
+      "    },"
+      "    {"
+      "      \"name\": \"os\","
+      "      \"value\": \"linux\""
+      "    }"
+      "  ],"
+      "  \"annotations\": ["
+      "    {"
+      "      \"name\": \"created\","
+      "      \"value\": \"1438983392\""
+      "    }"
+      "  ]"
+      "}").get();
+
+  EXPECT_SOME(spec::parse(stringify(manifest)));
+
+  // Incorrect acKind for image manifest.
+  manifest = JSON::parse(
+      "{"
+      "  \"acKind\": \"PodManifest\","
+      "  \"acVersion\": \"0.6.1\","
+      "  \"name\": \"foo.com/bar\""
+      "}").get();
+
+  EXPECT_ERROR(spec::parse(stringify(manifest)));
+}
+
+
+TEST_F(AppcProvisionerTest, ValidateLayout)
+{
+  string image = os::getcwd();
+
+  JSON::Value manifest = JSON::parse(
+      "{"
+      "  \"acKind\": \"ImageManifest\","
+      "  \"acVersion\": \"0.6.1\","
+      "  \"name\": \"foo.com/bar\""
+      "}").get();
+
+  ASSERT_SOME(os::write(path::join(image, "manifest"), stringify(manifest)));
+
+  // Missing rootfs.
+  EXPECT_SOME(spec::validateLayout(image));
+
+  ASSERT_SOME(os::mkdir(path::join(image, "rootfs", "tmp")));
+  ASSERT_SOME(os::write(path::join(image, "rootfs", "tmp", "test"), "test"));
+
+  EXPECT_NONE(spec::validateLayout(image));
+}
+
+
+TEST_F(AppcProvisionerTest, StoreRecover)
+{
+  // Create store.
+  slave::Flags flags;
+  flags.appc_store_dir = path::join(os::getcwd(), "store");
+  Try<Owned<Store>> store = Store::create(flags);
+  ASSERT_SOME(store);
+
+  // Create a simple image in the store:
+  // <store>
+  // |--images
+  //    |--<id>
+  //       |--manifest
+  //       |--rootfs/tmp/test
+  JSON::Value manifest = JSON::parse(
+      "{"
+      "  \"acKind\": \"ImageManifest\","
+      "  \"acVersion\": \"0.6.1\","
+      "  \"name\": \"foo.com/bar\","
+      "  \"labels\": ["
+      "    {"
+      "      \"name\": \"version\","
+      "      \"value\": \"1.0.0\""
+      "    },"
+      "    {"
+      "      \"name\": \"arch\","
+      "      \"value\": \"amd64\""
+      "    },"
+      "    {"
+      "      \"name\": \"os\","
+      "      \"value\": \"linux\""
+      "    }"
+      "  ],"
+      "  \"annotations\": ["
+      "    {"
+      "      \"name\": \"created\","
+      "      \"value\": \"1438983392\""
+      "    }"
+      "  ]"
+      "}").get();
+
+  // The 'imageId' below has the correct format but it's not computed by
+  // hashing the tarball of the image. It's OK here as we assume
+  // the images under 'images' have passed such check when they are
+  // downloaded and validated.
+  string imageId =
+    "sha512-e77d96aa0240eedf134b8c90baeaf76dca8e78691836301d7498c84020446042e"
+    "797b296d6ab296e0954c2626bfb264322ebeb8f447dac4fac6511ea06bc61f0";
+
+  string imagePath = path::join(flags.appc_store_dir, "images", imageId);
+
+  ASSERT_SOME(os::mkdir(path::join(imagePath, "rootfs", "tmp")));
+  ASSERT_SOME(
+      os::write(path::join(imagePath, "rootfs", "tmp", "test"), "test"));
+  ASSERT_SOME(
+      os::write(path::join(imagePath, "manifest"), stringify(manifest)));
+
+  // Recover the image from disk.
+  AWAIT_READY(store.get()->recover());
+
+  Image image;
+  image.mutable_appc()->set_name("foo.com/bar");
+  Future<vector<string>> layers = store.get()->get(image.appc());
+  AWAIT_READY(layers);
+
+  EXPECT_EQ(1u, layers.get().size());
+  ASSERT_SOME(os::realpath(imagePath));
+  EXPECT_EQ(
+      os::realpath(path::join(imagePath, "rootfs")).get(),
+      layers.get().front());
+}
+
+
+#ifdef __linux__
+// This test verifies that the provisioner can provision an rootfs from an
+// image that is already put into the store directory.
+TEST_F(AppcProvisionerTest, ROOT_Provision)
+{
+  // Create provisioner.
+  slave::Flags flags;
+  flags.appc_store_dir = path::join(os::getcwd(), "store");
+  flags.appc_provisioner_backend = "bind";
+  flags.provisioners = "appc";
+  flags.work_dir = "work_dir";
+
+  Fetcher fetcher;
+  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners =
+    Provisioner::create(flags, &fetcher);
+  ASSERT_SOME(provisioners);
+  ASSERT_TRUE(provisioners.get().contains(Image::APPC));
+
+  // Create a simple image in the store:
+  // <store>
+  // |--images
+  //    |--<id>
+  //       |--manifest
+  //       |--rootfs/tmp/test
+  JSON::Value manifest = JSON::parse(
+      "{"
+      "  \"acKind\": \"ImageManifest\","
+      "  \"acVersion\": \"0.6.1\","
+      "  \"name\": \"foo.com/bar\","
+      "  \"labels\": ["
+      "    {"
+      "      \"name\": \"version\","
+      "      \"value\": \"1.0.0\""
+      "    },"
+      "    {"
+      "      \"name\": \"arch\","
+      "      \"value\": \"amd64\""
+      "    },"
+      "    {"
+      "      \"name\": \"os\","
+      "      \"value\": \"linux\""
+      "    }"
+      "  ],"
+      "  \"annotations\": ["
+      "    {"
+      "      \"name\": \"created\","
+      "      \"value\": \"1438983392\""
+      "    }"
+      "  ]"
+      "}").get();
+
+  // The 'imageId' below has the correct format but it's not computed by
+  // hashing the tarball of the image. It's OK here as we assume
+  // the images under 'images' have passed such check when they are
+  // downloaded and validated.
+  string imageId =
+    "sha512-e77d96aa0240eedf134b8c90baeaf76dca8e78691836301d7498c84020446042e"
+    "797b296d6ab296e0954c2626bfb264322ebeb8f447dac4fac6511ea06bc61f0";
+
+  string imagePath = path::join(flags.appc_store_dir, "images", imageId);
+
+  ASSERT_SOME(os::mkdir(path::join(imagePath, "rootfs", "tmp")));
+  ASSERT_SOME(
+      os::write(path::join(imagePath, "rootfs", "tmp", "test"), "test"));
+  ASSERT_SOME(
+      os::write(path::join(imagePath, "manifest"), stringify(manifest)));
+
+  // Recover. This is when the image in the store is loaded.
+  AWAIT_READY(provisioners.get()[Image::APPC]->recover({}, {}));
+
+  // Simulate a task that requires an image.
+  Image image;
+  image.mutable_appc()->set_name("foo.com/bar");
+
+  ContainerID containerId;
+  containerId.set_value("12345");
+
+  Future<string> rootfs =
+    provisioners.get()[Image::APPC]->provision(containerId, image);
+  AWAIT_READY(rootfs);
+
+  string containerDir = path::join(
+      flags.work_dir,
+      "provisioners",
+      stringify(Image::APPC),
+      "containers",
+      containerId.value());
+
+  Try<list<string>> rootfses = os::ls(path::join(
+      containerDir,
+      "backends",
+      flags.appc_provisioner_backend,
+      "rootfses"));
+
+  ASSERT_SOME(rootfses);
+
+  // Verify that the rootfs is successfully provisioned.
+  EXPECT_EQ(1u, rootfses.get().size());
+  EXPECT_EQ(rootfses.get().front(), Path(rootfs.get()).basename());
+
+  Future<bool> destroy = provisioners.get()[Image::APPC]->destroy(containerId);
+  AWAIT_READY(destroy);
+
+  // One rootfs is destroyed.
+  EXPECT_TRUE(destroy.get());
+
+  // The container directory is successfully cleaned up.
+  EXPECT_FALSE(os::exists(containerDir));
+}
+#endif // __linux__
+
+
+// This test verifies that a provisioner can recover the rootfs provisioned
+// by a previous provisioner and then destroy it. Note that we use the copy
+// backend in this test so Linux is not required.
+TEST_F(AppcProvisionerTest, Recover)
+{
+  // Create provisioner.
+  slave::Flags flags;
+  flags.appc_store_dir = path::join(os::getcwd(), "store");
+  flags.appc_provisioner_backend = "copy";
+  flags.provisioners = "appc";
+  flags.work_dir = "work_dir";
+
+  Fetcher fetcher;
+  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners1 =
+    Provisioner::create(flags, &fetcher);
+  ASSERT_SOME(provisioners1);
+  ASSERT_TRUE(provisioners1.get().contains(Image::APPC));
+
+  // Create a simple image in the store:
+  // <store>
+  // |--images
+  //    |--<id>
+  //       |--manifest
+  //       |--rootfs/tmp/test
+  JSON::Value manifest = JSON::parse(
+      "{"
+      "  \"acKind\": \"ImageManifest\","
+      "  \"acVersion\": \"0.6.1\","
+      "  \"name\": \"foo.com/bar\""
+      "}").get();
+
+  // The 'imageId' below has the correct format but it's not computed by
+  // hashing the tarball of the image. It's OK here as we assume
+  // the images under 'images' have passed such check when they are
+  // downloaded and validated.
+  string imageId =
+    "sha512-e77d96aa0240eedf134b8c90baeaf76dca8e78691836301d7498c84020446042e"
+    "797b296d6ab296e0954c2626bfb264322ebeb8f447dac4fac6511ea06bc61f0";
+
+  string imagePath = path::join(flags.appc_store_dir, "images", imageId);
+
+  ASSERT_SOME(os::mkdir(path::join(imagePath, "rootfs", "tmp")));
+  ASSERT_SOME(
+      os::write(path::join(imagePath, "rootfs", "tmp", "test"), "test"));
+  ASSERT_SOME(
+      os::write(path::join(imagePath, "manifest"), stringify(manifest)));
+
+  // Recover. This is when the image in the store is loaded.
+  AWAIT_READY(provisioners1.get()[Image::APPC]->recover({}, {}));
+
+  Image image;
+  image.mutable_appc()->set_name("foo.com/bar");
+
+  ContainerID containerId;
+  containerId.set_value(UUID::random().toString());
+
+  Future<string> rootfs =
+    provisioners1.get()[Image::APPC]->provision(containerId, image);
+  AWAIT_READY(rootfs);
+
+  // Create a new provisioner to recover the state from the container.
+  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners2 =
+    Provisioner::create(flags, &fetcher);
+  ASSERT_SOME(provisioners2);
+  ASSERT_TRUE(provisioners2.get().contains(Image::APPC));
+
+  mesos::slave::ContainerState state;
+
+  // Here we are using an ExecutorInfo in the ContainerState without a
+  // ContainerInfo. This is the situation where the Image is specified via
+  // --default_container_info so it's not part of the recovered ExecutorInfo.
+  state.mutable_container_id()->CopyFrom(containerId);
+
+  AWAIT_READY(provisioners2.get()[Image::APPC]->recover({state}, {}));
+
+  // It's possible for the user to provision two different rootfses
+  // from the same image.
+  AWAIT_READY(provisioners2.get()[Image::APPC]->provision(containerId, image));
+
+  string containerDir = path::join(
+      flags.work_dir,
+      "provisioners",
+      stringify(Image::APPC),
+      "containers",
+      containerId.value());
+
+  Try<list<string>> rootfses = os::ls(path::join(
+      containerDir,
+      "backends",
+      flags.appc_provisioner_backend,
+      "rootfses"));
+
+  ASSERT_SOME(rootfses);
+
+  // Verify that the rootfs is successfully provisioned.
+  EXPECT_EQ(2u, rootfses.get().size());
+
+  Future<bool> destroy = provisioners2.get()[Image::APPC]->destroy(containerId);
+  AWAIT_READY(destroy);
+  EXPECT_TRUE(destroy.get());
+
+  // The container directory is successfully cleaned up.
+  EXPECT_FALSE(os::exists(containerDir));
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/tests/containerizer/provisioner_backend_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/provisioner_backend_tests.cpp b/src/tests/containerizer/provisioner_backend_tests.cpp
index f2498b1..b72edc6 100644
--- a/src/tests/containerizer/provisioner_backend_tests.cpp
+++ b/src/tests/containerizer/provisioner_backend_tests.cpp
@@ -29,8 +29,8 @@
 #include "linux/fs.hpp"
 #endif // __linux__
 
-#include "slave/containerizer/provisioners/backends/bind.hpp"
-#include "slave/containerizer/provisioners/backends/copy.hpp"
+#include "slave/containerizer/provisioner/backends/bind.hpp"
+#include "slave/containerizer/provisioner/backends/copy.hpp"
 
 #include "tests/flags.hpp"
 #include "tests/utils.hpp"

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/tests/containerizer/provisioner_docker_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/provisioner_docker_tests.cpp b/src/tests/containerizer/provisioner_docker_tests.cpp
new file mode 100644
index 0000000..1b0c304
--- /dev/null
+++ b/src/tests/containerizer/provisioner_docker_tests.cpp
@@ -0,0 +1,688 @@
+/**
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <stout/duration.hpp>
+
+#include <process/address.hpp>
+#include <process/clock.hpp>
+#include <process/future.hpp>
+#include <process/gmock.hpp>
+#include <process/owned.hpp>
+#include <process/socket.hpp>
+#include <process/subprocess.hpp>
+
+#include <process/ssl/gtest.hpp>
+
+#include "slave/containerizer/provisioner/docker/registry_client.hpp"
+#include "slave/containerizer/provisioner/docker/token_manager.hpp"
+
+#include "tests/mesos.hpp"
+
+using std::map;
+using std::string;
+using std::vector;
+
+using namespace mesos::internal::slave::docker::registry;
+
+using process::Clock;
+using process::Future;
+using process::Owned;
+
+using process::network::Socket;
+
+using ManifestResponse = RegistryClient::ManifestResponse;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+/**
+ * Provides token operations and defaults.
+ */
+class TokenHelper {
+protected:
+  const string hdrBase64 = base64::encode(
+    "{ \
+      \"alg\":\"ES256\", \
+      \"typ\":\"JWT\", \
+      \"x5c\":[\"test\"] \
+    }");
+
+  string getClaimsBase64() const
+  {
+    return base64::encode(claimsJsonString);
+  }
+
+  string getTokenString() const
+  {
+    return  hdrBase64 + "." + getClaimsBase64() + "." + signBase64;
+  }
+
+  string getDefaultTokenString()
+  {
+    // Construct response and send(server side).
+    const double expirySecs = Clock::now().secs() + Days(365).secs();
+
+    claimsJsonString =
+      "{\"access\" \
+        :[ \
+        { \
+          \"type\":\"repository\", \
+            \"name\":\"library/busybox\", \
+            \"actions\":[\"pull\"]}], \
+            \"aud\":\"registry.docker.io\", \
+            \"exp\":" + stringify(expirySecs) + ", \
+            \"iat\":1438887168, \
+            \"iss\":\"auth.docker.io\", \
+            \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
+            \"nbf\":1438887166, \
+            \"sub\":\"\" \
+        }";
+
+    return getTokenString();
+  }
+
+  const string signBase64 = base64::encode("{\"\"}");
+  string claimsJsonString;
+};
+
+
+/**
+ * Fixture for testing TokenManager component.
+ */
+class RegistryTokenTest : public TokenHelper, public ::testing::Test
+{};
+
+
+// Tests JSON Web Token parsing for a valid token string.
+TEST_F(RegistryTokenTest, ValidToken)
+{
+  const double expirySecs = Clock::now().secs() + Days(365).secs();
+
+  claimsJsonString =
+    "{\"access\" \
+      :[ \
+        { \
+          \"type\":\"repository\", \
+          \"name\":\"library/busybox\", \
+          \"actions\":[\"pull\"]}], \
+          \"aud\":\"registry.docker.io\", \
+          \"exp\":" + stringify(expirySecs) + ", \
+          \"iat\":1438887168, \
+          \"iss\":\"auth.docker.io\", \
+          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
+          \"nbf\":1438887166, \
+          \"sub\":\"\" \
+         }";
+
+  Try<Token> token = Token::create(getTokenString());
+
+  ASSERT_SOME(token);
+}
+
+
+// Tests JSON Web Token parsing for a token string with expiration date in the
+// past.
+TEST_F(RegistryTokenTest, ExpiredToken)
+{
+  const double expirySecs = Clock::now().secs() - Days(365).secs();
+
+  claimsJsonString =
+    "{\"access\" \
+      :[ \
+        { \
+          \"type\":\"repository\", \
+          \"name\":\"library/busybox\", \
+          \"actions\":[\"pull\"]}], \
+          \"aud\":\"registry.docker.io\", \
+          \"exp\":" + stringify(expirySecs) + ", \
+          \"iat\":1438887166, \
+          \"iss\":\"auth.docker.io\", \
+          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
+          \"nbf\":1438887166, \
+          \"sub\":\"\" \
+         }";
+
+  Try<Token> token = Token::create(getTokenString());
+
+  EXPECT_ERROR(token);
+}
+
+
+// Tests JSON Web Token parsing for a token string with no expiration date.
+TEST_F(RegistryTokenTest, NoExpiration)
+{
+  claimsJsonString =
+    "{\"access\" \
+      :[ \
+        { \
+          \"type\":\"repository\", \
+          \"name\":\"library/busybox\", \
+          \"actions\":[\"pull\"]}], \
+          \"aud\":\"registry.docker.io\", \
+          \"iat\":1438887166, \
+          \"iss\":\"auth.docker.io\", \
+          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
+          \"nbf\":1438887166, \
+          \"sub\":\"\" \
+      }";
+
+  const Try<Token> token = Token::create(getTokenString());
+
+  ASSERT_SOME(token);
+}
+
+
+// Tests JSON Web Token parsing for a token string with not-before date in the
+// future.
+TEST_F(RegistryTokenTest, NotBeforeInFuture)
+{
+  const double expirySecs = Clock::now().secs() + Days(365).secs();
+  const double nbfSecs = Clock::now().secs() + Days(7).secs();
+
+  claimsJsonString =
+    "{\"access\" \
+      :[ \
+        { \
+          \"type\":\"repository\", \
+          \"name\":\"library/busybox\", \
+          \"actions\":[\"pull\"]}], \
+          \"aud\":\"registry.docker.io\", \
+          \"exp\":" + stringify(expirySecs) + ", \
+          \"iat\":1438887166, \
+          \"iss\":\"auth.docker.io\", \
+          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
+          \"nbf\":" + stringify(nbfSecs) + ", \
+          \"sub\":\"\" \
+         }";
+
+  const Try<Token> token = Token::create(getTokenString());
+
+  ASSERT_SOME(token);
+  ASSERT_EQ(token.get().isValid(), false);
+}
+
+
+#ifdef USE_SSL_SOCKET
+
+// Test suite for docker registry tests.
+class RegistryClientTest : public virtual SSLTest, public TokenHelper
+{
+protected:
+  RegistryClientTest() {}
+
+  static void SetUpTestCase()
+  {
+    SSLTest::SetUpTestCase();
+
+    if (os::mkdir(RegistryClientTest::OUTPUT_DIR).isError()) {
+      SSLTest::cleanup_directories();
+      ABORT("Could not create temporary directory: " +
+          RegistryClientTest::OUTPUT_DIR);
+    }
+  }
+
+  static void TearDownTestCase()
+  {
+    SSLTest::TearDownTestCase();
+
+    os::rmdir(RegistryClientTest::OUTPUT_DIR);
+  }
+
+  static const string OUTPUT_DIR;
+};
+
+const string RegistryClientTest::OUTPUT_DIR = "output_dir";
+
+// Tests TokenManager for a simple token request.
+TEST_F(RegistryClientTest, SimpleGetToken)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}});
+
+  ASSERT_SOME(server);
+  ASSERT_SOME(server.get().address());
+  ASSERT_SOME(server.get().address().get().hostname());
+
+  Future<Socket> socket = server.get().accept();
+
+  // Create URL from server hostname and port.
+  const process::http::URL url(
+      "https",
+      server.get().address().get().hostname().get(),
+      server.get().address().get().port);
+
+  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url);
+  ASSERT_SOME(tokenMgr);
+
+  Future<Token> token =
+    tokenMgr.get()->getToken(
+        "registry.docker.io",
+        "repository:library/busybox:pull",
+        None());
+
+  AWAIT_ASSERT_READY(socket);
+
+  // Construct response and send(server side).
+  const double expirySecs = Clock::now().secs() + Days(365).secs();
+
+  claimsJsonString =
+    "{\"access\" \
+      :[ \
+        { \
+          \"type\":\"repository\", \
+          \"name\":\"library/busybox\", \
+          \"actions\":[\"pull\"]}], \
+          \"aud\":\"registry.docker.io\", \
+          \"exp\":" + stringify(expirySecs) + ", \
+          \"iat\":1438887168, \
+          \"iss\":\"auth.docker.io\", \
+          \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \
+          \"nbf\":1438887166, \
+          \"sub\":\"\" \
+         }";
+
+  const string tokenString(getTokenString());
+  const string tokenResponse = "{\"token\":\"" + tokenString + "\"}";
+
+  const string buffer =
+    string("HTTP/1.1 200 OK\r\n") +
+    "Content-Length : " +
+    stringify(tokenResponse.length()) + "\r\n" +
+    "\r\n" +
+    tokenResponse;
+
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer));
+
+  AWAIT_ASSERT_READY(token);
+  ASSERT_EQ(token.get().raw, tokenString);
+}
+
+
+// Tests TokenManager for bad token response from server.
+TEST_F(RegistryClientTest, BadTokenResponse)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}});
+
+  ASSERT_SOME(server);
+  ASSERT_SOME(server.get().address());
+  ASSERT_SOME(server.get().address().get().hostname());
+
+  Future<Socket> socket = server.get().accept();
+
+  // Create URL from server hostname and port.
+  const process::http::URL url(
+      "https",
+      server.get().address().get().hostname().get(),
+      server.get().address().get().port);
+
+  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url);
+  ASSERT_SOME(tokenMgr);
+
+  Future<Token> token =
+    tokenMgr.get()->getToken(
+        "registry.docker.io",
+        "repository:library/busybox:pull",
+        None());
+
+  AWAIT_ASSERT_READY(socket);
+
+  const string tokenString("bad token");
+  const string tokenResponse = "{\"token\":\"" + tokenString + "\"}";
+
+  const string buffer =
+    string("HTTP/1.1 200 OK\r\n") +
+    "Content-Length : " +
+    stringify(tokenResponse.length()) + "\r\n" +
+    "\r\n" +
+    tokenResponse;
+
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer));
+
+  AWAIT_FAILED(token);
+}
+
+
+// Tests TokenManager for request to invalid server.
+TEST_F(RegistryClientTest, BadTokenServerAddress)
+{
+  // Create an invalid URL with current time.
+  const process::http::URL url("https", stringify(Clock::now().secs()), 0);
+
+  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url);
+  ASSERT_SOME(tokenMgr);
+
+  Future<Token> token =
+    tokenMgr.get()->getToken(
+        "registry.docker.io",
+        "repository:library/busybox:pull",
+        None());
+
+  AWAIT_FAILED(token);
+}
+
+
+// Tests docker registry's getManifest API.
+TEST_F(RegistryClientTest, SimpleGetManifest)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}});
+
+  ASSERT_SOME(server);
+  ASSERT_SOME(server.get().address());
+  ASSERT_SOME(server.get().address().get().hostname());
+
+  Future<Socket> socket = server.get().accept();
+
+  const process::http::URL url(
+      "https",
+      server.get().address().get().hostname().get(),
+      server.get().address().get().port);
+
+  Try<Owned<RegistryClient>> registryClient =
+    RegistryClient::create(url, url, None());
+
+  ASSERT_SOME(registryClient);
+
+  Future<ManifestResponse> manifestResponseFuture =
+    registryClient.get()->getManifest("library/busybox", "latest", None());
+
+  const string unauthResponseHeaders = "Www-Authenticate: Bearer"
+    " realm=\"https://auth.docker.io/token\","
+    "service=" + stringify(server.get().address().get()) + ","
+    "scope=\"repository:library/busybox:pull\"";
+
+  const string unauthHttpResponse =
+    string("HTTP/1.1 401 Unauthorized\r\n") +
+    unauthResponseHeaders + "\r\n" +
+    "\r\n";
+
+  AWAIT_ASSERT_READY(socket);
+
+  // Send 401 Unauthorized response for a manifest request.
+  Future<string> manifestHttpRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(manifestHttpRequestFuture);
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse));
+
+  // Token response.
+  socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  Future<string> tokenRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(tokenRequestFuture);
+
+  const string tokenResponse =
+    "{\"token\":\"" + getDefaultTokenString() + "\"}";
+
+  const string tokenHttpResponse =
+    string("HTTP/1.1 200 OK\r\n") +
+    "Content-Length : " +
+    stringify(tokenResponse.length()) + "\r\n" +
+    "\r\n" +
+    tokenResponse;
+
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse));
+
+  // Manifest response.
+  socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  manifestHttpRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(manifestHttpRequestFuture);
+
+  const string manifestResponse = " \
+    { \
+      \"schemaVersion\": 1, \
+      \"name\": \"library/busybox\", \
+      \"tag\": \"latest\",  \
+      \"architecture\": \"amd64\",  \
+      \"fsLayers\": [ \
+        { \
+          \"blobSum\": \
+  \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\"  \
+        },  \
+        { \
+          \"blobSum\": \
+  \"sha256:1db09adb5ddd7f1a07b6d585a7db747a51c7bd17418d47e91f901bdf420abd66\"  \
+        },  \
+        { \
+          \"blobSum\": \
+  \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\"  \
+        } \
+      ],  \
+       \"signatures\": [  \
+          { \
+             \"header\": {  \
+                \"jwk\": {  \
+                   \"crv\": \"P-256\",  \
+                   \"kid\": \
+           \"OOI5:SI3T:LC7D:O7DX:FY6S:IAYW:WDRN:VQEM:BCFL:OIST:Q3LO:GTQQ\",  \
+                   \"kty\": \"EC\", \
+                   \"x\": \"J2N5ePGhlblMI2cdsR6NrAG_xbNC_X7s1HRtk5GXvzM\", \
+                   \"y\": \"Idr-tEBjnNnfq6_71aeXBi3Z9ah_rrE209l4wiaohk0\" \
+                },  \
+                \"alg\": \"ES256\"  \
+             }, \
+             \"signature\": \
+\"65vq57TakC_yperuhfefF4uvTbKO2L45gYGDs5bIEgOEarAs7_"
+"4dbEV5u-W7uR8gF6EDKfowUCmTq3a5vEOJ3w\", \
+       \"protected\": \
+       \"eyJmb3JtYXRMZW5ndGgiOjUwNTgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS"
+       "0wOC0xMVQwMzo0Mjo1OVoifQ\"  \
+          } \
+       ]  \
+    }";
+
+  const string manifestHttpResponse =
+    string("HTTP/1.1 200 OK\r\n") +
+    "Content-Length : " +
+    stringify(manifestResponse.length()) + "\r\n" +
+    "Docker-Content-Digest: "
+    "sha256:df9e13f36d2d5b30c16bfbf2a6110c45ebed0bfa1ea42d357651bc6c736d5322"
+    + "\r\n" +
+    "\r\n" +
+    manifestResponse;
+
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(manifestHttpResponse));
+
+  AWAIT_ASSERT_READY(manifestResponseFuture);
+}
+
+
+// Tests docker registry's getBlob API.
+TEST_F(RegistryClientTest, SimpleGetBlob)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}});
+
+  ASSERT_SOME(server);
+  ASSERT_SOME(server.get().address());
+  ASSERT_SOME(server.get().address().get().hostname());
+
+  Future<Socket> socket = server.get().accept();
+
+  const process::http::URL url(
+      "https",
+      server.get().address().get().hostname().get(),
+      server.get().address().get().port);
+
+  Try<Owned<RegistryClient>> registryClient =
+    RegistryClient::create(url, url, None());
+
+  ASSERT_SOME(registryClient);
+
+  const Path blobPath(RegistryClientTest::OUTPUT_DIR + "/blob");
+
+  Future<size_t> resultFuture =
+    registryClient.get()->getBlob(
+        "/blob",
+        "digest",
+        blobPath,
+        None(),
+        None());
+
+  const string unauthResponseHeaders = "WWW-Authenticate: Bearer"
+    " realm=\"https://auth.docker.io/token\","
+    "service=" + stringify(server.get().address().get()) + ","
+    "scope=\"repository:library/busybox:pull\"";
+
+  const string unauthHttpResponse =
+    string("HTTP/1.1 401 Unauthorized\r\n") +
+    unauthResponseHeaders + "\r\n" +
+    "\r\n";
+
+  AWAIT_ASSERT_READY(socket);
+
+  // Send 401 Unauthorized response.
+  Future<string> blobHttpRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(blobHttpRequestFuture);
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse));
+
+  // Send token response.
+  socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  Future<string> tokenRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(tokenRequestFuture);
+
+  const string tokenResponse =
+    "{\"token\":\"" + getDefaultTokenString() + "\"}";
+
+  const string tokenHttpResponse =
+    string("HTTP/1.1 200 OK\r\n") +
+    "Content-Length : " +
+    stringify(tokenResponse.length()) + "\r\n" +
+    "\r\n" +
+    tokenResponse;
+
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse));
+
+  // Send redirect.
+  socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  blobHttpRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(blobHttpRequestFuture);
+
+  const string redirectHttpResponse =
+    string("HTTP/1.1 307 Temporary Redirect\r\n") +
+    "Location: https://" +
+    stringify(server.get().address().get()) + "\r\n" +
+    "\r\n";
+
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(redirectHttpResponse));
+
+  // Finally send blob response.
+  socket = server.get().accept();
+  AWAIT_ASSERT_READY(socket);
+
+  blobHttpRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(blobHttpRequestFuture);
+
+  const string blobResponse = stringify(Clock::now());
+
+  const string blobHttpResponse =
+    string("HTTP/1.1 200 OK\r\n") +
+    "Content-Length : " +
+    stringify(blobResponse.length()) + "\r\n" +
+    "\r\n" +
+    blobResponse;
+
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(blobHttpResponse));
+
+  AWAIT_ASSERT_READY(resultFuture);
+
+  Try<string> blob = os::read(blobPath);
+  ASSERT_SOME(blob);
+  ASSERT_EQ(blob.get(), blobResponse);
+}
+
+
+TEST_F(RegistryClientTest, BadRequest)
+{
+  Try<Socket> server = setup_server({
+      {"SSL_ENABLED", "true"},
+      {"SSL_KEY_FILE", key_path().value},
+      {"SSL_CERT_FILE", certificate_path().value}});
+
+  ASSERT_SOME(server);
+  ASSERT_SOME(server.get().address());
+  ASSERT_SOME(server.get().address().get().hostname());
+
+  Future<Socket> socket = server.get().accept();
+
+  const process::http::URL url(
+      "https",
+      server.get().address().get().hostname().get(),
+      server.get().address().get().port);
+
+  Try<Owned<RegistryClient>> registryClient =
+    RegistryClient::create(url, url, None());
+
+  ASSERT_SOME(registryClient);
+
+  const Path blobPath(RegistryClientTest::OUTPUT_DIR + "/blob");
+
+  Future<size_t> resultFuture =
+    registryClient.get()->getBlob(
+        "/blob",
+        "digest",
+        blobPath,
+        None(),
+        None());
+
+  const string badRequestResponse =
+    "{\"errors\": [{\"message\": \"Error1\" }, {\"message\": \"Error2\"}]}";
+
+  const string badRequestHttpResponse =
+    string("HTTP/1.1 400 Bad Request\r\n") +
+    "Content-Length : " + stringify(badRequestResponse.length()) + "\r\n" +
+    "\r\n" +
+    badRequestResponse;
+
+  AWAIT_ASSERT_READY(socket);
+
+  // Send 400 Bad Request.
+  Future<string> blobHttpRequestFuture = Socket(socket.get()).recv();
+  AWAIT_ASSERT_READY(blobHttpRequestFuture);
+  AWAIT_ASSERT_READY(Socket(socket.get()).send(badRequestHttpResponse));
+
+  AWAIT_FAILED(resultFuture);
+
+  ASSERT_TRUE(strings::contains(resultFuture.failure(), "Error1"));
+  ASSERT_TRUE(strings::contains(resultFuture.failure(), "Error2"));
+}
+
+#endif // USE_SSL_SOCKET
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {


[2/6] mesos git commit: Moved files to prepare for unifying provisioners.

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/store.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/store.cpp b/src/slave/containerizer/provisioners/appc/store.cpp
deleted file mode 100644
index 33f692c..0000000
--- a/src/slave/containerizer/provisioners/appc/store.cpp
+++ /dev/null
@@ -1,280 +0,0 @@
-/**
- * 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 <list>
-
-#include <glog/logging.h>
-
-#include <process/defer.hpp>
-#include <process/dispatch.hpp>
-
-#include <stout/check.hpp>
-#include <stout/hashmap.hpp>
-#include <stout/os.hpp>
-#include <stout/path.hpp>
-
-#include "slave/containerizer/provisioners/appc/paths.hpp"
-#include "slave/containerizer/provisioners/appc/spec.hpp"
-#include "slave/containerizer/provisioners/appc/store.hpp"
-
-using namespace process;
-
-using std::list;
-using std::string;
-using std::vector;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-
-// Defines a locally cached image (which has passed validation).
-struct CachedImage
-{
-  CachedImage(
-      const AppcImageManifest& _manifest,
-      const string& _id,
-      const string& _path)
-    : manifest(_manifest), id(_id), path(_path) {}
-
-  string rootfs() const
-  {
-    return path::join(path, "rootfs");
-  }
-
-  const AppcImageManifest manifest;
-
-  // Image ID of the format "sha512-value" where "value" is the hex
-  // encoded string of the sha512 digest of the uncompressed tar file
-  // of the image.
-  const string id;
-
-  // Absolute path to the extracted image.
-  const string path;
-};
-
-
-// Helper that implements this:
-// https://github.com/appc/spec/blob/master/spec/aci.md#dependency-matching
-static bool matches(Image::Appc requirements, const CachedImage& candidate)
-{
-  // The name must match.
-  if (candidate.manifest.name() != requirements.name()) {
-    return false;
-  }
-
-  // If an id is specified the candidate must match.
-  if (requirements.has_id() && (candidate.id != requirements.id())) {
-    return false;
-  }
-
-  // Extract labels for easier comparison, this also weeds out duplicates.
-  // TODO(xujyan): Detect duplicate labels in image manifest validation
-  // and Image::Appc validation.
-  hashmap<string, string> requiredLabels;
-  foreach (const Label& label, requirements.labels().labels()) {
-    requiredLabels[label.key()] = label.value();
-  }
-
-  hashmap<string, string> candidateLabels;
-  foreach (const AppcImageManifest::Label& label,
-           candidate.manifest.labels()) {
-    candidateLabels[label.name()] = label.value();
-  }
-
-  // Any label specified must be present and match in the candidate.
-  foreachpair (const string& name,
-               const string& value,
-               requiredLabels) {
-    if (!candidateLabels.contains(name) ||
-        candidateLabels.get(name).get() != value) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-
-class StoreProcess : public Process<StoreProcess>
-{
-public:
-  StoreProcess(const string& root);
-
-  ~StoreProcess() {}
-
-  Future<Nothing> recover();
-
-  Future<vector<string>> get(const Image::Appc& image);
-
-private:
-  // Absolute path to the root directory of the store as defined by
-  // --appc_store_dir.
-  const string root;
-
-  // Mappings: name -> id -> image.
-  hashmap<string, hashmap<string, CachedImage>> images;
-};
-
-
-Try<Owned<Store>> Store::create(const Flags& flags)
-{
-  Try<Nothing> mkdir = os::mkdir(paths::getImagesDir(flags.appc_store_dir));
-  if (mkdir.isError()) {
-    return Error("Failed to create the images directory: " + mkdir.error());
-  }
-
-  // Make sure the root path is canonical so all image paths derived
-  // from it are canonical too.
-  Result<string> root = os::realpath(flags.appc_store_dir);
-  if (!root.isSome()) {
-    // The above mkdir call recursively creates the store directory
-    // if necessary so it cannot be None here.
-    CHECK_ERROR(root);
-    return Error(
-        "Failed to get the realpath of the store directory: " + root.error());
-  }
-
-  return Owned<Store>(new Store(
-      Owned<StoreProcess>(new StoreProcess(root.get()))));
-}
-
-
-Store::Store(Owned<StoreProcess> _process)
-  : process(_process)
-{
-  spawn(CHECK_NOTNULL(process.get()));
-}
-
-
-Store::~Store()
-{
-  terminate(process.get());
-  wait(process.get());
-}
-
-
-Future<Nothing> Store::recover()
-{
-  return dispatch(process.get(), &StoreProcess::recover);
-}
-
-
-Future<vector<string>> Store::get(const Image::Appc& image)
-{
-  return dispatch(process.get(), &StoreProcess::get, image);
-}
-
-
-StoreProcess::StoreProcess(const string& _root) : root(_root) {}
-
-
-// Implemented as a helper function because it's going to be used for a
-// newly downloaded image too.
-static Try<CachedImage> createImage(const string& imagePath)
-{
-  Option<Error> error = spec::validateLayout(imagePath);
-  if (error.isSome()) {
-    return Error("Invalid image layout: " + error.get().message);
-  }
-
-  string imageId = Path(imagePath).basename();
-
-  error = spec::validateImageID(imageId);
-  if (error.isSome()) {
-    return Error("Invalid image ID: " + error.get().message);
-  }
-
-  Try<string> read = os::read(paths::getImageManifestPath(imagePath));
-  if (read.isError()) {
-    return Error("Failed to read manifest: " + read.error());
-  }
-
-  Try<AppcImageManifest> manifest = spec::parse(read.get());
-  if (manifest.isError()) {
-    return Error("Failed to parse manifest: " + manifest.error());
-  }
-
-  return CachedImage(manifest.get(), imageId, imagePath);
-}
-
-
-Future<vector<string>> StoreProcess::get(const Image::Appc& image)
-{
-  if (!images.contains(image.name())) {
-    return Failure("No image named '" + image.name() + "' can be found");
-  }
-
-  // Get local candidates.
-  vector<CachedImage> candidates;
-  foreach (const CachedImage& candidate, images[image.name()].values()) {
-    // The first match is returned.
-    // TODO(xujyan): Some tie-breaking rules are necessary.
-    if (matches(image, candidate)) {
-      LOG(INFO) << "Found match for image '" << image.name()
-                << "' in the store";
-
-      // The Appc store current doesn't support dependencies and this is
-      // enforced by manifest validation: if the image's manifest contains
-      // dependencies it would fail the validation and wouldn't be stored
-      // in the store.
-      return vector<string>({candidate.rootfs()});
-    }
-  }
-
-  return Failure("No image named '" + image.name() +
-                 "' can match the requirements");
-}
-
-
-Future<Nothing> StoreProcess::recover()
-{
-  // Recover everything in the store.
-  Try<list<string>> imageIds = os::ls(paths::getImagesDir(root));
-  if (imageIds.isError()) {
-    return Failure(
-        "Failed to list images under '" +
-        paths::getImagesDir(root) + "': " +
-        imageIds.error());
-  }
-
-  foreach (const string& imageId, imageIds.get()) {
-    string path = paths::getImagePath(root, imageId);
-    if (!os::stat::isdir(path)) {
-      LOG(WARNING) << "Unexpected entry in storage: " << imageId;
-      continue;
-    }
-
-    Try<CachedImage> image = createImage(path);
-    if (image.isError()) {
-      LOG(WARNING) << "Unexpected entry in storage: " << image.error();
-      continue;
-    }
-
-    LOG(INFO) << "Restored image '" << image.get().manifest.name() << "'";
-
-    images[image.get().manifest.name()].put(image.get().id, image.get());
-  }
-
-  return Nothing();
-}
-
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/store.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/store.hpp b/src/slave/containerizer/provisioners/appc/store.hpp
deleted file mode 100644
index c4ce4b9..0000000
--- a/src/slave/containerizer/provisioners/appc/store.hpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/**
- * 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 __MESOS_APPC_STORE_HPP__
-#define __MESOS_APPC_STORE_HPP__
-
-#include <string>
-#include <vector>
-
-#include <mesos/mesos.hpp>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-
-#include <stout/try.hpp>
-
-#include "slave/flags.hpp"
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-
-// Forward declaration.
-class StoreProcess;
-
-
-// An image store abstraction that "stores" images. It serves as a read-through
-// cache (cache misses are fetched remotely and transparently) for images.
-// TODO(xujyan): The store currently keeps cached images indefinitely and we
-// should introduce cache eviction policies.
-class Store
-{
-public:
-  static Try<process::Owned<Store>> create(const Flags& flags);
-
-  ~Store();
-
-  process::Future<Nothing> recover();
-
-  // Get the specified image (and all its recursive dependencies) as a list
-  // of rootfs layers in the topological order (dependencies go before
-  // dependents in the list). The images required to build this list are
-  // either retrieved from the local cache or fetched remotely.
-  // NOTE: The returned list should not have duplicates. e.g., in the
-  // following scenario the result should be [C, B, D, A] (B before D in this
-  // example is decided by the order in which A specifies its dependencies).
-  //
-  // A --> B --> C
-  // |           ^
-  // |---> D ----|
-  //
-  // The returned future fails if the requested image or any of its
-  // dependencies cannot be found or failed to be fetched.
-  // TODO(xujyan): Fetching remotely is not implemented for now and until
-  // then the future fails directly if the image is not in the local cache.
-  // TODO(xujyan): The store currently doesn't support images that have
-  // dependencies and we should add it later.
-  process::Future<std::vector<std::string>> get(const Image::Appc& image);
-
-private:
-  Store(process::Owned<StoreProcess> process);
-
-  Store(const Store&); // Not copyable.
-  Store& operator=(const Store&); // Not assignable.
-
-  process::Owned<StoreProcess> process;
-};
-
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_APPC_STORE_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/backend.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/backend.cpp b/src/slave/containerizer/provisioners/backend.cpp
deleted file mode 100644
index 6560ece..0000000
--- a/src/slave/containerizer/provisioners/backend.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 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 <glog/logging.h>
-
-#include <stout/os.hpp>
-
-#include "slave/containerizer/provisioners/backend.hpp"
-
-#include "slave/containerizer/provisioners/backends/bind.hpp"
-#include "slave/containerizer/provisioners/backends/copy.hpp"
-
-using namespace process;
-
-using std::string;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-hashmap<string, Owned<Backend>> Backend::create(const Flags& flags)
-{
-  hashmap<string, Try<Owned<Backend>>(*)(const Flags&)> creators;
-
-#ifdef __linux__
-  creators.put("bind", &BindBackend::create);
-#endif // __linux__
-  creators.put("copy", &CopyBackend::create);
-
-  hashmap<string, Owned<Backend>> backends;
-
-  foreachkey (const string& name, creators) {
-    Try<Owned<Backend>> backend = creators[name](flags);
-    if (backend.isError()) {
-      LOG(WARNING) << "Failed to create '" << name << "' backend: "
-                   << backend.error();
-      continue;
-    }
-    backends.put(name, backend.get());
-  }
-
-  return backends;
-}
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/backend.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/backend.hpp b/src/slave/containerizer/provisioners/backend.hpp
deleted file mode 100644
index a25b4ea..0000000
--- a/src/slave/containerizer/provisioners/backend.hpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * 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 __MESOS_PROVISIONER_BACKEND_HPP__
-#define __MESOS_PROVISIONER_BACKEND_HPP__
-
-#include <string>
-#include <vector>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-
-#include <stout/hashmap.hpp>
-#include <stout/try.hpp>
-
-#include "slave/flags.hpp"
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-// Provision a root filesystem for a container.
-class Backend
-{
-public:
-  virtual ~Backend() {}
-
-  // Return a map of all supported backends keyed by their names. Note
-  // that Backends that failed to be created due to incorrect flags are
-  // simply not added to the result.
-  static hashmap<std::string, process::Owned<Backend>> create(
-      const Flags& flags);
-
-  // Provision a root filesystem for a container into the specified 'rootfs'
-  // directory by applying the specified list of root filesystem layers in
-  // the list order, i.e., files in a layer can overwrite/shadow those from
-  // another layer earlier in the list.
-  virtual process::Future<Nothing> provision(
-      const std::vector<std::string>& layers,
-      const std::string& rootfs) = 0;
-
-  // Destroy the root filesystem provisioned at the specified 'rootfs'
-  // directory. Return false if there is no provisioned root filesystem
-  // to destroy for the given directory.
-  virtual process::Future<bool> destroy(const std::string& rootfs) = 0;
-};
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_PROVISIONER_BACKEND_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/backends/bind.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/backends/bind.cpp b/src/slave/containerizer/provisioners/backends/bind.cpp
deleted file mode 100644
index 71861a9..0000000
--- a/src/slave/containerizer/provisioners/backends/bind.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-/**
- * 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 <errno.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include <process/dispatch.hpp>
-#include <process/process.hpp>
-
-#include <stout/foreach.hpp>
-#include <stout/os.hpp>
-
-#include "linux/fs.hpp"
-
-#include "slave/containerizer/provisioners/backends/bind.hpp"
-
-using namespace process;
-
-using std::string;
-using std::vector;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-class BindBackendProcess : public Process<BindBackendProcess>
-{
-public:
-  Future<Nothing> provision(const vector<string>& layers, const string& rootfs);
-
-  Future<bool> destroy(const string& rootfs);
-};
-
-
-Try<Owned<Backend>> BindBackend::create(const 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("BindBackend requires root privileges");
-  }
-
-  return Owned<Backend>(new BindBackend(
-      Owned<BindBackendProcess>(new BindBackendProcess())));
-}
-
-
-BindBackend::~BindBackend()
-{
-  terminate(process.get());
-  wait(process.get());
-}
-
-
-BindBackend::BindBackend(Owned<BindBackendProcess> _process)
-  : process(_process)
-{
-  spawn(CHECK_NOTNULL(process.get()));
-}
-
-
-Future<Nothing> BindBackend::provision(
-    const vector<string>& layers,
-    const string& rootfs)
-{
-  return dispatch(
-      process.get(), &BindBackendProcess::provision, layers, rootfs);
-}
-
-
-Future<bool> BindBackend::destroy(const string& rootfs)
-{
-  return dispatch(process.get(), &BindBackendProcess::destroy, rootfs);
-}
-
-
-Future<Nothing> BindBackendProcess::provision(
-    const vector<string>& layers,
-    const string& rootfs)
-{
-  if (layers.size() > 1) {
-    return Failure(
-        "Multiple layers are not supported by the bind backend");
-  }
-
-  if (layers.size() == 0) {
-    return Failure("No filesystem layer provided");
-  }
-
-  Try<Nothing> mkdir = os::mkdir(rootfs);
-  if (mkdir.isError()) {
-    return Failure("Failed to create container rootfs at " + rootfs);
-  }
-
-  // TODO(xujyan): Use MS_REC? Does any provisioner use mounts within
-  // its image store in a single layer?
-  Try<Nothing> mount = fs::mount(
-      layers.front(),
-      rootfs,
-      None(),
-      MS_BIND,
-      NULL);
-
-  if (mount.isError()) {
-    return Failure(
-        "Failed to bind mount rootfs '" + layers.front() +
-        "' to '" + rootfs + "': " + mount.error());
-  }
-
-  // And remount it read-only.
-  mount = fs::mount(
-      None(), // Ignored.
-      rootfs,
-      None(),
-      MS_BIND | MS_RDONLY | MS_REMOUNT,
-      NULL);
-
-  if (mount.isError()) {
-    return Failure(
-        "Failed to remount rootfs '" + rootfs + "' read-only: " +
-        mount.error());
-  }
-
-  return Nothing();
-}
-
-
-Future<bool> BindBackendProcess::destroy(const string& rootfs)
-{
-  Try<fs::MountInfoTable> mountTable = fs::MountInfoTable::read();
-
-  if (mountTable.isError()) {
-    return Failure("Failed to read mount table: " + mountTable.error());
-  }
-
-  foreach (const fs::MountInfoTable::Entry& entry, mountTable.get().entries) {
-    // TODO(xujyan): If MS_REC was used in 'provision()' we would need
-    // to check `strings::startsWith(entry.target, rootfs)` here to
-    // unmount all nested mounts.
-    if (entry.target == rootfs) {
-      // NOTE: This would fail if the rootfs is still in use.
-      Try<Nothing> unmount = fs::unmount(entry.target);
-      if (unmount.isError()) {
-        return Failure(
-            "Failed to destroy bind-mounted rootfs '" + rootfs + "': " +
-            unmount.error());
-      }
-
-      // TODO(jieyu): If 'rmdir' here returns EBUSY, we still returns
-      // a success. This is currently possible because the parent
-      // mount of 'rootfs' might not be a shared mount. Thus,
-      // containers in different mount namespaces might hold extra
-      // references to this mount. It is OK to ignore the EBUSY error
-      // because the provisioner will later try to delete all the
-      // rootfses for the terminated containers.
-      if (::rmdir(rootfs.c_str()) != 0) {
-        string message =
-          "Failed to remove rootfs mount point '" + rootfs +
-          "': " + strerror(errno);
-
-        if (errno == EBUSY) {
-          LOG(ERROR) << message;
-        } else {
-          return Failure(message);
-        }
-      }
-
-      return true;
-    }
-  }
-
-  return false;
-}
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/backends/bind.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/backends/bind.hpp b/src/slave/containerizer/provisioners/backends/bind.hpp
deleted file mode 100644
index 61a8838..0000000
--- a/src/slave/containerizer/provisioners/backends/bind.hpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * 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 __MESOS_PROVISIONER_BIND_HPP__
-#define __MESOS_PROVISIONER_BIND_HPP__
-
-#include "slave/containerizer/provisioners/backend.hpp"
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-// Forward declaration.
-class BindBackendProcess;
-
-
-// This is a specialized backend that may be useful for deployments
-// using large (multi-GB) single-layer images *and* where more recent
-// kernel features such as overlayfs are not available (overlayfs-based
-// backend tracked by MESOS-2971). For small images (10's to 100's of MB)
-// the copy backend may be sufficient. NOTE:
-// 1) BindBackend supports only a single layer. Multi-layer images will
-//    fail to provision and the container will fail to launch!
-// 2) The filesystem is read-only because all containers using this
-//    image share the source. Select writable areas can be achieved by
-//    mounting read-write volumes to places like /tmp, /var/tmp,
-//    /home, etc. using the ContainerInfo. These can be relative to
-//    the executor work directory.
-//    N.B. Since the filesystem is read-only, '--sandbox_directory' must
-//    already exist within the filesystem because the filesystem isolator
-//    is unable to create it!
-// 3) It's fast because the bind mount requires (nearly) zero IO.
-class BindBackend : public Backend
-{
-public:
-  virtual ~BindBackend();
-
-  // BindBackend doesn't use any flag.
-  static Try<process::Owned<Backend>> create(const Flags&);
-
-  virtual process::Future<Nothing> provision(
-      const std::vector<std::string>& layers,
-      const std::string& rootfs);
-
-  virtual process::Future<bool> destroy(const std::string& rootfs);
-
-private:
-  explicit BindBackend(process::Owned<BindBackendProcess> process);
-
-  BindBackend(const BindBackend&); // Not copyable.
-  BindBackend& operator=(const BindBackend&); // Not assignable.
-
-  process::Owned<BindBackendProcess> process;
-};
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_PROVISIONER_BIND_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/backends/copy.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/backends/copy.cpp b/src/slave/containerizer/provisioners/backends/copy.cpp
deleted file mode 100644
index b569465..0000000
--- a/src/slave/containerizer/provisioners/backends/copy.cpp
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * 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 <list>
-
-#include <process/collect.hpp>
-#include <process/defer.hpp>
-#include <process/dispatch.hpp>
-#include <process/io.hpp>
-#include <process/process.hpp>
-#include <process/subprocess.hpp>
-
-
-#include <stout/foreach.hpp>
-#include <stout/os.hpp>
-
-#include "common/status_utils.hpp"
-
-#include "slave/containerizer/provisioners/backends/copy.hpp"
-
-
-using namespace process;
-
-using std::string;
-using std::list;
-using std::vector;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-class CopyBackendProcess : public Process<CopyBackendProcess>
-{
-public:
-  Future<Nothing> provision(const vector<string>& layers, const string& rootfs);
-
-  Future<bool> destroy(const string& rootfs);
-
-private:
-  Future<Nothing> _provision(string layer, const string& rootfs);
-};
-
-
-Try<Owned<Backend>> CopyBackend::create(const Flags&)
-{
-  return Owned<Backend>(new CopyBackend(
-      Owned<CopyBackendProcess>(new CopyBackendProcess())));
-}
-
-
-CopyBackend::~CopyBackend()
-{
-  terminate(process.get());
-  wait(process.get());
-}
-
-
-CopyBackend::CopyBackend(Owned<CopyBackendProcess> _process)
-  : process(_process)
-{
-  spawn(CHECK_NOTNULL(process.get()));
-}
-
-
-Future<Nothing> CopyBackend::provision(
-    const vector<string>& layers,
-    const string& rootfs)
-{
-  return dispatch(
-      process.get(), &CopyBackendProcess::provision, layers, rootfs);
-}
-
-
-Future<bool> CopyBackend::destroy(const string& rootfs)
-{
-  return dispatch(process.get(), &CopyBackendProcess::destroy, rootfs);
-}
-
-
-Future<Nothing> CopyBackendProcess::provision(
-    const vector<string>& layers,
-    const string& rootfs)
-{
-  if (layers.size() == 0) {
-    return Failure("No filesystem layers provided");
-  }
-
-  if (os::exists(rootfs)) {
-    return Failure("Rootfs is already provisioned");
-  }
-
-  Try<Nothing> mkdir = os::mkdir(rootfs);
-  if (mkdir.isError()) {
-    return Failure("Failed to create rootfs directory: " + mkdir.error());
-  }
-
-  list<Future<Nothing>> futures{Nothing()};
-
-  foreach (const string layer, layers) {
-    futures.push_back(
-        futures.back().then(
-            defer(self(), &Self::_provision, layer, rootfs)));
-  }
-
-  return collect(futures)
-    .then([]() -> Future<Nothing> { return Nothing(); });
-}
-
-
-Future<Nothing> CopyBackendProcess::_provision(
-  string layer,
-  const string& rootfs)
-{
-  VLOG(1) << "Copying layer path '" << layer << "' to rootfs '" << rootfs
-          << "'";
-
-#ifdef __APPLE__
-  if (!strings::endsWith(layer, "/")) {
-    layer += "/";
-  }
-
-  // OSX cp doesn't support -T flag, but supports source trailing
-  // slash so we only copy the content but not the folder.
-  vector<string> args{"cp", "-a", layer, rootfs};
-#else
-  vector<string> args{"cp", "-aT", layer, rootfs};
-#endif // __APPLE__
-
-  Try<Subprocess> s = subprocess(
-      "cp",
-      args,
-      Subprocess::PATH("/dev/null"),
-      Subprocess::PATH("/dev/null"),
-      Subprocess::PIPE());
-
-  if (s.isError()) {
-    return Failure("Failed to create 'cp' subprocess: " + s.error());
-  }
-
-  Subprocess cp = s.get();
-
-  return cp.status()
-    .then([cp](const Option<int>& status) -> Future<Nothing> {
-      if (status.isNone()) {
-        return Failure("Failed to reap subprocess to copy image");
-      } else if (status.get() != 0) {
-        return io::read(cp.err().get())
-          .then([](const string& err) -> Future<Nothing> {
-            return Failure("Failed to copy layer: " + err);
-          });
-      }
-
-      return Nothing();
-    });
-}
-
-
-Future<bool> CopyBackendProcess::destroy(const string& rootfs)
-{
-  vector<string> argv{"rm", "-rf", rootfs};
-
-  Try<Subprocess> s = subprocess(
-      "rm",
-      argv,
-      Subprocess::PATH("/dev/null"),
-      Subprocess::FD(STDOUT_FILENO),
-      Subprocess::FD(STDERR_FILENO));
-
-  if (s.isError()) {
-    return Failure("Failed to create 'rm' subprocess: " + s.error());
-  }
-
-  return s.get().status()
-    .then([](const Option<int>& status) -> Future<bool> {
-      if (status.isNone()) {
-        return Failure("Failed to reap subprocess to destroy rootfs");
-      } else if (status.get() != 0) {
-        return Failure("Failed to destroy rootfs, exit status: " +
-                       WSTRINGIFY(status.get()));
-      }
-
-      return true;
-    });
-}
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/backends/copy.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/backends/copy.hpp b/src/slave/containerizer/provisioners/backends/copy.hpp
deleted file mode 100644
index 2abca37..0000000
--- a/src/slave/containerizer/provisioners/backends/copy.hpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * 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 __MESOS_PROVISIONER_COPY_HPP__
-#define __MESOS_PROVISIONER_COPY_HPP__
-
-#include "slave/containerizer/provisioners/backend.hpp"
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-// Forward declaration.
-class CopyBackendProcess;
-
-
-class CopyBackend : public Backend
-{
-public:
-  virtual ~CopyBackend();
-
-  // CopyBackend doesn't use any flag.
-  static Try<process::Owned<Backend>> create(const Flags&);
-
-  // Provisions a rootfs given the layers' paths and target rootfs
-  // path.
-  virtual process::Future<Nothing> provision(
-      const std::vector<std::string>& layers,
-      const std::string& rootfs);
-
-  virtual process::Future<bool> destroy(const std::string& rootfs);
-
-private:
-  explicit CopyBackend(process::Owned<CopyBackendProcess> process);
-
-  CopyBackend(const CopyBackend&); // Not copyable.
-  CopyBackend& operator=(const CopyBackend&); // Not assignable.
-
-  process::Owned<CopyBackendProcess> process;
-};
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_PROVISIONER_COPY_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/docker/registry_client.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/docker/registry_client.cpp b/src/slave/containerizer/provisioners/docker/registry_client.cpp
deleted file mode 100644
index b262ef0..0000000
--- a/src/slave/containerizer/provisioners/docker/registry_client.cpp
+++ /dev/null
@@ -1,575 +0,0 @@
-/**
- * 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 <vector>
-
-#include <process/defer.hpp>
-#include <process/dispatch.hpp>
-#include <process/io.hpp>
-
-#include "slave/containerizer/provisioners/docker/registry_client.hpp"
-#include "slave/containerizer/provisioners/docker/token_manager.hpp"
-
-using std::string;
-using std::vector;
-
-using process::Failure;
-using process::Future;
-using process::Owned;
-using process::Process;
-
-using process::http::Request;
-using process::http::Response;
-using process::http::URL;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace docker {
-namespace registry {
-
-using FileSystemLayerInfo = RegistryClient::FileSystemLayerInfo;
-
-using ManifestResponse = RegistryClient::ManifestResponse;
-
-const Duration RegistryClient::DEFAULT_MANIFEST_TIMEOUT_SECS = Seconds(10);
-
-const size_t RegistryClient::DEFAULT_MANIFEST_MAXSIZE_BYTES = 4096;
-
-static const uint16_t DEFAULT_SSL_PORT = 443;
-
-class RegistryClientProcess : public Process<RegistryClientProcess>
-{
-public:
-  static Try<Owned<RegistryClientProcess>> create(
-      const URL& authServer,
-      const URL& registry,
-      const Option<RegistryClient::Credentials>& creds);
-
-  Future<RegistryClient::ManifestResponse> getManifest(
-      const string& path,
-      const Option<string>& tag,
-      const Duration& timeout);
-
-  Future<size_t> getBlob(
-      const string& path,
-      const Option<string>& digest,
-      const Path& filePath,
-      const Duration& timeout,
-      size_t maxSize);
-
-private:
-  RegistryClientProcess(
-    const Owned<TokenManager>& tokenMgr,
-    const URL& registryServer,
-    const Option<RegistryClient::Credentials>& creds);
-
-  Future<Response> doHttpGet(
-      const URL& url,
-      const Option<hashmap<string, string>>& headers,
-      const Duration& timeout,
-      bool resend,
-      const Option<string>& lastResponse) const;
-
-  Try<hashmap<string, string>> getAuthenticationAttributes(
-      const Response& httpResponse) const;
-
-  Owned<TokenManager> tokenManager_;
-  const URL registryServer_;
-  const Option<RegistryClient::Credentials> credentials_;
-
-  RegistryClientProcess(const RegistryClientProcess&) = delete;
-  RegistryClientProcess& operator = (const RegistryClientProcess&) = delete;
-};
-
-
-Try<Owned<RegistryClient>> RegistryClient::create(
-    const URL& authServer,
-    const URL& registryServer,
-    const Option<Credentials>& creds)
-{
-  Try<Owned<RegistryClientProcess>> process =
-    RegistryClientProcess::create(authServer, registryServer, creds);
-
-  if (process.isError()) {
-    return Error(process.error());
-  }
-
-  return Owned<RegistryClient>(
-      new RegistryClient(authServer, registryServer, creds, process.get()));
-}
-
-
-RegistryClient::RegistryClient(
-    const URL& authServer,
-    const URL& registryServer,
-    const Option<Credentials>& creds,
-    const Owned<RegistryClientProcess>& process)
-  : authServer_(authServer),
-    registryServer_(registryServer),
-    credentials_(creds),
-    process_(process)
-{
-  spawn(CHECK_NOTNULL(process_.get()));
-}
-
-
-RegistryClient::~RegistryClient()
-{
-  terminate(process_.get());
-  process::wait(process_.get());
-}
-
-
-Future<ManifestResponse> RegistryClient::getManifest(
-    const string& _path,
-    const Option<string>& _tag,
-    const Option<Duration>& _timeout)
-{
-  Duration timeout = _timeout.getOrElse(DEFAULT_MANIFEST_TIMEOUT_SECS);
-
-  return dispatch(
-      process_.get(),
-      &RegistryClientProcess::getManifest,
-      _path,
-      _tag,
-      timeout);
-}
-
-
-Future<size_t> RegistryClient::getBlob(
-    const string& _path,
-    const Option<string>& _digest,
-    const Path& _filePath,
-    const Option<Duration>& _timeout,
-    const Option<size_t>& _maxSize)
-{
-  Duration timeout = _timeout.getOrElse(DEFAULT_MANIFEST_TIMEOUT_SECS);
-  size_t maxSize = _maxSize.getOrElse(DEFAULT_MANIFEST_MAXSIZE_BYTES);
-
-  return dispatch(
-        process_.get(),
-        &RegistryClientProcess::getBlob,
-        _path,
-        _digest,
-        _filePath,
-        timeout,
-        maxSize);
-}
-
-
-Try<Owned<RegistryClientProcess>> RegistryClientProcess::create(
-    const URL& authServer,
-    const URL& registryServer,
-    const Option<RegistryClient::Credentials>& creds)
-{
-  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(authServer);
-  if (tokenMgr.isError()) {
-    return Error("Failed to create token manager: " + tokenMgr.error());
-  }
-
-  return Owned<RegistryClientProcess>(
-      new RegistryClientProcess(tokenMgr.get(), registryServer, creds));
-}
-
-
-RegistryClientProcess::RegistryClientProcess(
-    const Owned<TokenManager>& tokenMgr,
-    const URL& registryServer,
-    const Option<RegistryClient::Credentials>& creds)
-  : tokenManager_(tokenMgr),
-    registryServer_(registryServer),
-    credentials_(creds) {}
-
-
-Try<hashmap<string, string>>
-RegistryClientProcess::getAuthenticationAttributes(
-    const Response& httpResponse) const
-{
-  if (httpResponse.headers.find("WWW-Authenticate") ==
-      httpResponse.headers.end()) {
-    return Error("Failed to find WWW-Authenticate header value");
-  }
-
-  const string& authString = httpResponse.headers.at("WWW-Authenticate");
-
-  const vector<string> authStringTokens = strings::tokenize(authString, " ");
-  if ((authStringTokens.size() != 2) || (authStringTokens[0] != "Bearer")) {
-    // TODO(jojy): Look at various possibilities of auth response. We currently
-    // assume that the string will have realm information.
-    return Error("Invalid authentication header value: " + authString);
-  }
-
-  const vector<string> authParams = strings::tokenize(authStringTokens[1], ",");
-
-  hashmap<string, string> authAttributes;
-  auto addAttribute = [&authAttributes](
-      const string& param) -> Try<Nothing> {
-    const vector<string> paramTokens =
-      strings::tokenize(param, "=\"");
-
-    if (paramTokens.size() != 2) {
-      return Error(
-          "Failed to get authentication attribute from response parameter " +
-          param);
-    }
-
-    authAttributes.insert({paramTokens[0], paramTokens[1]});
-
-    return Nothing();
-  };
-
-  foreach (const string& param, authParams) {
-    Try<Nothing> addRes = addAttribute(param);
-    if (addRes.isError()) {
-      return Error(addRes.error());
-    }
-  }
-
-  return authAttributes;
-}
-
-
-Future<Response>
-RegistryClientProcess::doHttpGet(
-    const URL& url,
-    const Option<hashmap<string, string>>& headers,
-    const Duration& timeout,
-    bool resend,
-    const Option<string>& lastResponseStatus) const
-{
-  return process::http::get(url, headers)
-    .after(timeout, [](
-        const Future<Response>& httpResponseFuture) -> Future<Response> {
-      return Failure("Response timeout");
-    })
-    .then(defer(self(), [=](
-        const Response& httpResponse) -> Future<Response> {
-      VLOG(1) << "Response status: " + httpResponse.status;
-
-      // Set the future if we get a OK response.
-      if (httpResponse.status == "200 OK") {
-        return httpResponse;
-      } else if (httpResponse.status == "400 Bad Request") {
-        Try<JSON::Object> errorResponse =
-          JSON::parse<JSON::Object>(httpResponse.body);
-
-        if (errorResponse.isError()) {
-          return Failure("Failed to parse bad request response JSON: " +
-                         errorResponse.error());
-        }
-
-        std::ostringstream out;
-        bool first = true;
-        Result<JSON::Array> errorObjects =
-          errorResponse.get().find<JSON::Array>("errors");
-
-        if (errorObjects.isError()) {
-          return Failure("Failed to find 'errors' in bad request response: " +
-                         errorObjects.error());
-        } else if (errorObjects.isNone()) {
-          return Failure("Errors not found in bad request response");
-        }
-
-        foreach (const JSON::Value& error, errorObjects.get().values) {
-          Result<JSON::String> message =
-            error.as<JSON::Object>().find<JSON::String>("message");
-          if (message.isError()) {
-            return Failure("Failed to parse bad request error message: " +
-                           message.error());
-          } else if (message.isNone()) {
-            continue;
-          }
-
-          if (first) {
-            out << message.get().value;
-            first = false;
-          } else {
-            out << ", " << message.get().value;
-          }
-        }
-        return Failure("Received Bad request, errors: [" + out.str() + "]");
-      }
-
-      // Prevent infinite recursion.
-      if (lastResponseStatus.isSome() &&
-          (lastResponseStatus.get() == httpResponse.status)) {
-        return Failure("Invalid response: " + httpResponse.status);
-      }
-
-      // If resend is not set, we dont try again and stop here.
-      if (!resend) {
-        return Failure("Bad response: " + httpResponse.status);
-      }
-
-      // Handle 401 Unauthorized.
-      if (httpResponse.status == "401 Unauthorized") {
-        Try<hashmap<string, string>> authAttributes =
-          getAuthenticationAttributes(httpResponse);
-
-        if (authAttributes.isError()) {
-          return Failure(
-              "Failed to get authentication attributes: " +
-              authAttributes.error());
-        }
-
-        // TODO(jojy): Currently only handling TLS/cert authentication.
-        Future<Token> tokenResponse = tokenManager_->getToken(
-          authAttributes.get().at("service"),
-          authAttributes.get().at("scope"),
-          None());
-
-        return tokenResponse
-          .after(timeout, [=](
-              Future<Token> tokenResponse) -> Future<Token> {
-            tokenResponse.discard();
-            return Failure("Token response timeout");
-          })
-          .then(defer(self(), [=](
-              const Future<Token>& tokenResponse) {
-            // Send request with acquired token.
-            hashmap<string, string> authHeaders = {
-              {"Authorization", "Bearer " + tokenResponse.get().raw}
-            };
-
-            return doHttpGet(
-                url,
-                authHeaders,
-                timeout,
-                true,
-                httpResponse.status);
-        }));
-      } else if (httpResponse.status == "307 Temporary Redirect") {
-        // Handle redirect.
-
-        // TODO(jojy): Add redirect functionality in http::get.
-
-        auto toURL = [](
-            const string& urlString) -> Try<URL> {
-          // TODO(jojy): Need to add functionality to URL class that parses a
-          // string to its URL components. For now, assuming:
-          //  - scheme is https
-          //  - path always ends with /
-
-          static const string schemePrefix = "https://";
-
-          if (!strings::contains(urlString, schemePrefix)) {
-            return Error(
-                "Failed to find expected token '" + schemePrefix +
-                "' in redirect url");
-          }
-
-          const string schemeSuffix = urlString.substr(schemePrefix.length());
-
-          const vector<string> components =
-            strings::tokenize(schemeSuffix, "/");
-
-          const string path = schemeSuffix.substr(components[0].length());
-
-          const vector<string> addrComponents =
-            strings::tokenize(components[0], ":");
-
-          uint16_t port = DEFAULT_SSL_PORT;
-          string domain = components[0];
-
-          // Parse the port.
-          if (addrComponents.size() == 2) {
-            domain = addrComponents[0];
-
-            Try<uint16_t> tryPort = numify<uint16_t>(addrComponents[1]);
-            if (tryPort.isError()) {
-              return Error(
-                  "Failed to parse location: " + urlString + " for port.");
-            }
-
-            port = tryPort.get();
-          }
-
-          return URL("https", domain, port, path);
-        };
-
-        if (httpResponse.headers.find("Location") ==
-            httpResponse.headers.end()) {
-          return Failure(
-              "Invalid redirect response: 'Location' not found in headers.");
-        }
-
-        const string& location = httpResponse.headers.at("Location");
-        Try<URL> tryUrl = toURL(location);
-        if (tryUrl.isError()) {
-          return Failure(
-              "Failed to parse '" + location + "': " + tryUrl.error());
-        }
-
-        return doHttpGet(
-            tryUrl.get(),
-            headers,
-            timeout,
-            false,
-            httpResponse.status);
-      } else {
-        return Failure("Invalid response: " + httpResponse.status);
-      }
-    }));
-}
-
-
-Future<ManifestResponse> RegistryClientProcess::getManifest(
-    const string& path,
-    const Option<string>& tag,
-    const Duration& timeout)
-{
-  //TODO(jojy): These validations belong in the URL class.
-  if (strings::contains(path, " ")) {
-    return Failure("Invalid repository path: " + path);
-  }
-
-  string repoTag = tag.getOrElse("latest");
-  if (strings::contains(repoTag, " ")) {
-    return Failure("Invalid repository tag: " + repoTag);
-  }
-
-  URL manifestURL(registryServer_);
-  manifestURL.path =
-    "v2/" + path + "/manifests/" + repoTag;
-
-  auto getManifestResponse = [](
-      const Response& httpResponse) -> Try<ManifestResponse> {
-    if (!httpResponse.headers.contains("Docker-Content-Digest")) {
-      return Error("Docker-Content-Digest header missing in response");
-    }
-
-    Try<JSON::Object> responseJSON =
-      JSON::parse<JSON::Object>(httpResponse.body);
-
-    if (responseJSON.isError()) {
-      return Error(responseJSON.error());
-    }
-
-    Result<JSON::String> name = responseJSON.get().find<JSON::String>("name");
-    if (name.isNone()) {
-      return Error("Failed to find \"name\" in manifest response");
-    }
-
-    Result<JSON::Array> fsLayers =
-      responseJSON.get().find<JSON::Array>("fsLayers");
-
-    if (fsLayers.isNone()) {
-      return Error("Failed to find \"fsLayers\" in manifest response");
-    }
-
-    vector<FileSystemLayerInfo> fsLayerInfoList;
-    foreach (const JSON::Value& layer, fsLayers.get().values) {
-      const JSON::Object& layerInfoJSON = layer.as<JSON::Object>();
-      Result<JSON::String> blobSumInfo =
-        layerInfoJSON.find<JSON::String>("blobSum");
-
-      if (blobSumInfo.isNone()) {
-        return Error("Failed to find \"blobSum\" in manifest response");
-      }
-
-      fsLayerInfoList.emplace_back(
-          FileSystemLayerInfo{blobSumInfo.get().value});
-    }
-
-    return ManifestResponse {
-      name.get().value,
-      httpResponse.headers.at("Docker-Content-Digest"),
-      fsLayerInfoList,
-    };
-  };
-
-  return doHttpGet(manifestURL, None(), timeout, true, None())
-    .then([getManifestResponse] (
-        const Response& response) -> Future<ManifestResponse> {
-      Try<ManifestResponse> manifestResponse = getManifestResponse(response);
-
-      if (manifestResponse.isError()) {
-        return Failure(
-            "Failed to parse manifest response: " + manifestResponse.error());
-      }
-
-      return manifestResponse.get();
-    });
-}
-
-
-Future<size_t> RegistryClientProcess::getBlob(
-    const string& path,
-    const Option<string>& digest,
-    const Path& filePath,
-    const Duration& timeout,
-    size_t maxSize)
-{
-  auto prepare = ([&filePath]() -> Try<Nothing> {
-      const string dirName = filePath.dirname();
-
-      //TODO(jojy): Return more state, for example - if the directory is new.
-      Try<Nothing> dirResult = os::mkdir(dirName, true);
-      if (dirResult.isError()) {
-        return Error(
-            "Failed to create directory to download blob: " +
-            dirResult.error());
-      }
-
-      return dirResult;
-  })();
-
-  // TODO(jojy): This currently leaves a residue in failure cases. Would be
-  // ideal if we can completely rollback.
-  if (prepare.isError()) {
-     return Failure(prepare.error());
-  }
-
-  if (strings::contains(path, " ")) {
-    return Failure("Invalid repository path: " + path);
-  }
-
-  URL blobURL(registryServer_);
-  blobURL.path =
-    "v2/" + path + "/blobs/" + digest.getOrElse("");
-
-  auto saveBlob = [filePath](
-      const Response& httpResponse) -> Future<size_t> {
-    // TODO(jojy): Add verification step.
-    // TODO(jojy): Add check for max size.
-    size_t size = httpResponse.body.length();
-    Try<int> fd = os::open(
-        filePath.value,
-        O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
-        S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
-
-    if (fd.isError()) {
-      return Failure("Failed to open file '" + filePath.value + "': " +
-                     fd.error());
-    }
-
-    return process::io::write(fd.get(), httpResponse.body)
-      .then([size](const Future<Nothing>&) { return size; })
-      .onAny([fd]() { os::close(fd.get()); } );
-  };
-
-  return doHttpGet(blobURL, None(), timeout, true, None())
-    .then([saveBlob](const Response& response) { return saveBlob(response); });
-}
-
-} // namespace registry {
-} // namespace docker {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/docker/registry_client.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/docker/registry_client.hpp b/src/slave/containerizer/provisioners/docker/registry_client.hpp
deleted file mode 100644
index 3ec3741..0000000
--- a/src/slave/containerizer/provisioners/docker/registry_client.hpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * 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 __PROVISIONERS_DOCKER_REGISTRY_CLIENT_HPP__
-#define __PROVISIONERS_DOCKER_REGISTRY_CLIENT_HPP__
-
-#include <string>
-#include <vector>
-
-#include <stout/duration.hpp>
-#include <stout/hashmap.hpp>
-#include <stout/json.hpp>
-#include <stout/path.hpp>
-
-#include <process/future.hpp>
-#include <process/http.hpp>
-#include <process/process.hpp>
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace docker {
-namespace registry {
-
-// Forward declarations.
-class RegistryClientProcess;
-
-
-class RegistryClient
-{
-public:
-  /**
-   * Encapsulates information about a file system layer.
-   */
-  struct FileSystemLayerInfo {
-    //TODO(jojy): This string includes the checksum type also now. Need to
-    //separate this into checksum method and checksum.
-    std::string checksumInfo;
-  };
-
-  /**
-   * Encapsulates response of "GET Manifest" request.
-   *
-   * Reference: https://docs.docker.com/registry/spec/api
-   */
-  struct ManifestResponse {
-    const std::string name;
-    const std::string digest;
-    const std::vector<FileSystemLayerInfo> fsLayerInfoList;
-  };
-
-  /**
-   * Encapsulates auth credentials for the client sessions.
-   * TODO(jojy): Secure heap to protect the credentials.
-   */
-  struct Credentials {
-    /**
-     * UserId for basic authentication.
-     */
-    const Option<std::string> userId;
-    /**
-     * Password for basic authentication.
-     */
-    const Option<std::string> password;
-    /**
-     * Account for fetching data from registry.
-     */
-    const Option<std::string> account;
-  };
-
-  /**
-   * Factory method for creating RegistryClient objects.
-   *
-   * @param authServer URL of authorization server.
-   * @param registryServer URL of docker registry server.
-   * @param credentials credentials for client session (optional).
-   * @return RegistryClient on Success.
-   *         Error on failure.
-   */
-  static Try<process::Owned<RegistryClient>> create(
-      const process::http::URL& authServer,
-      const process::http::URL& registryServer,
-      const Option<Credentials>& credentials);
-
-  /**
-   * Fetches manifest for a repository from the client's remote registry server.
-   *
-   * @param path path of the repository on the registry.
-   * @param tag unique tag that identifies the repository. Will default to
-   *    latest.
-   * @param timeout Maximum time ater which the request will timeout and return
-   *    a failure. Will default to RESPONSE_TIMEOUT.
-   * @return JSON object on success.
-   *         Failure on process failure.
-   */
-  process::Future<ManifestResponse> getManifest(
-      const std::string& path,
-      const Option<std::string>& tag,
-      const Option<Duration>& timeout);
-
-  /**
-   * Fetches blob for a repository from the client's remote registry server.
-   *
-   * @param path path of the repository on the registry.
-   * @param digest digest of the blob (from manifest).
-   * @param filePath file path to store the fetched blob.
-   * @param timeout Maximum time ater which the request will timeout and return
-   *    a failure. Will default to RESPONSE_TIMEOUT.
-   * @param maxSize Maximum size of the response thats acceptable. Will default
-   *    to MAX_RESPONSE_SIZE.
-   * @return size of downloaded blob on success.
-   *         Failure in case of any errors.
-   */
-  process::Future<size_t> getBlob(
-      const std::string& path,
-      const Option<std::string>& digest,
-      const Path& filePath,
-      const Option<Duration>& timeout,
-      const Option<size_t>& maxSize);
-
-  ~RegistryClient();
-
-private:
-  RegistryClient(
-    const process::http::URL& authServer,
-    const process::http::URL& registryServer,
-    const Option<Credentials>& credentials,
-    const process::Owned<RegistryClientProcess>& process);
-
-  static const Duration DEFAULT_MANIFEST_TIMEOUT_SECS;
-  static const size_t DEFAULT_MANIFEST_MAXSIZE_BYTES;
-
-  const process::http::URL authServer_;
-  const process::http::URL registryServer_;
-  const Option<Credentials> credentials_;
-  process::Owned<RegistryClientProcess> process_;
-
-  RegistryClient(const RegistryClient&) = delete;
-  RegistryClient& operator=(const RegistryClient&) = delete;
-};
-
-} // namespace registry {
-} // namespace docker {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __PROVISIONERS_DOCKER_REGISTRY_CLIENT_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/docker/token_manager.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/docker/token_manager.cpp b/src/slave/containerizer/provisioners/docker/token_manager.cpp
deleted file mode 100644
index aec915f..0000000
--- a/src/slave/containerizer/provisioners/docker/token_manager.cpp
+++ /dev/null
@@ -1,361 +0,0 @@
-/**
- * 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 <process/defer.hpp>
-#include <process/dispatch.hpp>
-
-#include "slave/containerizer/provisioners/docker/token_manager.hpp"
-
-using std::hash;
-using std::string;
-using std::vector;
-
-using process::Clock;
-using process::Failure;
-using process::Future;
-using process::Owned;
-using process::Process;
-using process::Time;
-
-using process::http::Request;
-using process::http::Response;
-using process::http::URL;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace docker {
-namespace registry {
-
-class TokenManagerProcess : public Process<TokenManagerProcess>
-{
-public:
-  static Try<Owned<TokenManagerProcess>> create(const URL& realm);
-
-  Future<Token> getToken(
-      const string& service,
-      const string& scope,
-      const Option<string>& account);
-
-private:
-  static const string TOKEN_PATH_PREFIX;
-  static const Duration RESPONSE_TIMEOUT;
-
-  TokenManagerProcess(const URL& realm)
-    : realm_(realm) {}
-
-  Try<Token> getTokenFromResponse(const Response& response) const;
-
-  /**
-   * Key for the token cache.
-   */
-  struct TokenCacheKey
-  {
-    string service;
-    string scope;
-  };
-
-  struct TokenCacheKeyHash
-  {
-    size_t operator()(const TokenCacheKey& key) const
-    {
-      hash<string> hashFn;
-
-      return (hashFn(key.service) ^
-          (hashFn(key.scope) << 1));
-    }
-  };
-
-  struct TokenCacheKeyEqual
-  {
-    bool operator()(
-        const TokenCacheKey& left,
-        const TokenCacheKey& right) const
-    {
-      return ((left.service == right.service) &&
-          (left.scope == right.scope));
-    }
-  };
-
-  typedef hashmap<
-    const TokenCacheKey,
-    Token,
-    TokenCacheKeyHash,
-    TokenCacheKeyEqual> TokenCacheType;
-
-  const URL realm_;
-  TokenCacheType tokenCache_;
-
-  TokenManagerProcess(const TokenManagerProcess&) = delete;
-  TokenManagerProcess& operator=(const TokenManagerProcess&) = delete;
-};
-
-const Duration TokenManagerProcess::RESPONSE_TIMEOUT = Seconds(10);
-const string TokenManagerProcess::TOKEN_PATH_PREFIX = "/v2/token/";
-
-
-Token::Token(
-    const string& _raw,
-    const JSON::Object& _header,
-    const JSON::Object& _claims,
-    const Option<Time>& _expiration,
-    const Option<Time>& _notBefore)
-  : raw(_raw),
-    header(_header),
-    claims(_claims),
-    expiration(_expiration),
-    notBefore(_notBefore) {}
-
-
-Try<Token> Token::create(const string& raw)
-{
-  auto decode = [](
-      const string& segment) -> Try<JSON::Object> {
-    const auto padding = segment.length() % 4;
-    string paddedSegment(segment);
-
-    if (padding) {
-      paddedSegment.append(padding, '=');
-    }
-
-    Try<string> decoded = base64::decode(paddedSegment);
-    if (decoded.isError()) {
-      return Error(decoded.error());
-    }
-
-    return JSON::parse<JSON::Object>(decoded.get());
-  };
-
-  const vector<string> tokens = strings::tokenize(raw, ".");
-
-  if (tokens.size() != 3) {
-    return Error("Invalid raw token string");
-  }
-
-  Try<JSON::Object> header = decode(tokens[0]);
-  if (header.isError()) {
-    return Error("Failed to decode 'header' segment: " + header.error());
-  }
-
-  Try<JSON::Object> claims = decode(tokens[1]);
-  if (claims.isError()) {
-    return Error("Failed to decode 'claims' segment: " + claims.error());
-  }
-
-  Result<Time> expirationTime = getTimeValue(claims.get(), "exp");
-  if (expirationTime.isError()) {
-    return Error("Failed to decode expiration time: " + expirationTime.error());
-  }
-
-  Option<Time> expiration;
-  if (expirationTime.isSome()) {
-    expiration = expirationTime.get();
-  }
-
-  Result<Time> notBeforeTime = getTimeValue(claims.get(), "nbf");
-  if (notBeforeTime.isError()) {
-    return Error("Failed to decode not-before time: " + notBeforeTime.error());
-  }
-
-  Option<Time> notBefore;
-  if (notBeforeTime.isSome()) {
-    notBefore = notBeforeTime.get();
-  }
-
-  Token token(raw, header.get(), claims.get(), expiration, notBefore);
-
-  if (token.isExpired()) {
-    return Error("Token has expired");
-  }
-
-  // TODO(jojy): Add signature validation.
-  return token;
-}
-
-
-Result<Time> Token::getTimeValue(const JSON::Object& object, const string& key)
-{
-  Result<JSON::Number> jsonValue = object.find<JSON::Number>(key);
-
-  Option<Time> timeValue;
-
-  // If expiration is provided, we will process it for future validations.
-  if (jsonValue.isSome()) {
-    Try<Time> time = Time::create(jsonValue.get().value);
-    if (time.isError()) {
-      return Error("Failed to decode time: " + time.error());
-    }
-
-    timeValue = time.get();
-  }
-
-  return timeValue;
-}
-
-
-bool Token::isExpired() const
-{
-  if (expiration.isSome()) {
-    return (Clock::now() >= expiration.get());
-  }
-
-  return false;
-}
-
-
-bool Token::isValid() const
-{
-  if (!isExpired()) {
-    if (notBefore.isSome()) {
-      return (Clock::now() >= notBefore.get());
-    }
-
-    return true;
-  }
-
-  // TODO(jojy): Add signature validation.
-  return false;
-}
-
-
-Try<Owned<TokenManager>> TokenManager::create(
-    const URL& realm)
-{
-  Try<Owned<TokenManagerProcess>> process = TokenManagerProcess::create(realm);
-  if (process.isError()) {
-    return Error(process.error());
-  }
-
-  return Owned<TokenManager>(new TokenManager(process.get()));
-}
-
-
-TokenManager::TokenManager(Owned<TokenManagerProcess>& process)
-  : process_(process)
-{
-  spawn(CHECK_NOTNULL(process_.get()));
-}
-
-
-TokenManager::~TokenManager()
-{
-  terminate(process_.get());
-  process::wait(process_.get());
-}
-
-
-Future<Token> TokenManager::getToken(
-    const string& service,
-    const string& scope,
-    const Option<string>& account)
-{
-  return dispatch(
-      process_.get(),
-      &TokenManagerProcess::getToken,
-      service,
-      scope,
-      account);
-}
-
-
-Try<Owned<TokenManagerProcess>> TokenManagerProcess::create(const URL& realm)
-{
-  return Owned<TokenManagerProcess>(new TokenManagerProcess(realm));
-}
-
-
-Try<Token> TokenManagerProcess::getTokenFromResponse(
-    const Response& response) const
-{
-  Try<JSON::Object> tokenJSON = JSON::parse<JSON::Object>(response.body);
-  if (tokenJSON.isError()) {
-    return Error(tokenJSON.error());
-  }
-
-  Result<JSON::String> tokenString =
-    tokenJSON.get().find<JSON::String>("token");
-
-  if (tokenString.isError()) {
-    return Error(tokenString.error());
-  }
-
-  Try<Token> result = Token::create(tokenString.get().value);
-  if (result.isError()) {
-    return Error(result.error());
-  }
-
-  return result.get();;
-}
-
-
-Future<Token> TokenManagerProcess::getToken(
-    const string& service,
-    const string& scope,
-    const Option<string>& account)
-{
-  const TokenCacheKey tokenKey = {service, scope};
-
-  if (tokenCache_.contains(tokenKey)) {
-    Token token = tokenCache_.at(tokenKey);
-
-    if (token.isValid()) {
-      return token;
-    } else {
-      LOG(WARNING) << "Cached token was invalid. Will fetch once again";
-    }
-  }
-
-  URL tokenUrl = realm_;
-  tokenUrl.path = TOKEN_PATH_PREFIX;
-
-  tokenUrl.query = {
-    {"service", service},
-    {"scope", scope},
-  };
-
-  if (account.isSome()) {
-    tokenUrl.query.insert({"account", account.get()});
-  }
-
-  return process::http::get(tokenUrl, None())
-    .after(RESPONSE_TIMEOUT, [] (Future<Response> resp) -> Future<Response> {
-      resp.discard();
-      return Failure("Timeout waiting for response to token request");
-    })
-    .then(defer(self(), [this, tokenKey](
-        const Future<Response>& response) -> Future<Token> {
-      Try<Token> token = getTokenFromResponse(response.get());
-      if (token.isError()) {
-        return Failure(
-            "Failed to parse JSON Web Token object from response: " +
-            token.error());
-      }
-
-      tokenCache_.insert({tokenKey, token.get()});
-
-      return token.get();
-    }));
-}
-
-// TODO(jojy): Add implementation for basic authentication based getToken API.
-
-} // namespace registry {
-} // namespace docker {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/docker/token_manager.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/docker/token_manager.hpp b/src/slave/containerizer/provisioners/docker/token_manager.hpp
deleted file mode 100644
index 879269d..0000000
--- a/src/slave/containerizer/provisioners/docker/token_manager.hpp
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * 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 __PROVISIONERS_DOCKER_TOKEN_MANAGER_HPP__
-#define __PROVISIONERS_DOCKER_TOKEN_MANAGER_HPP__
-
-#include <functional>
-#include <string>
-
-#include <stout/base64.hpp>
-#include <stout/duration.hpp>
-#include <stout/hashmap.hpp>
-#include <stout/strings.hpp>
-
-#include <process/future.hpp>
-#include <process/http.hpp>
-#include <process/process.hpp>
-#include <process/time.hpp>
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace docker {
-namespace registry {
-
-
-/**
- * Encapsulates JSON Web Token.
- *
- * Reference: https://tools.ietf.org/html/rfc7519.
- */
-struct Token
-{
-  /**
-   * Factory method for Token object.
-   *
-   * Parses the raw token string and validates for token's expiration.
-   *
-   * @returns Token if parsing and validation succeeds.
-   *          Error if parsing or validation fails.
-   */
-  static Try<Token> create(const std::string& rawString);
-
-  /**
-   * Compares token's expiration time(expressed in seconds) with current time.
-   *
-   * @returns True if token's expiration time is greater than current time.
-   *          False if token's expiration time is less than or equal to current
-   *          time.
-   */
-  bool isExpired() const;
-
-  /**
-   * Validates the token if its "exp" "nbf" values are in range.
-   *
-   * @returns True if current time is within token's "exp" and "nbf" values.
-   *          False if current time is not within token's "exp" and "nbf"
-   *          values.
-   */
-  bool isValid() const;
-
-  const std::string raw;
-  const JSON::Object header;
-  const JSON::Object claims;
-  // TODO(jojy): Add signature information.
-
-private:
-  Token(
-      const std::string& raw,
-      const JSON::Object& headerJson,
-      const JSON::Object& claimsJson,
-      const Option<process::Time>& expireTime,
-      const Option<process::Time>& notBeforeTime);
-
-  static Result<process::Time> getTimeValue(
-      const JSON::Object& object,
-      const std::string& key);
-
-  const Option<process::Time> expiration;
-  const Option<process::Time> notBefore;
-};
-
-
-// Forward declaration.
-class TokenManagerProcess;
-
-
-/**
- *  Acquires and manages docker registry tokens. It keeps the tokens in its
- *  cache to server any future request for the same token.
- *  The cache grows unbounded.
- *  TODO(jojy): The cache can be optimized to prune based on the expiry time of
- *  the token and server's issue time.
- */
-class TokenManager
-{
-public:
-  /**
-   * Factory method for creating TokenManager object.
-   *
-   * TokenManager and registry authorization realm has a 1:1 relationship.
-   *
-   * @param realm URL of the authorization server from where token will be
-   *     requested by this TokenManager.
-   * @returns Owned<TokenManager> if success.
-   *          Error on failure.
-   */
-  static Try<process::Owned<TokenManager>> create(
-      const process::http::URL& realm);
-
-  /**
-   * Returns JSON Web Token from cache or from remote server using "Basic
-   * authorization".
-   *
-   * @param service Name of the service that hosts the resource for which
-   *     token is being requested.
-   * @param scope unique scope returned by the 401 Unauthorized response
-   *     from the registry.
-   * @param account Name of the account which the client is acting as.
-   * @param user base64 encoded userid for basic authorization.
-   * @param password base64 encoded password for basic authorization.
-   * @returns Token struct that encapsulates JSON Web Token.
-   */
-  process::Future<Token> getToken(
-      const std::string& service,
-      const std::string& scope,
-      const Option<std::string>& account,
-      const std::string& user,
-      const Option<std::string>& password);
-
-  /**
-   * Returns JSON Web Token from cache or from remote server using "TLS/Cert"
-   * based authorization.
-   *
-   * @param service Name of the service that hosts the resource for which
-   *     token is being requested.
-   * @param scope unique scope returned by the 401 Unauthorized response
-   *     from the registry.
-   * @param account Name of the account which the client is acting as.
-   * @returns Token struct that encapsulates JSON Web Token.
-   */
-  process::Future<Token> getToken(
-      const std::string& service,
-      const std::string& scope,
-      const Option<std::string>& account);
-
-  ~TokenManager();
-
-private:
-  TokenManager(process::Owned<TokenManagerProcess>& process);
-
-  TokenManager(const TokenManager&) = delete;
-  TokenManager& operator=(const TokenManager&) = delete;
-
-  process::Owned<TokenManagerProcess> process_;
-};
-
-} // namespace registry {
-} // namespace docker {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __PROVISIONERS_DOCKER_TOKEN_MANAGER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/paths.cpp b/src/slave/containerizer/provisioners/paths.cpp
deleted file mode 100644
index 4293dd2..0000000
--- a/src/slave/containerizer/provisioners/paths.cpp
+++ /dev/null
@@ -1,191 +0,0 @@
-/**
- * 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 <list>
-
-#include <glog/logging.h>
-
-#include <mesos/type_utils.hpp>
-
-#include <stout/os.hpp>
-#include <stout/path.hpp>
-
-#include <stout/os/stat.hpp>
-
-#include "slave/containerizer/provisioners/paths.hpp"
-#include "slave/paths.hpp"
-
-using std::list;
-using std::string;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace provisioners {
-namespace paths {
-
-static string getContainersDir(const string& provisionerDir)
-{
-  return path::join(provisionerDir, "containers");
-}
-
-
-static string getBackendsDir(const string& containerDir)
-{
-  return path::join(containerDir, "backends");
-}
-
-
-static string getBackendDir(const string& backendsDir, const string& backend)
-{
-  return path::join(backendsDir, backend);
-}
-
-
-static string getRootfsesDir(const string& backendDir)
-{
-  return path::join(backendDir, "rootfses");
-}
-
-
-static string getRootfsDir(const string& rootfsesDir, const string& roofsId)
-{
-  return path::join(rootfsesDir, roofsId);
-}
-
-
-string getContainerDir(
-    const string& provisionerDir,
-    const ContainerID& containerId)
-{
-  return path::join(getContainersDir(provisionerDir), containerId.value());
-}
-
-
-string getContainerRootfsDir(
-    const string& provisionerDir,
-    const ContainerID& containerId,
-    const string& backend,
-    const string& rootfsId)
-{
-  return getRootfsDir(
-      getRootfsesDir(
-          getBackendDir(
-              getBackendsDir(
-                  getContainerDir(
-                      provisionerDir,
-                      containerId)),
-              backend)),
-      rootfsId);
-}
-
-
-Try<hashmap<ContainerID, string>> listContainers(
-    const string& provisionerDir)
-{
-  hashmap<ContainerID, string> results;
-
-  string containersDir = getContainersDir(provisionerDir);
-  if (!os::exists(containersDir)) {
-    // No container has been created yet.
-    return results;
-  }
-
-  Try<list<string>> containerIds = os::ls(containersDir);
-  if (containerIds.isError()) {
-    return Error("Unable to list the containers directory: " +
-                 containerIds.error());
-  }
-
-  foreach (const string& entry, containerIds.get()) {
-    string containerPath = path::join(containersDir, entry);
-
-    if (!os::stat::isdir(containerPath)) {
-      LOG(WARNING) << "Ignoring unexpected container entry at: "
-                   << containerPath;
-      continue;
-    }
-
-    ContainerID containerId;
-    containerId.set_value(entry);
-    results.put(containerId, containerPath);
-  }
-
-  return results;
-}
-
-
-Try<hashmap<string, hashmap<string, string>>> listContainerRootfses(
-    const string& provisionerDir,
-    const ContainerID& containerId)
-{
-  hashmap<string, hashmap<string, string>> results;
-
-  string backendsDir = getBackendsDir(
-      getContainerDir(
-          provisionerDir,
-          containerId));
-
-  Try<list<string>> backends = os::ls(backendsDir);
-  if (backends.isError()) {
-    return Error("Unable to list the container directory: " + backends.error());
-  }
-
-  foreach (const string& backend, backends.get()) {
-    string backendDir = getBackendDir(backendsDir, backend);
-    if (!os::stat::isdir(backendDir)) {
-      LOG(WARNING) << "Ignoring unexpected backend entry at: " << backendDir;
-      continue;
-    }
-
-    Try<list<string>> rootfses = os::ls(getRootfsesDir(backendDir));
-    if (rootfses.isError()) {
-      return Error("Unable to list the backend directory: " + rootfses.error());
-    }
-
-    hashmap<string, string> backendResults;
-
-    foreach (const string& rootfsId, rootfses.get()) {
-      string rootfs = getRootfsDir(getRootfsesDir(backendDir), rootfsId);
-
-      if (!os::stat::isdir(rootfs)) {
-        LOG(WARNING) << "Ignoring unexpected rootfs entry at: " << backendDir;
-        continue;
-      }
-
-      backendResults.put(rootfsId, rootfs);
-    }
-
-    if (backendResults.empty()) {
-      LOG(WARNING) << "Ignoring a backend directory with no rootfs in it: "
-                   << backendDir;
-      continue;
-    }
-
-    // The rootfs directory has passed validation.
-    results.put(backend, backendResults);
-  }
-
-  return results;
-}
-
-} // namespace paths {
-} // namespace provisioners {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/paths.hpp b/src/slave/containerizer/provisioners/paths.hpp
deleted file mode 100644
index 5b82591..0000000
--- a/src/slave/containerizer/provisioners/paths.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * 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 __MESOS_PROVISIONERS_PATHS_HPP__
-#define __MESOS_PROVISIONERS_PATHS_HPP__
-
-#include <string>
-
-#include <mesos/mesos.hpp>
-
-#include <stout/hashmap.hpp>
-#include <stout/try.hpp>
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace provisioners {
-namespace paths {
-
-// The provisioner rootfs directory is as follows:
-// <work_dir> ('--work_dir' flag)
-// |-- provisioners
-//     |-- <provisioner_type> (APPC, DOCKER, etc.)
-//         |-- containers
-//             |-- <container_id>
-//                 |-- backends
-//                     |-- <backend> (copy, bind, etc.)
-//                         |-- rootfses
-//                             |-- <rootfs_id> (the rootfs)
-//
-// NOTE: Each container could have multiple image types, therefore there
-// can be the same <container_id> directory under other provisioners e.g.,
-// <work_dir>/provisioners/DOCKER, <work_dir>/provisioners/APPC, etc.
-// Under each provisioner + container there can be multiple backends
-// due to the change of backend flags. Under each backend a rootfs is
-// identified by the 'rootfs_id' which is a UUID.
-
-std::string getContainerDir(
-    const std::string& provisionerDir,
-    const ContainerID& containerId);
-
-
-std::string getContainerRootfsDir(
-    const std::string& provisionerDir,
-    const ContainerID& containerId,
-    const std::string& backend,
-    const std::string& rootfsId);
-
-
-// Recursively "ls" the container directory and return a map of
-// backend -> rootfsId -> rootfsPath.
-Try<hashmap<std::string, hashmap<std::string, std::string>>>
-listContainerRootfses(
-    const std::string& provisionerDir,
-    const ContainerID& containerId);
-
-
-// Return a map of containerId -> containerPath;
-Try<hashmap<ContainerID, std::string>> listContainers(
-    const std::string& provisionerDir);
-
-} // namespace paths {
-} // namespace provisioners {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_PROVISIONERS_PATHS__


[3/6] mesos git commit: Moved files to prepare for unifying provisioners.

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/docker/registry_client.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/docker/registry_client.cpp b/src/slave/containerizer/provisioner/docker/registry_client.cpp
new file mode 100644
index 0000000..0a96631
--- /dev/null
+++ b/src/slave/containerizer/provisioner/docker/registry_client.cpp
@@ -0,0 +1,575 @@
+/**
+ * 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 <vector>
+
+#include <process/defer.hpp>
+#include <process/dispatch.hpp>
+#include <process/io.hpp>
+
+#include "slave/containerizer/provisioner/docker/registry_client.hpp"
+#include "slave/containerizer/provisioner/docker/token_manager.hpp"
+
+using std::string;
+using std::vector;
+
+using process::Failure;
+using process::Future;
+using process::Owned;
+using process::Process;
+
+using process::http::Request;
+using process::http::Response;
+using process::http::URL;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace docker {
+namespace registry {
+
+using FileSystemLayerInfo = RegistryClient::FileSystemLayerInfo;
+
+using ManifestResponse = RegistryClient::ManifestResponse;
+
+const Duration RegistryClient::DEFAULT_MANIFEST_TIMEOUT_SECS = Seconds(10);
+
+const size_t RegistryClient::DEFAULT_MANIFEST_MAXSIZE_BYTES = 4096;
+
+static const uint16_t DEFAULT_SSL_PORT = 443;
+
+class RegistryClientProcess : public Process<RegistryClientProcess>
+{
+public:
+  static Try<Owned<RegistryClientProcess>> create(
+      const URL& authServer,
+      const URL& registry,
+      const Option<RegistryClient::Credentials>& creds);
+
+  Future<RegistryClient::ManifestResponse> getManifest(
+      const string& path,
+      const Option<string>& tag,
+      const Duration& timeout);
+
+  Future<size_t> getBlob(
+      const string& path,
+      const Option<string>& digest,
+      const Path& filePath,
+      const Duration& timeout,
+      size_t maxSize);
+
+private:
+  RegistryClientProcess(
+    const Owned<TokenManager>& tokenMgr,
+    const URL& registryServer,
+    const Option<RegistryClient::Credentials>& creds);
+
+  Future<Response> doHttpGet(
+      const URL& url,
+      const Option<hashmap<string, string>>& headers,
+      const Duration& timeout,
+      bool resend,
+      const Option<string>& lastResponse) const;
+
+  Try<hashmap<string, string>> getAuthenticationAttributes(
+      const Response& httpResponse) const;
+
+  Owned<TokenManager> tokenManager_;
+  const URL registryServer_;
+  const Option<RegistryClient::Credentials> credentials_;
+
+  RegistryClientProcess(const RegistryClientProcess&) = delete;
+  RegistryClientProcess& operator = (const RegistryClientProcess&) = delete;
+};
+
+
+Try<Owned<RegistryClient>> RegistryClient::create(
+    const URL& authServer,
+    const URL& registryServer,
+    const Option<Credentials>& creds)
+{
+  Try<Owned<RegistryClientProcess>> process =
+    RegistryClientProcess::create(authServer, registryServer, creds);
+
+  if (process.isError()) {
+    return Error(process.error());
+  }
+
+  return Owned<RegistryClient>(
+      new RegistryClient(authServer, registryServer, creds, process.get()));
+}
+
+
+RegistryClient::RegistryClient(
+    const URL& authServer,
+    const URL& registryServer,
+    const Option<Credentials>& creds,
+    const Owned<RegistryClientProcess>& process)
+  : authServer_(authServer),
+    registryServer_(registryServer),
+    credentials_(creds),
+    process_(process)
+{
+  spawn(CHECK_NOTNULL(process_.get()));
+}
+
+
+RegistryClient::~RegistryClient()
+{
+  terminate(process_.get());
+  process::wait(process_.get());
+}
+
+
+Future<ManifestResponse> RegistryClient::getManifest(
+    const string& _path,
+    const Option<string>& _tag,
+    const Option<Duration>& _timeout)
+{
+  Duration timeout = _timeout.getOrElse(DEFAULT_MANIFEST_TIMEOUT_SECS);
+
+  return dispatch(
+      process_.get(),
+      &RegistryClientProcess::getManifest,
+      _path,
+      _tag,
+      timeout);
+}
+
+
+Future<size_t> RegistryClient::getBlob(
+    const string& _path,
+    const Option<string>& _digest,
+    const Path& _filePath,
+    const Option<Duration>& _timeout,
+    const Option<size_t>& _maxSize)
+{
+  Duration timeout = _timeout.getOrElse(DEFAULT_MANIFEST_TIMEOUT_SECS);
+  size_t maxSize = _maxSize.getOrElse(DEFAULT_MANIFEST_MAXSIZE_BYTES);
+
+  return dispatch(
+        process_.get(),
+        &RegistryClientProcess::getBlob,
+        _path,
+        _digest,
+        _filePath,
+        timeout,
+        maxSize);
+}
+
+
+Try<Owned<RegistryClientProcess>> RegistryClientProcess::create(
+    const URL& authServer,
+    const URL& registryServer,
+    const Option<RegistryClient::Credentials>& creds)
+{
+  Try<Owned<TokenManager>> tokenMgr = TokenManager::create(authServer);
+  if (tokenMgr.isError()) {
+    return Error("Failed to create token manager: " + tokenMgr.error());
+  }
+
+  return Owned<RegistryClientProcess>(
+      new RegistryClientProcess(tokenMgr.get(), registryServer, creds));
+}
+
+
+RegistryClientProcess::RegistryClientProcess(
+    const Owned<TokenManager>& tokenMgr,
+    const URL& registryServer,
+    const Option<RegistryClient::Credentials>& creds)
+  : tokenManager_(tokenMgr),
+    registryServer_(registryServer),
+    credentials_(creds) {}
+
+
+Try<hashmap<string, string>>
+RegistryClientProcess::getAuthenticationAttributes(
+    const Response& httpResponse) const
+{
+  if (httpResponse.headers.find("WWW-Authenticate") ==
+      httpResponse.headers.end()) {
+    return Error("Failed to find WWW-Authenticate header value");
+  }
+
+  const string& authString = httpResponse.headers.at("WWW-Authenticate");
+
+  const vector<string> authStringTokens = strings::tokenize(authString, " ");
+  if ((authStringTokens.size() != 2) || (authStringTokens[0] != "Bearer")) {
+    // TODO(jojy): Look at various possibilities of auth response. We currently
+    // assume that the string will have realm information.
+    return Error("Invalid authentication header value: " + authString);
+  }
+
+  const vector<string> authParams = strings::tokenize(authStringTokens[1], ",");
+
+  hashmap<string, string> authAttributes;
+  auto addAttribute = [&authAttributes](
+      const string& param) -> Try<Nothing> {
+    const vector<string> paramTokens =
+      strings::tokenize(param, "=\"");
+
+    if (paramTokens.size() != 2) {
+      return Error(
+          "Failed to get authentication attribute from response parameter " +
+          param);
+    }
+
+    authAttributes.insert({paramTokens[0], paramTokens[1]});
+
+    return Nothing();
+  };
+
+  foreach (const string& param, authParams) {
+    Try<Nothing> addRes = addAttribute(param);
+    if (addRes.isError()) {
+      return Error(addRes.error());
+    }
+  }
+
+  return authAttributes;
+}
+
+
+Future<Response>
+RegistryClientProcess::doHttpGet(
+    const URL& url,
+    const Option<hashmap<string, string>>& headers,
+    const Duration& timeout,
+    bool resend,
+    const Option<string>& lastResponseStatus) const
+{
+  return process::http::get(url, headers)
+    .after(timeout, [](
+        const Future<Response>& httpResponseFuture) -> Future<Response> {
+      return Failure("Response timeout");
+    })
+    .then(defer(self(), [=](
+        const Response& httpResponse) -> Future<Response> {
+      VLOG(1) << "Response status: " + httpResponse.status;
+
+      // Set the future if we get a OK response.
+      if (httpResponse.status == "200 OK") {
+        return httpResponse;
+      } else if (httpResponse.status == "400 Bad Request") {
+        Try<JSON::Object> errorResponse =
+          JSON::parse<JSON::Object>(httpResponse.body);
+
+        if (errorResponse.isError()) {
+          return Failure("Failed to parse bad request response JSON: " +
+                         errorResponse.error());
+        }
+
+        std::ostringstream out;
+        bool first = true;
+        Result<JSON::Array> errorObjects =
+          errorResponse.get().find<JSON::Array>("errors");
+
+        if (errorObjects.isError()) {
+          return Failure("Failed to find 'errors' in bad request response: " +
+                         errorObjects.error());
+        } else if (errorObjects.isNone()) {
+          return Failure("Errors not found in bad request response");
+        }
+
+        foreach (const JSON::Value& error, errorObjects.get().values) {
+          Result<JSON::String> message =
+            error.as<JSON::Object>().find<JSON::String>("message");
+          if (message.isError()) {
+            return Failure("Failed to parse bad request error message: " +
+                           message.error());
+          } else if (message.isNone()) {
+            continue;
+          }
+
+          if (first) {
+            out << message.get().value;
+            first = false;
+          } else {
+            out << ", " << message.get().value;
+          }
+        }
+        return Failure("Received Bad request, errors: [" + out.str() + "]");
+      }
+
+      // Prevent infinite recursion.
+      if (lastResponseStatus.isSome() &&
+          (lastResponseStatus.get() == httpResponse.status)) {
+        return Failure("Invalid response: " + httpResponse.status);
+      }
+
+      // If resend is not set, we dont try again and stop here.
+      if (!resend) {
+        return Failure("Bad response: " + httpResponse.status);
+      }
+
+      // Handle 401 Unauthorized.
+      if (httpResponse.status == "401 Unauthorized") {
+        Try<hashmap<string, string>> authAttributes =
+          getAuthenticationAttributes(httpResponse);
+
+        if (authAttributes.isError()) {
+          return Failure(
+              "Failed to get authentication attributes: " +
+              authAttributes.error());
+        }
+
+        // TODO(jojy): Currently only handling TLS/cert authentication.
+        Future<Token> tokenResponse = tokenManager_->getToken(
+          authAttributes.get().at("service"),
+          authAttributes.get().at("scope"),
+          None());
+
+        return tokenResponse
+          .after(timeout, [=](
+              Future<Token> tokenResponse) -> Future<Token> {
+            tokenResponse.discard();
+            return Failure("Token response timeout");
+          })
+          .then(defer(self(), [=](
+              const Future<Token>& tokenResponse) {
+            // Send request with acquired token.
+            hashmap<string, string> authHeaders = {
+              {"Authorization", "Bearer " + tokenResponse.get().raw}
+            };
+
+            return doHttpGet(
+                url,
+                authHeaders,
+                timeout,
+                true,
+                httpResponse.status);
+        }));
+      } else if (httpResponse.status == "307 Temporary Redirect") {
+        // Handle redirect.
+
+        // TODO(jojy): Add redirect functionality in http::get.
+
+        auto toURL = [](
+            const string& urlString) -> Try<URL> {
+          // TODO(jojy): Need to add functionality to URL class that parses a
+          // string to its URL components. For now, assuming:
+          //  - scheme is https
+          //  - path always ends with /
+
+          static const string schemePrefix = "https://";
+
+          if (!strings::contains(urlString, schemePrefix)) {
+            return Error(
+                "Failed to find expected token '" + schemePrefix +
+                "' in redirect url");
+          }
+
+          const string schemeSuffix = urlString.substr(schemePrefix.length());
+
+          const vector<string> components =
+            strings::tokenize(schemeSuffix, "/");
+
+          const string path = schemeSuffix.substr(components[0].length());
+
+          const vector<string> addrComponents =
+            strings::tokenize(components[0], ":");
+
+          uint16_t port = DEFAULT_SSL_PORT;
+          string domain = components[0];
+
+          // Parse the port.
+          if (addrComponents.size() == 2) {
+            domain = addrComponents[0];
+
+            Try<uint16_t> tryPort = numify<uint16_t>(addrComponents[1]);
+            if (tryPort.isError()) {
+              return Error(
+                  "Failed to parse location: " + urlString + " for port.");
+            }
+
+            port = tryPort.get();
+          }
+
+          return URL("https", domain, port, path);
+        };
+
+        if (httpResponse.headers.find("Location") ==
+            httpResponse.headers.end()) {
+          return Failure(
+              "Invalid redirect response: 'Location' not found in headers.");
+        }
+
+        const string& location = httpResponse.headers.at("Location");
+        Try<URL> tryUrl = toURL(location);
+        if (tryUrl.isError()) {
+          return Failure(
+              "Failed to parse '" + location + "': " + tryUrl.error());
+        }
+
+        return doHttpGet(
+            tryUrl.get(),
+            headers,
+            timeout,
+            false,
+            httpResponse.status);
+      } else {
+        return Failure("Invalid response: " + httpResponse.status);
+      }
+    }));
+}
+
+
+Future<ManifestResponse> RegistryClientProcess::getManifest(
+    const string& path,
+    const Option<string>& tag,
+    const Duration& timeout)
+{
+  //TODO(jojy): These validations belong in the URL class.
+  if (strings::contains(path, " ")) {
+    return Failure("Invalid repository path: " + path);
+  }
+
+  string repoTag = tag.getOrElse("latest");
+  if (strings::contains(repoTag, " ")) {
+    return Failure("Invalid repository tag: " + repoTag);
+  }
+
+  URL manifestURL(registryServer_);
+  manifestURL.path =
+    "v2/" + path + "/manifests/" + repoTag;
+
+  auto getManifestResponse = [](
+      const Response& httpResponse) -> Try<ManifestResponse> {
+    if (!httpResponse.headers.contains("Docker-Content-Digest")) {
+      return Error("Docker-Content-Digest header missing in response");
+    }
+
+    Try<JSON::Object> responseJSON =
+      JSON::parse<JSON::Object>(httpResponse.body);
+
+    if (responseJSON.isError()) {
+      return Error(responseJSON.error());
+    }
+
+    Result<JSON::String> name = responseJSON.get().find<JSON::String>("name");
+    if (name.isNone()) {
+      return Error("Failed to find \"name\" in manifest response");
+    }
+
+    Result<JSON::Array> fsLayers =
+      responseJSON.get().find<JSON::Array>("fsLayers");
+
+    if (fsLayers.isNone()) {
+      return Error("Failed to find \"fsLayers\" in manifest response");
+    }
+
+    vector<FileSystemLayerInfo> fsLayerInfoList;
+    foreach (const JSON::Value& layer, fsLayers.get().values) {
+      const JSON::Object& layerInfoJSON = layer.as<JSON::Object>();
+      Result<JSON::String> blobSumInfo =
+        layerInfoJSON.find<JSON::String>("blobSum");
+
+      if (blobSumInfo.isNone()) {
+        return Error("Failed to find \"blobSum\" in manifest response");
+      }
+
+      fsLayerInfoList.emplace_back(
+          FileSystemLayerInfo{blobSumInfo.get().value});
+    }
+
+    return ManifestResponse {
+      name.get().value,
+      httpResponse.headers.at("Docker-Content-Digest"),
+      fsLayerInfoList,
+    };
+  };
+
+  return doHttpGet(manifestURL, None(), timeout, true, None())
+    .then([getManifestResponse] (
+        const Response& response) -> Future<ManifestResponse> {
+      Try<ManifestResponse> manifestResponse = getManifestResponse(response);
+
+      if (manifestResponse.isError()) {
+        return Failure(
+            "Failed to parse manifest response: " + manifestResponse.error());
+      }
+
+      return manifestResponse.get();
+    });
+}
+
+
+Future<size_t> RegistryClientProcess::getBlob(
+    const string& path,
+    const Option<string>& digest,
+    const Path& filePath,
+    const Duration& timeout,
+    size_t maxSize)
+{
+  auto prepare = ([&filePath]() -> Try<Nothing> {
+      const string dirName = filePath.dirname();
+
+      //TODO(jojy): Return more state, for example - if the directory is new.
+      Try<Nothing> dirResult = os::mkdir(dirName, true);
+      if (dirResult.isError()) {
+        return Error(
+            "Failed to create directory to download blob: " +
+            dirResult.error());
+      }
+
+      return dirResult;
+  })();
+
+  // TODO(jojy): This currently leaves a residue in failure cases. Would be
+  // ideal if we can completely rollback.
+  if (prepare.isError()) {
+     return Failure(prepare.error());
+  }
+
+  if (strings::contains(path, " ")) {
+    return Failure("Invalid repository path: " + path);
+  }
+
+  URL blobURL(registryServer_);
+  blobURL.path =
+    "v2/" + path + "/blobs/" + digest.getOrElse("");
+
+  auto saveBlob = [filePath](
+      const Response& httpResponse) -> Future<size_t> {
+    // TODO(jojy): Add verification step.
+    // TODO(jojy): Add check for max size.
+    size_t size = httpResponse.body.length();
+    Try<int> fd = os::open(
+        filePath.value,
+        O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
+        S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+    if (fd.isError()) {
+      return Failure("Failed to open file '" + filePath.value + "': " +
+                     fd.error());
+    }
+
+    return process::io::write(fd.get(), httpResponse.body)
+      .then([size](const Future<Nothing>&) { return size; })
+      .onAny([fd]() { os::close(fd.get()); } );
+  };
+
+  return doHttpGet(blobURL, None(), timeout, true, None())
+    .then([saveBlob](const Response& response) { return saveBlob(response); });
+}
+
+} // namespace registry {
+} // namespace docker {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/docker/registry_client.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/docker/registry_client.hpp b/src/slave/containerizer/provisioner/docker/registry_client.hpp
new file mode 100644
index 0000000..9d5d154
--- /dev/null
+++ b/src/slave/containerizer/provisioner/docker/registry_client.hpp
@@ -0,0 +1,163 @@
+/**
+ * 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 __PROVISIONER_DOCKER_REGISTRY_CLIENT_HPP__
+#define __PROVISIONER_DOCKER_REGISTRY_CLIENT_HPP__
+
+#include <string>
+#include <vector>
+
+#include <stout/duration.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/json.hpp>
+#include <stout/path.hpp>
+
+#include <process/future.hpp>
+#include <process/http.hpp>
+#include <process/process.hpp>
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace docker {
+namespace registry {
+
+// Forward declarations.
+class RegistryClientProcess;
+
+
+class RegistryClient
+{
+public:
+  /**
+   * Encapsulates information about a file system layer.
+   */
+  struct FileSystemLayerInfo {
+    //TODO(jojy): This string includes the checksum type also now. Need to
+    //separate this into checksum method and checksum.
+    std::string checksumInfo;
+  };
+
+  /**
+   * Encapsulates response of "GET Manifest" request.
+   *
+   * Reference: https://docs.docker.com/registry/spec/api
+   */
+  struct ManifestResponse {
+    const std::string name;
+    const std::string digest;
+    const std::vector<FileSystemLayerInfo> fsLayerInfoList;
+  };
+
+  /**
+   * Encapsulates auth credentials for the client sessions.
+   * TODO(jojy): Secure heap to protect the credentials.
+   */
+  struct Credentials {
+    /**
+     * UserId for basic authentication.
+     */
+    const Option<std::string> userId;
+    /**
+     * Password for basic authentication.
+     */
+    const Option<std::string> password;
+    /**
+     * Account for fetching data from registry.
+     */
+    const Option<std::string> account;
+  };
+
+  /**
+   * Factory method for creating RegistryClient objects.
+   *
+   * @param authServer URL of authorization server.
+   * @param registryServer URL of docker registry server.
+   * @param credentials credentials for client session (optional).
+   * @return RegistryClient on Success.
+   *         Error on failure.
+   */
+  static Try<process::Owned<RegistryClient>> create(
+      const process::http::URL& authServer,
+      const process::http::URL& registryServer,
+      const Option<Credentials>& credentials);
+
+  /**
+   * Fetches manifest for a repository from the client's remote registry server.
+   *
+   * @param path path of the repository on the registry.
+   * @param tag unique tag that identifies the repository. Will default to
+   *    latest.
+   * @param timeout Maximum time ater which the request will timeout and return
+   *    a failure. Will default to RESPONSE_TIMEOUT.
+   * @return JSON object on success.
+   *         Failure on process failure.
+   */
+  process::Future<ManifestResponse> getManifest(
+      const std::string& path,
+      const Option<std::string>& tag,
+      const Option<Duration>& timeout);
+
+  /**
+   * Fetches blob for a repository from the client's remote registry server.
+   *
+   * @param path path of the repository on the registry.
+   * @param digest digest of the blob (from manifest).
+   * @param filePath file path to store the fetched blob.
+   * @param timeout Maximum time ater which the request will timeout and return
+   *    a failure. Will default to RESPONSE_TIMEOUT.
+   * @param maxSize Maximum size of the response thats acceptable. Will default
+   *    to MAX_RESPONSE_SIZE.
+   * @return size of downloaded blob on success.
+   *         Failure in case of any errors.
+   */
+  process::Future<size_t> getBlob(
+      const std::string& path,
+      const Option<std::string>& digest,
+      const Path& filePath,
+      const Option<Duration>& timeout,
+      const Option<size_t>& maxSize);
+
+  ~RegistryClient();
+
+private:
+  RegistryClient(
+    const process::http::URL& authServer,
+    const process::http::URL& registryServer,
+    const Option<Credentials>& credentials,
+    const process::Owned<RegistryClientProcess>& process);
+
+  static const Duration DEFAULT_MANIFEST_TIMEOUT_SECS;
+  static const size_t DEFAULT_MANIFEST_MAXSIZE_BYTES;
+
+  const process::http::URL authServer_;
+  const process::http::URL registryServer_;
+  const Option<Credentials> credentials_;
+  process::Owned<RegistryClientProcess> process_;
+
+  RegistryClient(const RegistryClient&) = delete;
+  RegistryClient& operator=(const RegistryClient&) = delete;
+};
+
+} // namespace registry {
+} // namespace docker {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_DOCKER_REGISTRY_CLIENT_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/docker/token_manager.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/docker/token_manager.cpp b/src/slave/containerizer/provisioner/docker/token_manager.cpp
new file mode 100644
index 0000000..cf52626
--- /dev/null
+++ b/src/slave/containerizer/provisioner/docker/token_manager.cpp
@@ -0,0 +1,361 @@
+/**
+ * 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 <process/defer.hpp>
+#include <process/dispatch.hpp>
+
+#include "slave/containerizer/provisioner/docker/token_manager.hpp"
+
+using std::hash;
+using std::string;
+using std::vector;
+
+using process::Clock;
+using process::Failure;
+using process::Future;
+using process::Owned;
+using process::Process;
+using process::Time;
+
+using process::http::Request;
+using process::http::Response;
+using process::http::URL;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace docker {
+namespace registry {
+
+class TokenManagerProcess : public Process<TokenManagerProcess>
+{
+public:
+  static Try<Owned<TokenManagerProcess>> create(const URL& realm);
+
+  Future<Token> getToken(
+      const string& service,
+      const string& scope,
+      const Option<string>& account);
+
+private:
+  static const string TOKEN_PATH_PREFIX;
+  static const Duration RESPONSE_TIMEOUT;
+
+  TokenManagerProcess(const URL& realm)
+    : realm_(realm) {}
+
+  Try<Token> getTokenFromResponse(const Response& response) const;
+
+  /**
+   * Key for the token cache.
+   */
+  struct TokenCacheKey
+  {
+    string service;
+    string scope;
+  };
+
+  struct TokenCacheKeyHash
+  {
+    size_t operator()(const TokenCacheKey& key) const
+    {
+      hash<string> hashFn;
+
+      return (hashFn(key.service) ^
+          (hashFn(key.scope) << 1));
+    }
+  };
+
+  struct TokenCacheKeyEqual
+  {
+    bool operator()(
+        const TokenCacheKey& left,
+        const TokenCacheKey& right) const
+    {
+      return ((left.service == right.service) &&
+          (left.scope == right.scope));
+    }
+  };
+
+  typedef hashmap<
+    const TokenCacheKey,
+    Token,
+    TokenCacheKeyHash,
+    TokenCacheKeyEqual> TokenCacheType;
+
+  const URL realm_;
+  TokenCacheType tokenCache_;
+
+  TokenManagerProcess(const TokenManagerProcess&) = delete;
+  TokenManagerProcess& operator=(const TokenManagerProcess&) = delete;
+};
+
+const Duration TokenManagerProcess::RESPONSE_TIMEOUT = Seconds(10);
+const string TokenManagerProcess::TOKEN_PATH_PREFIX = "/v2/token/";
+
+
+Token::Token(
+    const string& _raw,
+    const JSON::Object& _header,
+    const JSON::Object& _claims,
+    const Option<Time>& _expiration,
+    const Option<Time>& _notBefore)
+  : raw(_raw),
+    header(_header),
+    claims(_claims),
+    expiration(_expiration),
+    notBefore(_notBefore) {}
+
+
+Try<Token> Token::create(const string& raw)
+{
+  auto decode = [](
+      const string& segment) -> Try<JSON::Object> {
+    const auto padding = segment.length() % 4;
+    string paddedSegment(segment);
+
+    if (padding) {
+      paddedSegment.append(padding, '=');
+    }
+
+    Try<string> decoded = base64::decode(paddedSegment);
+    if (decoded.isError()) {
+      return Error(decoded.error());
+    }
+
+    return JSON::parse<JSON::Object>(decoded.get());
+  };
+
+  const vector<string> tokens = strings::tokenize(raw, ".");
+
+  if (tokens.size() != 3) {
+    return Error("Invalid raw token string");
+  }
+
+  Try<JSON::Object> header = decode(tokens[0]);
+  if (header.isError()) {
+    return Error("Failed to decode 'header' segment: " + header.error());
+  }
+
+  Try<JSON::Object> claims = decode(tokens[1]);
+  if (claims.isError()) {
+    return Error("Failed to decode 'claims' segment: " + claims.error());
+  }
+
+  Result<Time> expirationTime = getTimeValue(claims.get(), "exp");
+  if (expirationTime.isError()) {
+    return Error("Failed to decode expiration time: " + expirationTime.error());
+  }
+
+  Option<Time> expiration;
+  if (expirationTime.isSome()) {
+    expiration = expirationTime.get();
+  }
+
+  Result<Time> notBeforeTime = getTimeValue(claims.get(), "nbf");
+  if (notBeforeTime.isError()) {
+    return Error("Failed to decode not-before time: " + notBeforeTime.error());
+  }
+
+  Option<Time> notBefore;
+  if (notBeforeTime.isSome()) {
+    notBefore = notBeforeTime.get();
+  }
+
+  Token token(raw, header.get(), claims.get(), expiration, notBefore);
+
+  if (token.isExpired()) {
+    return Error("Token has expired");
+  }
+
+  // TODO(jojy): Add signature validation.
+  return token;
+}
+
+
+Result<Time> Token::getTimeValue(const JSON::Object& object, const string& key)
+{
+  Result<JSON::Number> jsonValue = object.find<JSON::Number>(key);
+
+  Option<Time> timeValue;
+
+  // If expiration is provided, we will process it for future validations.
+  if (jsonValue.isSome()) {
+    Try<Time> time = Time::create(jsonValue.get().value);
+    if (time.isError()) {
+      return Error("Failed to decode time: " + time.error());
+    }
+
+    timeValue = time.get();
+  }
+
+  return timeValue;
+}
+
+
+bool Token::isExpired() const
+{
+  if (expiration.isSome()) {
+    return (Clock::now() >= expiration.get());
+  }
+
+  return false;
+}
+
+
+bool Token::isValid() const
+{
+  if (!isExpired()) {
+    if (notBefore.isSome()) {
+      return (Clock::now() >= notBefore.get());
+    }
+
+    return true;
+  }
+
+  // TODO(jojy): Add signature validation.
+  return false;
+}
+
+
+Try<Owned<TokenManager>> TokenManager::create(
+    const URL& realm)
+{
+  Try<Owned<TokenManagerProcess>> process = TokenManagerProcess::create(realm);
+  if (process.isError()) {
+    return Error(process.error());
+  }
+
+  return Owned<TokenManager>(new TokenManager(process.get()));
+}
+
+
+TokenManager::TokenManager(Owned<TokenManagerProcess>& process)
+  : process_(process)
+{
+  spawn(CHECK_NOTNULL(process_.get()));
+}
+
+
+TokenManager::~TokenManager()
+{
+  terminate(process_.get());
+  process::wait(process_.get());
+}
+
+
+Future<Token> TokenManager::getToken(
+    const string& service,
+    const string& scope,
+    const Option<string>& account)
+{
+  return dispatch(
+      process_.get(),
+      &TokenManagerProcess::getToken,
+      service,
+      scope,
+      account);
+}
+
+
+Try<Owned<TokenManagerProcess>> TokenManagerProcess::create(const URL& realm)
+{
+  return Owned<TokenManagerProcess>(new TokenManagerProcess(realm));
+}
+
+
+Try<Token> TokenManagerProcess::getTokenFromResponse(
+    const Response& response) const
+{
+  Try<JSON::Object> tokenJSON = JSON::parse<JSON::Object>(response.body);
+  if (tokenJSON.isError()) {
+    return Error(tokenJSON.error());
+  }
+
+  Result<JSON::String> tokenString =
+    tokenJSON.get().find<JSON::String>("token");
+
+  if (tokenString.isError()) {
+    return Error(tokenString.error());
+  }
+
+  Try<Token> result = Token::create(tokenString.get().value);
+  if (result.isError()) {
+    return Error(result.error());
+  }
+
+  return result.get();;
+}
+
+
+Future<Token> TokenManagerProcess::getToken(
+    const string& service,
+    const string& scope,
+    const Option<string>& account)
+{
+  const TokenCacheKey tokenKey = {service, scope};
+
+  if (tokenCache_.contains(tokenKey)) {
+    Token token = tokenCache_.at(tokenKey);
+
+    if (token.isValid()) {
+      return token;
+    } else {
+      LOG(WARNING) << "Cached token was invalid. Will fetch once again";
+    }
+  }
+
+  URL tokenUrl = realm_;
+  tokenUrl.path = TOKEN_PATH_PREFIX;
+
+  tokenUrl.query = {
+    {"service", service},
+    {"scope", scope},
+  };
+
+  if (account.isSome()) {
+    tokenUrl.query.insert({"account", account.get()});
+  }
+
+  return process::http::get(tokenUrl, None())
+    .after(RESPONSE_TIMEOUT, [] (Future<Response> resp) -> Future<Response> {
+      resp.discard();
+      return Failure("Timeout waiting for response to token request");
+    })
+    .then(defer(self(), [this, tokenKey](
+        const Future<Response>& response) -> Future<Token> {
+      Try<Token> token = getTokenFromResponse(response.get());
+      if (token.isError()) {
+        return Failure(
+            "Failed to parse JSON Web Token object from response: " +
+            token.error());
+      }
+
+      tokenCache_.insert({tokenKey, token.get()});
+
+      return token.get();
+    }));
+}
+
+// TODO(jojy): Add implementation for basic authentication based getToken API.
+
+} // namespace registry {
+} // namespace docker {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/docker/token_manager.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/docker/token_manager.hpp b/src/slave/containerizer/provisioner/docker/token_manager.hpp
new file mode 100644
index 0000000..2f4abff
--- /dev/null
+++ b/src/slave/containerizer/provisioner/docker/token_manager.hpp
@@ -0,0 +1,179 @@
+/**
+ * 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 __PROVISIONER_DOCKER_TOKEN_MANAGER_HPP__
+#define __PROVISIONER_DOCKER_TOKEN_MANAGER_HPP__
+
+#include <functional>
+#include <string>
+
+#include <stout/base64.hpp>
+#include <stout/duration.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/strings.hpp>
+
+#include <process/future.hpp>
+#include <process/http.hpp>
+#include <process/process.hpp>
+#include <process/time.hpp>
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace docker {
+namespace registry {
+
+
+/**
+ * Encapsulates JSON Web Token.
+ *
+ * Reference: https://tools.ietf.org/html/rfc7519.
+ */
+struct Token
+{
+  /**
+   * Factory method for Token object.
+   *
+   * Parses the raw token string and validates for token's expiration.
+   *
+   * @returns Token if parsing and validation succeeds.
+   *          Error if parsing or validation fails.
+   */
+  static Try<Token> create(const std::string& rawString);
+
+  /**
+   * Compares token's expiration time(expressed in seconds) with current time.
+   *
+   * @returns True if token's expiration time is greater than current time.
+   *          False if token's expiration time is less than or equal to current
+   *          time.
+   */
+  bool isExpired() const;
+
+  /**
+   * Validates the token if its "exp" "nbf" values are in range.
+   *
+   * @returns True if current time is within token's "exp" and "nbf" values.
+   *          False if current time is not within token's "exp" and "nbf"
+   *          values.
+   */
+  bool isValid() const;
+
+  const std::string raw;
+  const JSON::Object header;
+  const JSON::Object claims;
+  // TODO(jojy): Add signature information.
+
+private:
+  Token(
+      const std::string& raw,
+      const JSON::Object& headerJson,
+      const JSON::Object& claimsJson,
+      const Option<process::Time>& expireTime,
+      const Option<process::Time>& notBeforeTime);
+
+  static Result<process::Time> getTimeValue(
+      const JSON::Object& object,
+      const std::string& key);
+
+  const Option<process::Time> expiration;
+  const Option<process::Time> notBefore;
+};
+
+
+// Forward declaration.
+class TokenManagerProcess;
+
+
+/**
+ *  Acquires and manages docker registry tokens. It keeps the tokens in its
+ *  cache to server any future request for the same token.
+ *  The cache grows unbounded.
+ *  TODO(jojy): The cache can be optimized to prune based on the expiry time of
+ *  the token and server's issue time.
+ */
+class TokenManager
+{
+public:
+  /**
+   * Factory method for creating TokenManager object.
+   *
+   * TokenManager and registry authorization realm has a 1:1 relationship.
+   *
+   * @param realm URL of the authorization server from where token will be
+   *     requested by this TokenManager.
+   * @returns Owned<TokenManager> if success.
+   *          Error on failure.
+   */
+  static Try<process::Owned<TokenManager>> create(
+      const process::http::URL& realm);
+
+  /**
+   * Returns JSON Web Token from cache or from remote server using "Basic
+   * authorization".
+   *
+   * @param service Name of the service that hosts the resource for which
+   *     token is being requested.
+   * @param scope unique scope returned by the 401 Unauthorized response
+   *     from the registry.
+   * @param account Name of the account which the client is acting as.
+   * @param user base64 encoded userid for basic authorization.
+   * @param password base64 encoded password for basic authorization.
+   * @returns Token struct that encapsulates JSON Web Token.
+   */
+  process::Future<Token> getToken(
+      const std::string& service,
+      const std::string& scope,
+      const Option<std::string>& account,
+      const std::string& user,
+      const Option<std::string>& password);
+
+  /**
+   * Returns JSON Web Token from cache or from remote server using "TLS/Cert"
+   * based authorization.
+   *
+   * @param service Name of the service that hosts the resource for which
+   *     token is being requested.
+   * @param scope unique scope returned by the 401 Unauthorized response
+   *     from the registry.
+   * @param account Name of the account which the client is acting as.
+   * @returns Token struct that encapsulates JSON Web Token.
+   */
+  process::Future<Token> getToken(
+      const std::string& service,
+      const std::string& scope,
+      const Option<std::string>& account);
+
+  ~TokenManager();
+
+private:
+  TokenManager(process::Owned<TokenManagerProcess>& process);
+
+  TokenManager(const TokenManager&) = delete;
+  TokenManager& operator=(const TokenManager&) = delete;
+
+  process::Owned<TokenManagerProcess> process_;
+};
+
+} // namespace registry {
+} // namespace docker {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_DOCKER_TOKEN_MANAGER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/paths.cpp b/src/slave/containerizer/provisioner/paths.cpp
new file mode 100644
index 0000000..cb2690e
--- /dev/null
+++ b/src/slave/containerizer/provisioner/paths.cpp
@@ -0,0 +1,192 @@
+/**
+ * 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 <list>
+
+#include <glog/logging.h>
+
+#include <mesos/type_utils.hpp>
+
+#include <stout/os.hpp>
+#include <stout/path.hpp>
+
+#include <stout/os/stat.hpp>
+
+#include "slave/paths.hpp"
+
+#include "slave/containerizer/provisioner/paths.hpp"
+
+using std::list;
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace provisioners {
+namespace paths {
+
+static string getContainersDir(const string& provisionerDir)
+{
+  return path::join(provisionerDir, "containers");
+}
+
+
+static string getBackendsDir(const string& containerDir)
+{
+  return path::join(containerDir, "backends");
+}
+
+
+static string getBackendDir(const string& backendsDir, const string& backend)
+{
+  return path::join(backendsDir, backend);
+}
+
+
+static string getRootfsesDir(const string& backendDir)
+{
+  return path::join(backendDir, "rootfses");
+}
+
+
+static string getRootfsDir(const string& rootfsesDir, const string& roofsId)
+{
+  return path::join(rootfsesDir, roofsId);
+}
+
+
+string getContainerDir(
+    const string& provisionerDir,
+    const ContainerID& containerId)
+{
+  return path::join(getContainersDir(provisionerDir), containerId.value());
+}
+
+
+string getContainerRootfsDir(
+    const string& provisionerDir,
+    const ContainerID& containerId,
+    const string& backend,
+    const string& rootfsId)
+{
+  return getRootfsDir(
+      getRootfsesDir(
+          getBackendDir(
+              getBackendsDir(
+                  getContainerDir(
+                      provisionerDir,
+                      containerId)),
+              backend)),
+      rootfsId);
+}
+
+
+Try<hashmap<ContainerID, string>> listContainers(
+    const string& provisionerDir)
+{
+  hashmap<ContainerID, string> results;
+
+  string containersDir = getContainersDir(provisionerDir);
+  if (!os::exists(containersDir)) {
+    // No container has been created yet.
+    return results;
+  }
+
+  Try<list<string>> containerIds = os::ls(containersDir);
+  if (containerIds.isError()) {
+    return Error("Unable to list the containers directory: " +
+                 containerIds.error());
+  }
+
+  foreach (const string& entry, containerIds.get()) {
+    string containerPath = path::join(containersDir, entry);
+
+    if (!os::stat::isdir(containerPath)) {
+      LOG(WARNING) << "Ignoring unexpected container entry at: "
+                   << containerPath;
+      continue;
+    }
+
+    ContainerID containerId;
+    containerId.set_value(entry);
+    results.put(containerId, containerPath);
+  }
+
+  return results;
+}
+
+
+Try<hashmap<string, hashmap<string, string>>> listContainerRootfses(
+    const string& provisionerDir,
+    const ContainerID& containerId)
+{
+  hashmap<string, hashmap<string, string>> results;
+
+  string backendsDir = getBackendsDir(
+      getContainerDir(
+          provisionerDir,
+          containerId));
+
+  Try<list<string>> backends = os::ls(backendsDir);
+  if (backends.isError()) {
+    return Error("Unable to list the container directory: " + backends.error());
+  }
+
+  foreach (const string& backend, backends.get()) {
+    string backendDir = getBackendDir(backendsDir, backend);
+    if (!os::stat::isdir(backendDir)) {
+      LOG(WARNING) << "Ignoring unexpected backend entry at: " << backendDir;
+      continue;
+    }
+
+    Try<list<string>> rootfses = os::ls(getRootfsesDir(backendDir));
+    if (rootfses.isError()) {
+      return Error("Unable to list the backend directory: " + rootfses.error());
+    }
+
+    hashmap<string, string> backendResults;
+
+    foreach (const string& rootfsId, rootfses.get()) {
+      string rootfs = getRootfsDir(getRootfsesDir(backendDir), rootfsId);
+
+      if (!os::stat::isdir(rootfs)) {
+        LOG(WARNING) << "Ignoring unexpected rootfs entry at: " << backendDir;
+        continue;
+      }
+
+      backendResults.put(rootfsId, rootfs);
+    }
+
+    if (backendResults.empty()) {
+      LOG(WARNING) << "Ignoring a backend directory with no rootfs in it: "
+                   << backendDir;
+      continue;
+    }
+
+    // The rootfs directory has passed validation.
+    results.put(backend, backendResults);
+  }
+
+  return results;
+}
+
+} // namespace paths {
+} // namespace provisioners {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/paths.hpp b/src/slave/containerizer/provisioner/paths.hpp
new file mode 100644
index 0000000..4ea47e3
--- /dev/null
+++ b/src/slave/containerizer/provisioner/paths.hpp
@@ -0,0 +1,83 @@
+/**
+ * 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 __PROVISIONER_PATHS_HPP__
+#define __PROVISIONER_PATHS_HPP__
+
+#include <string>
+
+#include <mesos/mesos.hpp>
+
+#include <stout/hashmap.hpp>
+#include <stout/try.hpp>
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace provisioners {
+namespace paths {
+
+// The provisioner rootfs directory is as follows:
+// <work_dir> ('--work_dir' flag)
+// |-- provisioners
+//     |-- <provisioner_type> (APPC, DOCKER, etc.)
+//         |-- containers
+//             |-- <container_id>
+//                 |-- backends
+//                     |-- <backend> (copy, bind, etc.)
+//                         |-- rootfses
+//                             |-- <rootfs_id> (the rootfs)
+//
+// NOTE: Each container could have multiple image types, therefore there
+// can be the same <container_id> directory under other provisioners e.g.,
+// <work_dir>/provisioners/DOCKER, <work_dir>/provisioners/APPC, etc.
+// Under each provisioner + container there can be multiple backends
+// due to the change of backend flags. Under each backend a rootfs is
+// identified by the 'rootfs_id' which is a UUID.
+
+std::string getContainerDir(
+    const std::string& provisionerDir,
+    const ContainerID& containerId);
+
+
+std::string getContainerRootfsDir(
+    const std::string& provisionerDir,
+    const ContainerID& containerId,
+    const std::string& backend,
+    const std::string& rootfsId);
+
+
+// Recursively "ls" the container directory and return a map of
+// backend -> rootfsId -> rootfsPath.
+Try<hashmap<std::string, hashmap<std::string, std::string>>>
+listContainerRootfses(
+    const std::string& provisionerDir,
+    const ContainerID& containerId);
+
+
+// Return a map of containerId -> containerPath;
+Try<hashmap<ContainerID, std::string>> listContainers(
+    const std::string& provisionerDir);
+
+} // namespace paths {
+} // namespace provisioners {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_PATHS_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/provisioner.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/provisioner.cpp b/src/slave/containerizer/provisioner/provisioner.cpp
new file mode 100644
index 0000000..cb751dc
--- /dev/null
+++ b/src/slave/containerizer/provisioner/provisioner.cpp
@@ -0,0 +1,79 @@
+/**
+ * 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 <stout/hashset.hpp>
+#include <stout/stringify.hpp>
+#include <stout/strings.hpp>
+
+#include "slave/containerizer/provisioner/provisioner.hpp"
+
+#include "slave/containerizer/provisioner/appc/provisioner.hpp"
+
+using namespace process;
+
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+Try<hashmap<Image::Type, Owned<Provisioner>>> Provisioner::create(
+    const Flags& flags,
+    Fetcher* fetcher)
+{
+  if (flags.provisioners.isNone()) {
+    return hashmap<Image::Type, Owned<Provisioner>>();
+  }
+
+  hashmap<Image::Type,
+          Try<Owned<Provisioner>>(*)(const Flags&, Fetcher*)> creators;
+
+  // Register all supported creators.
+  creators.put(Image::APPC, &appc::AppcProvisioner::create);
+
+  hashmap<Image::Type, Owned<Provisioner>> provisioners;
+
+  // NOTE: Change in '--provisioners' flag may result in leaked rootfs
+  // files on the disk but it's at least safe because files managed by
+  // different provisioners are totally separated.
+  foreach (const string& type,
+           strings::tokenize(flags.provisioners.get(), ",")) {
+     Image::Type imageType;
+     if (!Image::Type_Parse(strings::upper(type), &imageType)) {
+       return Error("Unknown provisioner '" + type + "'");
+     }
+
+     if (!creators.contains(imageType)) {
+       return Error("Unsupported provisioner '" + type + "'");
+     }
+
+     Try<Owned<Provisioner>> provisioner = creators[imageType](flags, fetcher);
+     if (provisioner.isError()) {
+       return Error("Failed to create '" + stringify(imageType) +
+                    "' provisioner: " + provisioner.error());
+     }
+
+     provisioners[imageType] = provisioner.get();
+  }
+
+  return provisioners;
+}
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/provisioner.hpp b/src/slave/containerizer/provisioner/provisioner.hpp
new file mode 100644
index 0000000..c55225e
--- /dev/null
+++ b/src/slave/containerizer/provisioner/provisioner.hpp
@@ -0,0 +1,79 @@
+/**
+ * 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 __PROVISIONER_HPP__
+#define __PROVISIONER_HPP__
+
+#include <list>
+
+#include <mesos/resources.hpp>
+
+#include <mesos/slave/isolator.hpp> // For ContainerState.
+
+#include <stout/hashmap.hpp>
+#include <stout/nothing.hpp>
+#include <stout/try.hpp>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+
+#include "slave/flags.hpp"
+
+#include "slave/containerizer/fetcher.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+class Provisioner
+{
+public:
+  virtual ~Provisioner() {}
+
+  // Create provisioners based on specified flags. An error is returned if
+  // any of the provisioners specified in --provisioner failed to be created.
+  static Try<hashmap<Image::Type, process::Owned<Provisioner>>>
+    create(const Flags& flags, Fetcher* fetcher);
+
+  // Recover root filesystems for containers from the run states and
+  // the orphan containers (known to the launcher but not known to the
+  // slave) detected by the launcher. This function is also
+  // responsible for cleaning up any intermediate artifacts (e.g.
+  // directories) to not leak anything.
+  virtual process::Future<Nothing> recover(
+      const std::list<mesos::slave::ContainerState>& states,
+      const hashset<ContainerID>& orphans) = 0;
+
+  // Provision a root filesystem for the container using the specified
+  // image and return the absolute path to the root filesystem.
+  virtual process::Future<std::string> provision(
+      const ContainerID& containerId,
+      const Image& image) = 0;
+
+  // Destroy a previously provisioned root filesystem. Assumes that
+  // all references (e.g., mounts, open files) to the provisioned
+  // filesystem have been removed. Return false if there is no
+  // provisioned root filesystem for the given container.
+  virtual process::Future<bool> destroy(const ContainerID& containerId) = 0;
+};
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/paths.cpp b/src/slave/containerizer/provisioners/appc/paths.cpp
deleted file mode 100644
index e598df0..0000000
--- a/src/slave/containerizer/provisioners/appc/paths.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * 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 <list>
-
-#include <glog/logging.h>
-
-#include <stout/path.hpp>
-
-#include "slave/containerizer/provisioners/appc/paths.hpp"
-
-using std::list;
-using std::string;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-namespace paths {
-
-string getStagingDir(const string& storeDir)
-{
-  return path::join(storeDir, "staging");
-}
-
-
-string getImagesDir(const string& storeDir)
-{
-  return path::join(storeDir, "images");
-}
-
-
-string getImagePath(const string& storeDir, const string& imageId)
-{
-  return path::join(getImagesDir(storeDir), imageId);
-}
-
-
-string getImageRootfsPath(
-    const string& storeDir,
-    const string& imageId)
-{
-  return path::join(getImagePath(storeDir, imageId), "rootfs");
-}
-
-
-string getImageRootfsPath(const string& imagePath)
-{
-  return path::join(imagePath, "rootfs");
-}
-
-
-string getImageManifestPath(
-    const string& storeDir,
-    const string& imageId)
-{
-  return path::join(getImagePath(storeDir, imageId), "manifest");
-}
-
-
-string getImageManifestPath(const string& imagePath)
-{
-  return path::join(imagePath, "manifest");
-}
-
-} // namespace paths {
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/paths.hpp b/src/slave/containerizer/provisioners/appc/paths.hpp
deleted file mode 100644
index 37bbf09..0000000
--- a/src/slave/containerizer/provisioners/appc/paths.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * 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 __MESOS_APPC_PATHS_HPP__
-#define __MESOS_APPC_PATHS_HPP__
-
-#include <string>
-
-#include <mesos/mesos.hpp>
-
-#include <stout/hashmap.hpp>
-#include <stout/try.hpp>
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-namespace paths {
-
-// The appc store file system layout is as follows:
-//
-// <store_dir> ('--appc_store_dir' flag)
-// |--staging (contains temp directories for staging downloads)
-// |
-// |--images (stores validated images)
-//    |--<image_id> (in the form of "sha512-<128_character_hash_sum>")
-//       |--manifest
-//       |--rootfs
-//          |--... (according to the ACI spec)
-//
-// TODO(xujyan): The staging directory is unused for now (it's
-// externally managed) but implemented to illustrate the need for a
-// separate 'images' directory. Complete the layout diagram when the
-// staging directory is utilized by the provisioner.
-
-std::string getStagingDir(const std::string& storeDir);
-
-
-std::string getImagesDir(const std::string& storeDir);
-
-
-std::string getImagePath(
-    const std::string& storeDir,
-    const std::string& imageId);
-
-
-std::string getImageRootfsPath(
-    const std::string& storeDir,
-    const std::string& imageId);
-
-
-std::string getImageRootfsPath(const std::string& imagePath);
-
-
-std::string getImageManifestPath(
-    const std::string& storeDir,
-    const std::string& imageId);
-
-
-std::string getImageManifestPath(const std::string& imagePath);
-
-} // namespace paths {
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_APPC_PATHS__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/provisioner.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/provisioner.cpp b/src/slave/containerizer/provisioners/appc/provisioner.cpp
deleted file mode 100644
index 77f9cbe..0000000
--- a/src/slave/containerizer/provisioners/appc/provisioner.cpp
+++ /dev/null
@@ -1,397 +0,0 @@
-/**
- * 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 <mesos/type_utils.hpp>
-
-#include <process/collect.hpp>
-#include <process/defer.hpp>
-#include <process/dispatch.hpp>
-#include <process/process.hpp>
-
-#include <stout/foreach.hpp>
-#include <stout/hashset.hpp>
-#include <stout/os.hpp>
-#include <stout/stringify.hpp>
-#include <stout/strings.hpp>
-#include <stout/uuid.hpp>
-
-#include "slave/containerizer/provisioners/backend.hpp"
-#include "slave/containerizer/provisioners/paths.hpp"
-
-#include "slave/containerizer/provisioners/appc/paths.hpp"
-#include "slave/containerizer/provisioners/appc/provisioner.hpp"
-#include "slave/containerizer/provisioners/appc/spec.hpp"
-#include "slave/containerizer/provisioners/appc/store.hpp"
-
-#include "slave/paths.hpp"
-
-using namespace process;
-using namespace mesos::internal::slave;
-
-using std::list;
-using std::string;
-using std::vector;
-
-using mesos::slave::ContainerState;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-
-class AppcProvisionerProcess : public Process<AppcProvisionerProcess>
-{
-public:
-  AppcProvisionerProcess(
-      const Flags& flags,
-      const string& root,
-      const Owned<Store>& store,
-      const hashmap<string, Owned<Backend>>& backends);
-
-  Future<Nothing> recover(
-      const list<ContainerState>& states,
-      const hashset<ContainerID>& orphans);
-
-  Future<string> provision(const ContainerID& containerId, const Image& image);
-
-  Future<bool> destroy(const ContainerID& containerId);
-
-private:
-  Future<string> _provision(const vector<string>& layers, const string& rootfs);
-
-  const Flags flags;
-
-  // Absolute path to the Appc provisioner root directory. It can be derived
-  // from '--work_dir' but we keep a separate copy here because we converted
-  // it into an absolute path so managed rootfs paths match the ones in
-  // 'mountinfo' (important if mount-based backends are used).
-  const string root;
-
-  const Owned<Store> store;
-  const hashmap<string, Owned<Backend>> backends;
-
-  struct Info
-  {
-    // Mappings: backend -> rootfsId -> rootfsPath.
-    hashmap<string, hashmap<string, string>> rootfses;
-  };
-
-  hashmap<ContainerID, Owned<Info>> infos;
-};
-
-
-// NOTE: Successful creation of the provisioner means its managed
-// directory under --work_dir is also created.
-Try<Owned<Provisioner>> AppcProvisioner::create(
-    const Flags& flags,
-    Fetcher* fetcher)
-{
-  string _root =
-    slave::paths::getProvisionerDir(flags.work_dir, Image::APPC);
-
-  Try<Nothing> mkdir = os::mkdir(_root);
-  if (mkdir.isError()) {
-    return Error("Failed to create provisioner root directory '" +
-                 _root + "': " + mkdir.error());
-  }
-
-  Result<string> root = os::realpath(_root);
-  if (root.isError()) {
-    return Error(
-        "Failed to resolve the realpath of provisioner root directory '" +
-        _root + "': " + root.error());
-  }
-
-  CHECK_SOME(root); // Can't be None since we just created it.
-
-  Try<Owned<Store>> store = Store::create(flags);
-  if (store.isError()) {
-    return Error("Failed to create image store: " + store.error());
-  }
-
-  hashmap<string, Owned<Backend>> backends = Backend::create(flags);
-  if (backends.empty()) {
-    return Error("No usable provisioner backend created");
-  }
-
-  if (!backends.contains(flags.appc_provisioner_backend)) {
-    return Error("The specified provisioner backend '" +
-                 flags.appc_provisioner_backend + "'is unsupported");
-  }
-
-  return Owned<Provisioner>(new AppcProvisioner(
-      Owned<AppcProvisionerProcess>(new AppcProvisionerProcess(
-          flags,
-          root.get(),
-          store.get(),
-          backends))));
-}
-
-
-AppcProvisioner::AppcProvisioner(Owned<AppcProvisionerProcess> _process)
-  : process(_process)
-{
-  spawn(CHECK_NOTNULL(process.get()));
-}
-
-
-AppcProvisioner::~AppcProvisioner()
-{
-  terminate(process.get());
-  wait(process.get());
-}
-
-
-Future<Nothing> AppcProvisioner::recover(
-    const list<ContainerState>& states,
-    const hashset<ContainerID>& orphans)
-{
-  return dispatch(
-      process.get(),
-      &AppcProvisionerProcess::recover,
-      states,
-      orphans);
-}
-
-
-Future<string> AppcProvisioner::provision(
-    const ContainerID& containerId,
-    const Image& image)
-{
-  return dispatch(
-      process.get(),
-      &AppcProvisionerProcess::provision,
-      containerId,
-      image);
-}
-
-
-Future<bool> AppcProvisioner::destroy(const ContainerID& containerId)
-{
-  return dispatch(
-      process.get(),
-      &AppcProvisionerProcess::destroy,
-      containerId);
-}
-
-
-AppcProvisionerProcess::AppcProvisionerProcess(
-    const Flags& _flags,
-    const string& _root,
-    const Owned<Store>& _store,
-    const hashmap<string, Owned<Backend>>& _backends)
-  : flags(_flags),
-    root(_root),
-    store(_store),
-    backends(_backends) {}
-
-
-Future<Nothing> AppcProvisionerProcess::recover(
-    const list<ContainerState>& states,
-    const hashset<ContainerID>& orphans)
-{
-  // Register living containers, including the ones that do not
-  // provision Appc images.
-  hashset<ContainerID> alive;
-  foreach (const ContainerState& state, states) {
-    alive.insert(state.container_id());
-  }
-
-  // List provisioned containers; recover living ones; destroy unknown orphans.
-  // Note that known orphan containers are recovered as well and they will
-  // be destroyed by the containerizer using the normal cleanup path. See
-  // MESOS-2367 for details.
-  Try<hashmap<ContainerID, string>> containers =
-    provisioners::paths::listContainers(root);
-
-  if (containers.isError()) {
-    return Failure("Failed to list the containers managed by Appc "
-                   "provisioner: " + containers.error());
-  }
-
-  // Scan the list of containers, register all of them with 'infos' but
-  // mark unknown orphans for immediate cleanup.
-  hashset<ContainerID> unknownOrphans;
-  foreachkey (const ContainerID& containerId, containers.get()) {
-    Owned<Info> info = Owned<Info>(new Info());
-
-    Try<hashmap<string, hashmap<string, string>>> rootfses =
-      provisioners::paths::listContainerRootfses(root, containerId);
-
-    if (rootfses.isError()) {
-      return Failure("Unable to list rootfses belonged to container '" +
-                     containerId.value() + "': " + rootfses.error());
-    }
-
-    foreachkey (const string& backend, rootfses.get()) {
-      if (!backends.contains(backend)) {
-        return Failure("Found rootfses managed by an unrecognized backend: " +
-                       backend);
-      }
-
-      info->rootfses.put(backend, rootfses.get()[backend]);
-    }
-
-    infos.put(containerId, info);
-
-    if (alive.contains(containerId) || orphans.contains(containerId)) {
-      VLOG(1) << "Recovered container " << containerId;
-      continue;
-    } else {
-      // For immediate cleanup below.
-      unknownOrphans.insert(containerId);
-    }
-  }
-
-  LOG(INFO)
-    << "Recovered living and known orphan containers for Appc provisioner";
-
-  // Destroy unknown orphan containers' rootfses.
-  list<Future<bool>> destroys;
-  foreach (const ContainerID& containerId, unknownOrphans) {
-    destroys.push_back(destroy(containerId));
-  }
-
-  Future<Nothing> cleanup = collect(destroys)
-    .then([]() -> Future<Nothing> {
-      LOG(INFO) << "Cleaned up unknown orphan containers for Appc provisioner";
-      return Nothing();
-    });
-
-  Future<Nothing> recover = store->recover()
-    .then([]() -> Future<Nothing> {
-      LOG(INFO) << "Recovered Appc image store";
-      return Nothing();
-    });
-
-
-  // A successful provisioner recovery depends on:
-  // 1) Recovery of living containers and known orphans (done above).
-  // 2) Successful cleanup of unknown orphans.
-  // 3) Successful store recovery.
-  return collect(cleanup, recover)
-    .then([=]() -> Future<Nothing> {
-      return Nothing();
-    });
-}
-
-
-Future<string> AppcProvisionerProcess::provision(
-    const ContainerID& containerId,
-    const Image& image)
-{
-  if (image.type() != Image::APPC) {
-    return Failure("Unsupported container image type: " +
-                   stringify(image.type()));
-  }
-
-  if (!image.has_appc()) {
-    return Failure("Missing Appc image info");
-  }
-
-  string rootfsId = UUID::random().toString();
-  string rootfs = provisioners::paths::getContainerRootfsDir(
-      root, containerId, flags.appc_provisioner_backend, rootfsId);
-
-  if (!infos.contains(containerId)) {
-    infos.put(containerId, Owned<Info>(new Info()));
-  }
-
-  infos[containerId]->rootfses[flags.appc_provisioner_backend].put(
-      rootfsId, rootfs);
-
-  // Get and then provision image layers from the store.
-  return store->get(image.appc())
-    .then(defer(self(), &Self::_provision, lambda::_1, rootfs));
-}
-
-
-Future<string> AppcProvisionerProcess::_provision(
-     const vector<string>& layers,
-     const string& rootfs)
-{
-  LOG(INFO) << "Provisioning image layers to rootfs '" << rootfs << "'";
-
-  CHECK(backends.contains(flags.appc_provisioner_backend));
-  return backends.get(flags.appc_provisioner_backend).get()->provision(
-      layers,
-      rootfs)
-    .then([rootfs]() -> Future<string> { return rootfs; });
-}
-
-
-Future<bool> AppcProvisionerProcess::destroy(const ContainerID& containerId)
-{
-  if (!infos.contains(containerId)) {
-    LOG(INFO) << "Ignoring destroy request for unknown container: "
-              << containerId;
-
-    return false;
-  }
-
-  // Unregister the container first. If destroy() fails, we can rely
-  // on recover() to retry it later.
-  Owned<Info> info = infos[containerId];
-  infos.erase(containerId);
-
-  list<Future<bool>> futures;
-  foreachkey (const string& backend, info->rootfses) {
-    foreachvalue (const string& rootfs, info->rootfses[backend]) {
-      if (!backends.contains(backend)) {
-        return Failure("Cannot destroy rootfs '" + rootfs +
-                       "' provisioned by an unknown backend '" + backend + "'");
-      }
-
-      LOG(INFO) << "Destroying container rootfs for container '"
-                << containerId << "' at '" << rootfs << "'";
-
-      futures.push_back(
-          backends.get(backend).get()->destroy(rootfs));
-    }
-  }
-
-  // NOTE: We calculate 'containerDir' here so that the following
-  // lambda does not need to bind 'this'.
-  string containerDir =
-    provisioners::paths::getContainerDir(root, containerId);
-
-  // TODO(xujyan): Revisit the usefulness of this return value.
-  return collect(futures)
-    .then([containerDir]() -> Future<bool> {
-      // This should be fairly cheap as the directory should only
-      // contain a few empty sub-directories at this point.
-      //
-      // TODO(jieyu): Currently, it's possible that some directories
-      // cannot be removed due to EBUSY. EBUSY is caused by the race
-      // between cleaning up this container and new containers copying
-      // the host mount table. It's OK to ignore them. The cleanup
-      // will be retried during slave recovery.
-      Try<Nothing> rmdir = os::rmdir(containerDir);
-      if (rmdir.isError()) {
-        LOG(ERROR) << "Failed to remove the provisioned container directory "
-                   << "at '" << containerDir << "'";
-      }
-
-      return true;
-    });
-}
-
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/provisioner.hpp b/src/slave/containerizer/provisioners/appc/provisioner.hpp
deleted file mode 100644
index 764b119..0000000
--- a/src/slave/containerizer/provisioners/appc/provisioner.hpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * 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 __APPC_PROVISIONER_HPP__
-#define __APPC_PROVISIONER_HPP__
-
-#include <list>
-#include <string>
-#include <vector>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-
-#include <stout/hashmap.hpp>
-#include <stout/json.hpp>
-#include <stout/nothing.hpp>
-#include <stout/try.hpp>
-
-#include "slave/containerizer/provisioner.hpp"
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-
-// Forward declaration.
-class AppcProvisionerProcess;
-
-
-class AppcProvisioner : public Provisioner
-{
-public:
-  static Try<process::Owned<Provisioner>> create(
-      const Flags& flags,
-      Fetcher* fetcher);
-
-  ~AppcProvisioner();
-
-  virtual process::Future<Nothing> recover(
-      const std::list<mesos::slave::ContainerState>& states,
-      const hashset<ContainerID>& orphans);
-
-  virtual process::Future<std::string> provision(
-      const ContainerID& containerId,
-      const Image& image);
-
-  virtual process::Future<bool> destroy(const ContainerID& containerId);
-
-private:
-  explicit AppcProvisioner(process::Owned<AppcProvisionerProcess> process);
-
-  AppcProvisioner(const AppcProvisioner&); // Not copyable.
-  AppcProvisioner& operator=(const AppcProvisioner&); // Not assignable.
-
-  process::Owned<AppcProvisionerProcess> process;
-};
-
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __APPC_PROVISIONER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/spec.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/spec.cpp b/src/slave/containerizer/provisioners/appc/spec.cpp
deleted file mode 100644
index 15a3257..0000000
--- a/src/slave/containerizer/provisioners/appc/spec.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * 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 <stout/os/stat.hpp>
-#include <stout/protobuf.hpp>
-#include <stout/strings.hpp>
-
-#include "slave/containerizer/provisioners/appc/paths.hpp"
-#include "slave/containerizer/provisioners/appc/spec.hpp"
-
-using std::string;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-namespace spec {
-
-Option<Error> validateManifest(const AppcImageManifest& manifest)
-{
-  // TODO(idownes): Validate that required fields are present when
-  // this cannot be expressed in the protobuf specification, e.g.,
-  // repeated fields with >= 1.
-  // TODO(xujyan): More thorough type validation:
-  // https://github.com/appc/spec/blob/master/spec/types.md
-  if (manifest.ackind() != "ImageManifest") {
-    return Error("Incorrect acKind field: " + manifest.ackind());
-  }
-
-  return None();
-}
-
-
-Option<Error> validateImageID(const string& imageId)
-{
-  if (!strings::startsWith(imageId, "sha512-")) {
-    return Error("Image ID needs to start with sha512-");
-  }
-
-  string hash = strings::remove(imageId, "sha512-", strings::PREFIX);
-  if (hash.length() != 128) {
-    return Error("Invalid hash length for: " + hash);
-  }
-
-  return None();
-}
-
-
-Option<Error> validateLayout(const string& imagePath)
-{
-  if (!os::stat::isdir(paths::getImageRootfsPath(imagePath))) {
-    return Error("No rootfs directory found in image layout");
-  }
-
-  if (!os::stat::isfile(paths::getImageManifestPath(imagePath))) {
-    return Error("No manifest found in image layout");
-  }
-
-  return None();
-}
-
-
-Try<AppcImageManifest> parse(const string& value)
-{
-  Try<JSON::Object> json = JSON::parse<JSON::Object>(value);
-  if (json.isError()) {
-    return Error("JSON parse failed: " + json.error());
-  }
-
-  Try<AppcImageManifest> manifest =
-    protobuf::parse<AppcImageManifest>(json.get());
-
-  if (manifest.isError()) {
-    return Error("Protobuf parse failed: " + manifest.error());
-  }
-
-  Option<Error> error = validateManifest(manifest.get());
-  if (error.isSome()) {
-    return Error("Schema validation failed: " + error.get().message);
-  }
-
-  return manifest.get();
-}
-
-} // namespace spec {
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioners/appc/spec.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioners/appc/spec.hpp b/src/slave/containerizer/provisioners/appc/spec.hpp
deleted file mode 100644
index 63c7930..0000000
--- a/src/slave/containerizer/provisioners/appc/spec.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * 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 __MESOS_APPC_SPEC_HPP__
-#define __MESOS_APPC_SPEC_HPP__
-
-#include <string>
-
-#include <stout/error.hpp>
-#include <stout/option.hpp>
-
-#include <mesos/mesos.hpp>
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-namespace spec {
-
-// Validate if the specified image manifest conforms to the Appc spec.
-Option<Error> validateManifest(const AppcImageManifest& manifest);
-
-// Validate if the specified image ID conforms to the Appc spec.
-Option<Error> validateImageID(const std::string& imageId);
-
-// Validate if the specified image has the disk layout that conforms
-// to the Appc spec.
-Option<Error> validateLayout(const std::string& imagePath);
-
-// Parse the AppcImageManifest in the specified JSON string.
-Try<AppcImageManifest> parse(const std::string& value);
-
-} // namespace spec {
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_APPC_SPEC_HPP__


[4/6] mesos git commit: Moved files to prepare for unifying provisioners.

Posted by ji...@apache.org.
Moved files to prepare for unifying provisioners.

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


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

Branch: refs/heads/master
Commit: cc1f8f54ec1373e500c4a4b25b92a455d2a27a83
Parents: c68c3bd
Author: Jie Yu <yu...@gmail.com>
Authored: Tue Sep 15 11:01:20 2015 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Sep 16 15:45:49 2015 -0700

----------------------------------------------------------------------
 src/Makefile.am                                 |  50 +-
 .../isolators/filesystem/linux.hpp              |   3 +-
 src/slave/containerizer/mesos/containerizer.cpp |   3 +-
 src/slave/containerizer/provisioner.cpp         |  79 ---
 src/slave/containerizer/provisioner.hpp         |  79 ---
 .../containerizer/provisioner/appc/paths.cpp    |  85 +++
 .../containerizer/provisioner/appc/paths.hpp    |  83 +++
 .../provisioner/appc/provisioner.cpp            | 397 +++++++++++
 .../provisioner/appc/provisioner.hpp            |  78 +++
 .../containerizer/provisioner/appc/spec.cpp     | 104 +++
 .../containerizer/provisioner/appc/spec.hpp     |  54 ++
 .../containerizer/provisioner/appc/store.cpp    | 280 ++++++++
 .../containerizer/provisioner/appc/store.hpp    |  90 +++
 src/slave/containerizer/provisioner/backend.cpp |  62 ++
 src/slave/containerizer/provisioner/backend.hpp |  67 ++
 .../containerizer/provisioner/backends/bind.cpp | 197 ++++++
 .../containerizer/provisioner/backends/bind.hpp |  75 ++
 .../containerizer/provisioner/backends/copy.cpp | 203 ++++++
 .../containerizer/provisioner/backends/copy.hpp |  61 ++
 .../provisioner/docker/registry_client.cpp      | 575 ++++++++++++++++
 .../provisioner/docker/registry_client.hpp      | 163 +++++
 .../provisioner/docker/token_manager.cpp        | 361 ++++++++++
 .../provisioner/docker/token_manager.hpp        | 179 +++++
 src/slave/containerizer/provisioner/paths.cpp   | 192 ++++++
 src/slave/containerizer/provisioner/paths.hpp   |  83 +++
 .../containerizer/provisioner/provisioner.cpp   |  79 +++
 .../containerizer/provisioner/provisioner.hpp   |  79 +++
 .../containerizer/provisioners/appc/paths.cpp   |  85 ---
 .../containerizer/provisioners/appc/paths.hpp   |  83 ---
 .../provisioners/appc/provisioner.cpp           | 397 -----------
 .../provisioners/appc/provisioner.hpp           |  78 ---
 .../containerizer/provisioners/appc/spec.cpp    | 104 ---
 .../containerizer/provisioners/appc/spec.hpp    |  54 --
 .../containerizer/provisioners/appc/store.cpp   | 280 --------
 .../containerizer/provisioners/appc/store.hpp   |  90 ---
 .../containerizer/provisioners/backend.cpp      |  62 --
 .../containerizer/provisioners/backend.hpp      |  67 --
 .../provisioners/backends/bind.cpp              | 197 ------
 .../provisioners/backends/bind.hpp              |  75 --
 .../provisioners/backends/copy.cpp              | 203 ------
 .../provisioners/backends/copy.hpp              |  61 --
 .../provisioners/docker/registry_client.cpp     | 575 ----------------
 .../provisioners/docker/registry_client.hpp     | 163 -----
 .../provisioners/docker/token_manager.cpp       | 361 ----------
 .../provisioners/docker/token_manager.hpp       | 179 -----
 src/slave/containerizer/provisioners/paths.cpp  | 191 -----
 src/slave/containerizer/provisioners/paths.hpp  |  83 ---
 .../containerizer/appc_provisioner_tests.cpp    | 415 -----------
 .../containerizer/docker_provisioner_tests.cpp  | 688 -------------------
 src/tests/containerizer/provisioner.hpp         |   2 +-
 .../containerizer/provisioner_appc_tests.cpp    | 415 +++++++++++
 .../containerizer/provisioner_backend_tests.cpp |   4 +-
 .../containerizer/provisioner_docker_tests.cpp  | 688 +++++++++++++++++++
 53 files changed, 4682 insertions(+), 4679 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index db2f8d9..60cb10d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -503,16 +503,16 @@ libmesos_no_3rdparty_la_SOURCES =					\
 	slave/containerizer/launcher.cpp				\
 	slave/containerizer/mesos/containerizer.cpp			\
 	slave/containerizer/mesos/launch.cpp				\
-	slave/containerizer/provisioner.cpp				\
-        slave/containerizer/provisioners/paths.cpp                      \
-	slave/containerizer/provisioners/appc/paths.cpp			\
-	slave/containerizer/provisioners/appc/provisioner.cpp		\
-	slave/containerizer/provisioners/appc/spec.cpp			\
-	slave/containerizer/provisioners/appc/store.cpp			\
-	slave/containerizer/provisioners/backend.cpp			\
-	slave/containerizer/provisioners/backends/copy.cpp              \
-	slave/containerizer/provisioners/docker/registry_client.cpp	\
-	slave/containerizer/provisioners/docker/token_manager.cpp	\
+        slave/containerizer/provisioner/paths.cpp			\
+	slave/containerizer/provisioner/provisioner.cpp			\
+	slave/containerizer/provisioner/appc/paths.cpp			\
+	slave/containerizer/provisioner/appc/provisioner.cpp		\
+	slave/containerizer/provisioner/appc/spec.cpp			\
+	slave/containerizer/provisioner/appc/store.cpp			\
+	slave/containerizer/provisioner/backend.cpp			\
+	slave/containerizer/provisioner/backends/copy.cpp		\
+	slave/containerizer/provisioner/docker/registry_client.cpp	\
+	slave/containerizer/provisioner/docker/token_manager.cpp	\
 	slave/resource_estimators/noop.cpp				\
 	usage/usage.cpp							\
 	v1/attributes.cpp						\
@@ -680,7 +680,7 @@ if OS_LINUX
   libmesos_no_3rdparty_la_SOURCES += slave/containerizer/isolators/filesystem/linux.cpp
   libmesos_no_3rdparty_la_SOURCES += slave/containerizer/isolators/filesystem/shared.cpp
   libmesos_no_3rdparty_la_SOURCES += slave/containerizer/linux_launcher.cpp
-  libmesos_no_3rdparty_la_SOURCES += slave/containerizer/provisioners/backends/bind.cpp
+  libmesos_no_3rdparty_la_SOURCES += slave/containerizer/provisioner/backends/bind.cpp
 else
   EXTRA_DIST += linux/cgroups.cpp
   EXTRA_DIST += linux/fs.cpp
@@ -794,17 +794,17 @@ libmesos_no_3rdparty_la_SOURCES +=					\
 	slave/containerizer/isolator.hpp				\
 	slave/containerizer/launcher.hpp				\
 	slave/containerizer/linux_launcher.hpp				\
-	slave/containerizer/provisioner.hpp				\
-        slave/containerizer/provisioners/paths.hpp                      \
-	slave/containerizer/provisioners/appc/paths.hpp			\
-	slave/containerizer/provisioners/appc/provisioner.hpp		\
-	slave/containerizer/provisioners/appc/spec.hpp			\
-	slave/containerizer/provisioners/appc/store.hpp			\
-	slave/containerizer/provisioners/backend.hpp			\
-	slave/containerizer/provisioners/backends/bind.hpp		\
-	slave/containerizer/provisioners/backends/copy.hpp		\
-	slave/containerizer/provisioners/docker/registry_client.hpp	\
-	slave/containerizer/provisioners/docker/token_manager.hpp	\
+        slave/containerizer/provisioner/paths.hpp			\
+	slave/containerizer/provisioner/provisioner.hpp			\
+	slave/containerizer/provisioner/appc/paths.hpp			\
+	slave/containerizer/provisioner/appc/provisioner.hpp		\
+	slave/containerizer/provisioner/appc/spec.hpp			\
+	slave/containerizer/provisioner/appc/store.hpp			\
+	slave/containerizer/provisioner/backend.hpp			\
+	slave/containerizer/provisioner/backends/bind.hpp		\
+	slave/containerizer/provisioner/backends/copy.hpp		\
+	slave/containerizer/provisioner/docker/registry_client.hpp	\
+	slave/containerizer/provisioner/docker/token_manager.hpp	\
 	slave/containerizer/isolators/posix.hpp				\
 	slave/containerizer/isolators/posix/disk.hpp			\
 	slave/containerizer/isolators/cgroups/constants.hpp		\
@@ -1719,16 +1719,16 @@ mesos_tests_SOURCES =						\
   tests/zookeeper_url_tests.cpp					\
   tests/common/http_tests.cpp					\
   tests/common/recordio_tests.cpp				\
-  tests/containerizer/appc_provisioner_tests.cpp		\
   tests/containerizer/composing_containerizer_tests.cpp		\
   tests/containerizer/docker_containerizer_tests.cpp		\
   tests/containerizer/docker_tests.cpp				\
-  tests/containerizer/docker_provisioner_tests.cpp	        \
   tests/containerizer/external_containerizer_test.cpp		\
   tests/containerizer/isolator_tests.cpp			\
   tests/containerizer/memory_test_helper.cpp			\
   tests/containerizer/mesos_containerizer_tests.cpp		\
-  tests/containerizer/provisioner_backend_tests.cpp
+  tests/containerizer/provisioner_appc_tests.cpp		\
+  tests/containerizer/provisioner_backend_tests.cpp		\
+  tests/containerizer/provisioner_docker_tests.cpp
 
 mesos_tests_CPPFLAGS = $(MESOS_CPPFLAGS)
 mesos_tests_CPPFLAGS += -DSOURCE_DIR=\"$(abs_top_srcdir)\"

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/isolators/filesystem/linux.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/isolators/filesystem/linux.hpp b/src/slave/containerizer/isolators/filesystem/linux.hpp
index 6cfe9fa..ff76c89 100644
--- a/src/slave/containerizer/isolators/filesystem/linux.hpp
+++ b/src/slave/containerizer/isolators/filesystem/linux.hpp
@@ -29,7 +29,8 @@
 #include "slave/flags.hpp"
 
 #include "slave/containerizer/isolator.hpp"
-#include "slave/containerizer/provisioner.hpp"
+
+#include "slave/containerizer/provisioner/provisioner.hpp"
 
 namespace mesos {
 namespace internal {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/mesos/containerizer.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/containerizer.cpp b/src/slave/containerizer/mesos/containerizer.cpp
index 1b83a87..6ab4c08 100644
--- a/src/slave/containerizer/mesos/containerizer.cpp
+++ b/src/slave/containerizer/mesos/containerizer.cpp
@@ -46,7 +46,6 @@
 #ifdef __linux__
 #include "slave/containerizer/linux_launcher.hpp"
 #endif
-#include "slave/containerizer/provisioner.hpp"
 
 #include "slave/containerizer/isolators/posix.hpp"
 
@@ -77,6 +76,8 @@
 #include "slave/containerizer/mesos/containerizer.hpp"
 #include "slave/containerizer/mesos/launch.hpp"
 
+#include "slave/containerizer/provisioner/provisioner.hpp"
+
 using std::list;
 using std::map;
 using std::string;

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner.cpp b/src/slave/containerizer/provisioner.cpp
deleted file mode 100644
index 2ac9008..0000000
--- a/src/slave/containerizer/provisioner.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * 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 <stout/hashset.hpp>
-#include <stout/stringify.hpp>
-#include <stout/strings.hpp>
-
-#include "slave/containerizer/provisioner.hpp"
-
-#include "slave/containerizer/provisioners/appc/provisioner.hpp"
-
-using namespace process;
-
-using std::string;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-Try<hashmap<Image::Type, Owned<Provisioner>>> Provisioner::create(
-    const Flags& flags,
-    Fetcher* fetcher)
-{
-  if (flags.provisioners.isNone()) {
-    return hashmap<Image::Type, Owned<Provisioner>>();
-  }
-
-  hashmap<Image::Type,
-          Try<Owned<Provisioner>>(*)(const Flags&, Fetcher*)> creators;
-
-  // Register all supported creators.
-  creators.put(Image::APPC, &appc::AppcProvisioner::create);
-
-  hashmap<Image::Type, Owned<Provisioner>> provisioners;
-
-  // NOTE: Change in '--provisioners' flag may result in leaked rootfs
-  // files on the disk but it's at least safe because files managed by
-  // different provisioners are totally separated.
-  foreach (const string& type,
-           strings::tokenize(flags.provisioners.get(), ",")) {
-     Image::Type imageType;
-     if (!Image::Type_Parse(strings::upper(type), &imageType)) {
-       return Error("Unknown provisioner '" + type + "'");
-     }
-
-     if (!creators.contains(imageType)) {
-       return Error("Unsupported provisioner '" + type + "'");
-     }
-
-     Try<Owned<Provisioner>> provisioner = creators[imageType](flags, fetcher);
-     if (provisioner.isError()) {
-       return Error("Failed to create '" + stringify(imageType) +
-                    "' provisioner: " + provisioner.error());
-     }
-
-     provisioners[imageType] = provisioner.get();
-  }
-
-  return provisioners;
-}
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner.hpp b/src/slave/containerizer/provisioner.hpp
deleted file mode 100644
index 9e0e0b8..0000000
--- a/src/slave/containerizer/provisioner.hpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * 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 __MESOS_PROVISIONER_HPP__
-#define __MESOS_PROVISIONER_HPP__
-
-#include <list>
-
-#include <mesos/resources.hpp>
-
-#include <mesos/slave/isolator.hpp> // For ContainerState.
-
-#include <stout/hashmap.hpp>
-#include <stout/nothing.hpp>
-#include <stout/try.hpp>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-
-#include "slave/flags.hpp"
-
-#include "slave/containerizer/fetcher.hpp"
-
-namespace mesos {
-namespace internal {
-namespace slave {
-
-class Provisioner
-{
-public:
-  virtual ~Provisioner() {}
-
-  // Create provisioners based on specified flags. An error is returned if
-  // any of the provisioners specified in --provisioner failed to be created.
-  static Try<hashmap<Image::Type, process::Owned<Provisioner>>>
-    create(const Flags& flags, Fetcher* fetcher);
-
-  // Recover root filesystems for containers from the run states and
-  // the orphan containers (known to the launcher but not known to the
-  // slave) detected by the launcher. This function is also
-  // responsible for cleaning up any intermediate artifacts (e.g.
-  // directories) to not leak anything.
-  virtual process::Future<Nothing> recover(
-      const std::list<mesos::slave::ContainerState>& states,
-      const hashset<ContainerID>& orphans) = 0;
-
-  // Provision a root filesystem for the container using the specified
-  // image and return the absolute path to the root filesystem.
-  virtual process::Future<std::string> provision(
-      const ContainerID& containerId,
-      const Image& image) = 0;
-
-  // Destroy a previously provisioned root filesystem. Assumes that
-  // all references (e.g., mounts, open files) to the provisioned
-  // filesystem have been removed. Return false if there is no
-  // provisioned root filesystem for the given container.
-  virtual process::Future<bool> destroy(const ContainerID& containerId) = 0;
-};
-
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MESOS_PROVISIONER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/paths.cpp b/src/slave/containerizer/provisioner/appc/paths.cpp
new file mode 100644
index 0000000..8817c0f
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/paths.cpp
@@ -0,0 +1,85 @@
+/**
+ * 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 <list>
+
+#include <glog/logging.h>
+
+#include <stout/path.hpp>
+
+#include "slave/containerizer/provisioner/appc/paths.hpp"
+
+using std::list;
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+namespace paths {
+
+string getStagingDir(const string& storeDir)
+{
+  return path::join(storeDir, "staging");
+}
+
+
+string getImagesDir(const string& storeDir)
+{
+  return path::join(storeDir, "images");
+}
+
+
+string getImagePath(const string& storeDir, const string& imageId)
+{
+  return path::join(getImagesDir(storeDir), imageId);
+}
+
+
+string getImageRootfsPath(
+    const string& storeDir,
+    const string& imageId)
+{
+  return path::join(getImagePath(storeDir, imageId), "rootfs");
+}
+
+
+string getImageRootfsPath(const string& imagePath)
+{
+  return path::join(imagePath, "rootfs");
+}
+
+
+string getImageManifestPath(
+    const string& storeDir,
+    const string& imageId)
+{
+  return path::join(getImagePath(storeDir, imageId), "manifest");
+}
+
+
+string getImageManifestPath(const string& imagePath)
+{
+  return path::join(imagePath, "manifest");
+}
+
+} // namespace paths {
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/paths.hpp b/src/slave/containerizer/provisioner/appc/paths.hpp
new file mode 100644
index 0000000..7c36d67
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/paths.hpp
@@ -0,0 +1,83 @@
+/**
+ * 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 __PROVISIONER_APPC_PATHS_HPP__
+#define __PROVISIONER_APPC_PATHS_HPP__
+
+#include <string>
+
+#include <mesos/mesos.hpp>
+
+#include <stout/hashmap.hpp>
+#include <stout/try.hpp>
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+namespace paths {
+
+// The appc store file system layout is as follows:
+//
+// <store_dir> ('--appc_store_dir' flag)
+// |--staging (contains temp directories for staging downloads)
+// |
+// |--images (stores validated images)
+//    |--<image_id> (in the form of "sha512-<128_character_hash_sum>")
+//       |--manifest
+//       |--rootfs
+//          |--... (according to the ACI spec)
+//
+// TODO(xujyan): The staging directory is unused for now (it's
+// externally managed) but implemented to illustrate the need for a
+// separate 'images' directory. Complete the layout diagram when the
+// staging directory is utilized by the provisioner.
+
+std::string getStagingDir(const std::string& storeDir);
+
+
+std::string getImagesDir(const std::string& storeDir);
+
+
+std::string getImagePath(
+    const std::string& storeDir,
+    const std::string& imageId);
+
+
+std::string getImageRootfsPath(
+    const std::string& storeDir,
+    const std::string& imageId);
+
+
+std::string getImageRootfsPath(const std::string& imagePath);
+
+
+std::string getImageManifestPath(
+    const std::string& storeDir,
+    const std::string& imageId);
+
+
+std::string getImageManifestPath(const std::string& imagePath);
+
+} // namespace paths {
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_APPC_PATHS_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/provisioner.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/provisioner.cpp b/src/slave/containerizer/provisioner/appc/provisioner.cpp
new file mode 100644
index 0000000..929e42a
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/provisioner.cpp
@@ -0,0 +1,397 @@
+/**
+ * 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 <mesos/type_utils.hpp>
+
+#include <process/collect.hpp>
+#include <process/defer.hpp>
+#include <process/dispatch.hpp>
+#include <process/process.hpp>
+
+#include <stout/foreach.hpp>
+#include <stout/hashset.hpp>
+#include <stout/os.hpp>
+#include <stout/stringify.hpp>
+#include <stout/strings.hpp>
+#include <stout/uuid.hpp>
+
+#include "slave/containerizer/provisioner/backend.hpp"
+#include "slave/containerizer/provisioner/paths.hpp"
+
+#include "slave/containerizer/provisioner/appc/paths.hpp"
+#include "slave/containerizer/provisioner/appc/provisioner.hpp"
+#include "slave/containerizer/provisioner/appc/spec.hpp"
+#include "slave/containerizer/provisioner/appc/store.hpp"
+
+#include "slave/paths.hpp"
+
+using namespace process;
+using namespace mesos::internal::slave;
+
+using std::list;
+using std::string;
+using std::vector;
+
+using mesos::slave::ContainerState;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+
+class AppcProvisionerProcess : public Process<AppcProvisionerProcess>
+{
+public:
+  AppcProvisionerProcess(
+      const Flags& flags,
+      const string& root,
+      const Owned<Store>& store,
+      const hashmap<string, Owned<Backend>>& backends);
+
+  Future<Nothing> recover(
+      const list<ContainerState>& states,
+      const hashset<ContainerID>& orphans);
+
+  Future<string> provision(const ContainerID& containerId, const Image& image);
+
+  Future<bool> destroy(const ContainerID& containerId);
+
+private:
+  Future<string> _provision(const vector<string>& layers, const string& rootfs);
+
+  const Flags flags;
+
+  // Absolute path to the Appc provisioner root directory. It can be derived
+  // from '--work_dir' but we keep a separate copy here because we converted
+  // it into an absolute path so managed rootfs paths match the ones in
+  // 'mountinfo' (important if mount-based backends are used).
+  const string root;
+
+  const Owned<Store> store;
+  const hashmap<string, Owned<Backend>> backends;
+
+  struct Info
+  {
+    // Mappings: backend -> rootfsId -> rootfsPath.
+    hashmap<string, hashmap<string, string>> rootfses;
+  };
+
+  hashmap<ContainerID, Owned<Info>> infos;
+};
+
+
+// NOTE: Successful creation of the provisioner means its managed
+// directory under --work_dir is also created.
+Try<Owned<Provisioner>> AppcProvisioner::create(
+    const Flags& flags,
+    Fetcher* fetcher)
+{
+  string _root =
+    slave::paths::getProvisionerDir(flags.work_dir, Image::APPC);
+
+  Try<Nothing> mkdir = os::mkdir(_root);
+  if (mkdir.isError()) {
+    return Error("Failed to create provisioner root directory '" +
+                 _root + "': " + mkdir.error());
+  }
+
+  Result<string> root = os::realpath(_root);
+  if (root.isError()) {
+    return Error(
+        "Failed to resolve the realpath of provisioner root directory '" +
+        _root + "': " + root.error());
+  }
+
+  CHECK_SOME(root); // Can't be None since we just created it.
+
+  Try<Owned<Store>> store = Store::create(flags);
+  if (store.isError()) {
+    return Error("Failed to create image store: " + store.error());
+  }
+
+  hashmap<string, Owned<Backend>> backends = Backend::create(flags);
+  if (backends.empty()) {
+    return Error("No usable provisioner backend created");
+  }
+
+  if (!backends.contains(flags.appc_provisioner_backend)) {
+    return Error("The specified provisioner backend '" +
+                 flags.appc_provisioner_backend + "'is unsupported");
+  }
+
+  return Owned<Provisioner>(new AppcProvisioner(
+      Owned<AppcProvisionerProcess>(new AppcProvisionerProcess(
+          flags,
+          root.get(),
+          store.get(),
+          backends))));
+}
+
+
+AppcProvisioner::AppcProvisioner(Owned<AppcProvisionerProcess> _process)
+  : process(_process)
+{
+  spawn(CHECK_NOTNULL(process.get()));
+}
+
+
+AppcProvisioner::~AppcProvisioner()
+{
+  terminate(process.get());
+  wait(process.get());
+}
+
+
+Future<Nothing> AppcProvisioner::recover(
+    const list<ContainerState>& states,
+    const hashset<ContainerID>& orphans)
+{
+  return dispatch(
+      process.get(),
+      &AppcProvisionerProcess::recover,
+      states,
+      orphans);
+}
+
+
+Future<string> AppcProvisioner::provision(
+    const ContainerID& containerId,
+    const Image& image)
+{
+  return dispatch(
+      process.get(),
+      &AppcProvisionerProcess::provision,
+      containerId,
+      image);
+}
+
+
+Future<bool> AppcProvisioner::destroy(const ContainerID& containerId)
+{
+  return dispatch(
+      process.get(),
+      &AppcProvisionerProcess::destroy,
+      containerId);
+}
+
+
+AppcProvisionerProcess::AppcProvisionerProcess(
+    const Flags& _flags,
+    const string& _root,
+    const Owned<Store>& _store,
+    const hashmap<string, Owned<Backend>>& _backends)
+  : flags(_flags),
+    root(_root),
+    store(_store),
+    backends(_backends) {}
+
+
+Future<Nothing> AppcProvisionerProcess::recover(
+    const list<ContainerState>& states,
+    const hashset<ContainerID>& orphans)
+{
+  // Register living containers, including the ones that do not
+  // provision Appc images.
+  hashset<ContainerID> alive;
+  foreach (const ContainerState& state, states) {
+    alive.insert(state.container_id());
+  }
+
+  // List provisioned containers; recover living ones; destroy unknown orphans.
+  // Note that known orphan containers are recovered as well and they will
+  // be destroyed by the containerizer using the normal cleanup path. See
+  // MESOS-2367 for details.
+  Try<hashmap<ContainerID, string>> containers =
+    provisioners::paths::listContainers(root);
+
+  if (containers.isError()) {
+    return Failure("Failed to list the containers managed by Appc "
+                   "provisioner: " + containers.error());
+  }
+
+  // Scan the list of containers, register all of them with 'infos' but
+  // mark unknown orphans for immediate cleanup.
+  hashset<ContainerID> unknownOrphans;
+  foreachkey (const ContainerID& containerId, containers.get()) {
+    Owned<Info> info = Owned<Info>(new Info());
+
+    Try<hashmap<string, hashmap<string, string>>> rootfses =
+      provisioners::paths::listContainerRootfses(root, containerId);
+
+    if (rootfses.isError()) {
+      return Failure("Unable to list rootfses belonged to container '" +
+                     containerId.value() + "': " + rootfses.error());
+    }
+
+    foreachkey (const string& backend, rootfses.get()) {
+      if (!backends.contains(backend)) {
+        return Failure("Found rootfses managed by an unrecognized backend: " +
+                       backend);
+      }
+
+      info->rootfses.put(backend, rootfses.get()[backend]);
+    }
+
+    infos.put(containerId, info);
+
+    if (alive.contains(containerId) || orphans.contains(containerId)) {
+      VLOG(1) << "Recovered container " << containerId;
+      continue;
+    } else {
+      // For immediate cleanup below.
+      unknownOrphans.insert(containerId);
+    }
+  }
+
+  LOG(INFO)
+    << "Recovered living and known orphan containers for Appc provisioner";
+
+  // Destroy unknown orphan containers' rootfses.
+  list<Future<bool>> destroys;
+  foreach (const ContainerID& containerId, unknownOrphans) {
+    destroys.push_back(destroy(containerId));
+  }
+
+  Future<Nothing> cleanup = collect(destroys)
+    .then([]() -> Future<Nothing> {
+      LOG(INFO) << "Cleaned up unknown orphan containers for Appc provisioner";
+      return Nothing();
+    });
+
+  Future<Nothing> recover = store->recover()
+    .then([]() -> Future<Nothing> {
+      LOG(INFO) << "Recovered Appc image store";
+      return Nothing();
+    });
+
+
+  // A successful provisioner recovery depends on:
+  // 1) Recovery of living containers and known orphans (done above).
+  // 2) Successful cleanup of unknown orphans.
+  // 3) Successful store recovery.
+  return collect(cleanup, recover)
+    .then([=]() -> Future<Nothing> {
+      return Nothing();
+    });
+}
+
+
+Future<string> AppcProvisionerProcess::provision(
+    const ContainerID& containerId,
+    const Image& image)
+{
+  if (image.type() != Image::APPC) {
+    return Failure("Unsupported container image type: " +
+                   stringify(image.type()));
+  }
+
+  if (!image.has_appc()) {
+    return Failure("Missing Appc image info");
+  }
+
+  string rootfsId = UUID::random().toString();
+  string rootfs = provisioners::paths::getContainerRootfsDir(
+      root, containerId, flags.appc_provisioner_backend, rootfsId);
+
+  if (!infos.contains(containerId)) {
+    infos.put(containerId, Owned<Info>(new Info()));
+  }
+
+  infos[containerId]->rootfses[flags.appc_provisioner_backend].put(
+      rootfsId, rootfs);
+
+  // Get and then provision image layers from the store.
+  return store->get(image.appc())
+    .then(defer(self(), &Self::_provision, lambda::_1, rootfs));
+}
+
+
+Future<string> AppcProvisionerProcess::_provision(
+     const vector<string>& layers,
+     const string& rootfs)
+{
+  LOG(INFO) << "Provisioning image layers to rootfs '" << rootfs << "'";
+
+  CHECK(backends.contains(flags.appc_provisioner_backend));
+  return backends.get(flags.appc_provisioner_backend).get()->provision(
+      layers,
+      rootfs)
+    .then([rootfs]() -> Future<string> { return rootfs; });
+}
+
+
+Future<bool> AppcProvisionerProcess::destroy(const ContainerID& containerId)
+{
+  if (!infos.contains(containerId)) {
+    LOG(INFO) << "Ignoring destroy request for unknown container: "
+              << containerId;
+
+    return false;
+  }
+
+  // Unregister the container first. If destroy() fails, we can rely
+  // on recover() to retry it later.
+  Owned<Info> info = infos[containerId];
+  infos.erase(containerId);
+
+  list<Future<bool>> futures;
+  foreachkey (const string& backend, info->rootfses) {
+    foreachvalue (const string& rootfs, info->rootfses[backend]) {
+      if (!backends.contains(backend)) {
+        return Failure("Cannot destroy rootfs '" + rootfs +
+                       "' provisioned by an unknown backend '" + backend + "'");
+      }
+
+      LOG(INFO) << "Destroying container rootfs for container '"
+                << containerId << "' at '" << rootfs << "'";
+
+      futures.push_back(
+          backends.get(backend).get()->destroy(rootfs));
+    }
+  }
+
+  // NOTE: We calculate 'containerDir' here so that the following
+  // lambda does not need to bind 'this'.
+  string containerDir =
+    provisioners::paths::getContainerDir(root, containerId);
+
+  // TODO(xujyan): Revisit the usefulness of this return value.
+  return collect(futures)
+    .then([containerDir]() -> Future<bool> {
+      // This should be fairly cheap as the directory should only
+      // contain a few empty sub-directories at this point.
+      //
+      // TODO(jieyu): Currently, it's possible that some directories
+      // cannot be removed due to EBUSY. EBUSY is caused by the race
+      // between cleaning up this container and new containers copying
+      // the host mount table. It's OK to ignore them. The cleanup
+      // will be retried during slave recovery.
+      Try<Nothing> rmdir = os::rmdir(containerDir);
+      if (rmdir.isError()) {
+        LOG(ERROR) << "Failed to remove the provisioned container directory "
+                   << "at '" << containerDir << "'";
+      }
+
+      return true;
+    });
+}
+
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/provisioner.hpp b/src/slave/containerizer/provisioner/appc/provisioner.hpp
new file mode 100644
index 0000000..e4d5b8e
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/provisioner.hpp
@@ -0,0 +1,78 @@
+/**
+ * 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 __APPC_PROVISIONER_HPP__
+#define __APPC_PROVISIONER_HPP__
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+
+#include <stout/hashmap.hpp>
+#include <stout/json.hpp>
+#include <stout/nothing.hpp>
+#include <stout/try.hpp>
+
+#include "slave/containerizer/provisioner/provisioner.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+
+// Forward declaration.
+class AppcProvisionerProcess;
+
+
+class AppcProvisioner : public Provisioner
+{
+public:
+  static Try<process::Owned<Provisioner>> create(
+      const Flags& flags,
+      Fetcher* fetcher);
+
+  ~AppcProvisioner();
+
+  virtual process::Future<Nothing> recover(
+      const std::list<mesos::slave::ContainerState>& states,
+      const hashset<ContainerID>& orphans);
+
+  virtual process::Future<std::string> provision(
+      const ContainerID& containerId,
+      const Image& image);
+
+  virtual process::Future<bool> destroy(const ContainerID& containerId);
+
+private:
+  explicit AppcProvisioner(process::Owned<AppcProvisionerProcess> process);
+
+  AppcProvisioner(const AppcProvisioner&); // Not copyable.
+  AppcProvisioner& operator=(const AppcProvisioner&); // Not assignable.
+
+  process::Owned<AppcProvisionerProcess> process;
+};
+
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __APPC_PROVISIONER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/spec.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/spec.cpp b/src/slave/containerizer/provisioner/appc/spec.cpp
new file mode 100644
index 0000000..bbe523d
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/spec.cpp
@@ -0,0 +1,104 @@
+/**
+ * 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 <stout/os/stat.hpp>
+#include <stout/protobuf.hpp>
+#include <stout/strings.hpp>
+
+#include "slave/containerizer/provisioner/appc/paths.hpp"
+#include "slave/containerizer/provisioner/appc/spec.hpp"
+
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+namespace spec {
+
+Option<Error> validateManifest(const AppcImageManifest& manifest)
+{
+  // TODO(idownes): Validate that required fields are present when
+  // this cannot be expressed in the protobuf specification, e.g.,
+  // repeated fields with >= 1.
+  // TODO(xujyan): More thorough type validation:
+  // https://github.com/appc/spec/blob/master/spec/types.md
+  if (manifest.ackind() != "ImageManifest") {
+    return Error("Incorrect acKind field: " + manifest.ackind());
+  }
+
+  return None();
+}
+
+
+Option<Error> validateImageID(const string& imageId)
+{
+  if (!strings::startsWith(imageId, "sha512-")) {
+    return Error("Image ID needs to start with sha512-");
+  }
+
+  string hash = strings::remove(imageId, "sha512-", strings::PREFIX);
+  if (hash.length() != 128) {
+    return Error("Invalid hash length for: " + hash);
+  }
+
+  return None();
+}
+
+
+Option<Error> validateLayout(const string& imagePath)
+{
+  if (!os::stat::isdir(paths::getImageRootfsPath(imagePath))) {
+    return Error("No rootfs directory found in image layout");
+  }
+
+  if (!os::stat::isfile(paths::getImageManifestPath(imagePath))) {
+    return Error("No manifest found in image layout");
+  }
+
+  return None();
+}
+
+
+Try<AppcImageManifest> parse(const string& value)
+{
+  Try<JSON::Object> json = JSON::parse<JSON::Object>(value);
+  if (json.isError()) {
+    return Error("JSON parse failed: " + json.error());
+  }
+
+  Try<AppcImageManifest> manifest =
+    protobuf::parse<AppcImageManifest>(json.get());
+
+  if (manifest.isError()) {
+    return Error("Protobuf parse failed: " + manifest.error());
+  }
+
+  Option<Error> error = validateManifest(manifest.get());
+  if (error.isSome()) {
+    return Error("Schema validation failed: " + error.get().message);
+  }
+
+  return manifest.get();
+}
+
+} // namespace spec {
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/spec.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/spec.hpp b/src/slave/containerizer/provisioner/appc/spec.hpp
new file mode 100644
index 0000000..2bc8c6f
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/spec.hpp
@@ -0,0 +1,54 @@
+/**
+ * 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 __PROVISIONER_APPC_SPEC_HPP__
+#define __PROVISIONER_APPC_SPEC_HPP__
+
+#include <string>
+
+#include <stout/error.hpp>
+#include <stout/option.hpp>
+
+#include <mesos/mesos.hpp>
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+namespace spec {
+
+// Validate if the specified image manifest conforms to the Appc spec.
+Option<Error> validateManifest(const AppcImageManifest& manifest);
+
+// Validate if the specified image ID conforms to the Appc spec.
+Option<Error> validateImageID(const std::string& imageId);
+
+// Validate if the specified image has the disk layout that conforms
+// to the Appc spec.
+Option<Error> validateLayout(const std::string& imagePath);
+
+// Parse the AppcImageManifest in the specified JSON string.
+Try<AppcImageManifest> parse(const std::string& value);
+
+} // namespace spec {
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_APPC_SPEC_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/store.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/store.cpp b/src/slave/containerizer/provisioner/appc/store.cpp
new file mode 100644
index 0000000..327ac9b
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/store.cpp
@@ -0,0 +1,280 @@
+/**
+ * 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 <list>
+
+#include <glog/logging.h>
+
+#include <process/defer.hpp>
+#include <process/dispatch.hpp>
+
+#include <stout/check.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/os.hpp>
+#include <stout/path.hpp>
+
+#include "slave/containerizer/provisioner/appc/paths.hpp"
+#include "slave/containerizer/provisioner/appc/spec.hpp"
+#include "slave/containerizer/provisioner/appc/store.hpp"
+
+using namespace process;
+
+using std::list;
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+
+// Defines a locally cached image (which has passed validation).
+struct CachedImage
+{
+  CachedImage(
+      const AppcImageManifest& _manifest,
+      const string& _id,
+      const string& _path)
+    : manifest(_manifest), id(_id), path(_path) {}
+
+  string rootfs() const
+  {
+    return path::join(path, "rootfs");
+  }
+
+  const AppcImageManifest manifest;
+
+  // Image ID of the format "sha512-value" where "value" is the hex
+  // encoded string of the sha512 digest of the uncompressed tar file
+  // of the image.
+  const string id;
+
+  // Absolute path to the extracted image.
+  const string path;
+};
+
+
+// Helper that implements this:
+// https://github.com/appc/spec/blob/master/spec/aci.md#dependency-matching
+static bool matches(Image::Appc requirements, const CachedImage& candidate)
+{
+  // The name must match.
+  if (candidate.manifest.name() != requirements.name()) {
+    return false;
+  }
+
+  // If an id is specified the candidate must match.
+  if (requirements.has_id() && (candidate.id != requirements.id())) {
+    return false;
+  }
+
+  // Extract labels for easier comparison, this also weeds out duplicates.
+  // TODO(xujyan): Detect duplicate labels in image manifest validation
+  // and Image::Appc validation.
+  hashmap<string, string> requiredLabels;
+  foreach (const Label& label, requirements.labels().labels()) {
+    requiredLabels[label.key()] = label.value();
+  }
+
+  hashmap<string, string> candidateLabels;
+  foreach (const AppcImageManifest::Label& label,
+           candidate.manifest.labels()) {
+    candidateLabels[label.name()] = label.value();
+  }
+
+  // Any label specified must be present and match in the candidate.
+  foreachpair (const string& name,
+               const string& value,
+               requiredLabels) {
+    if (!candidateLabels.contains(name) ||
+        candidateLabels.get(name).get() != value) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+
+class StoreProcess : public Process<StoreProcess>
+{
+public:
+  StoreProcess(const string& root);
+
+  ~StoreProcess() {}
+
+  Future<Nothing> recover();
+
+  Future<vector<string>> get(const Image::Appc& image);
+
+private:
+  // Absolute path to the root directory of the store as defined by
+  // --appc_store_dir.
+  const string root;
+
+  // Mappings: name -> id -> image.
+  hashmap<string, hashmap<string, CachedImage>> images;
+};
+
+
+Try<Owned<Store>> Store::create(const Flags& flags)
+{
+  Try<Nothing> mkdir = os::mkdir(paths::getImagesDir(flags.appc_store_dir));
+  if (mkdir.isError()) {
+    return Error("Failed to create the images directory: " + mkdir.error());
+  }
+
+  // Make sure the root path is canonical so all image paths derived
+  // from it are canonical too.
+  Result<string> root = os::realpath(flags.appc_store_dir);
+  if (!root.isSome()) {
+    // The above mkdir call recursively creates the store directory
+    // if necessary so it cannot be None here.
+    CHECK_ERROR(root);
+    return Error(
+        "Failed to get the realpath of the store directory: " + root.error());
+  }
+
+  return Owned<Store>(new Store(
+      Owned<StoreProcess>(new StoreProcess(root.get()))));
+}
+
+
+Store::Store(Owned<StoreProcess> _process)
+  : process(_process)
+{
+  spawn(CHECK_NOTNULL(process.get()));
+}
+
+
+Store::~Store()
+{
+  terminate(process.get());
+  wait(process.get());
+}
+
+
+Future<Nothing> Store::recover()
+{
+  return dispatch(process.get(), &StoreProcess::recover);
+}
+
+
+Future<vector<string>> Store::get(const Image::Appc& image)
+{
+  return dispatch(process.get(), &StoreProcess::get, image);
+}
+
+
+StoreProcess::StoreProcess(const string& _root) : root(_root) {}
+
+
+// Implemented as a helper function because it's going to be used for a
+// newly downloaded image too.
+static Try<CachedImage> createImage(const string& imagePath)
+{
+  Option<Error> error = spec::validateLayout(imagePath);
+  if (error.isSome()) {
+    return Error("Invalid image layout: " + error.get().message);
+  }
+
+  string imageId = Path(imagePath).basename();
+
+  error = spec::validateImageID(imageId);
+  if (error.isSome()) {
+    return Error("Invalid image ID: " + error.get().message);
+  }
+
+  Try<string> read = os::read(paths::getImageManifestPath(imagePath));
+  if (read.isError()) {
+    return Error("Failed to read manifest: " + read.error());
+  }
+
+  Try<AppcImageManifest> manifest = spec::parse(read.get());
+  if (manifest.isError()) {
+    return Error("Failed to parse manifest: " + manifest.error());
+  }
+
+  return CachedImage(manifest.get(), imageId, imagePath);
+}
+
+
+Future<vector<string>> StoreProcess::get(const Image::Appc& image)
+{
+  if (!images.contains(image.name())) {
+    return Failure("No image named '" + image.name() + "' can be found");
+  }
+
+  // Get local candidates.
+  vector<CachedImage> candidates;
+  foreach (const CachedImage& candidate, images[image.name()].values()) {
+    // The first match is returned.
+    // TODO(xujyan): Some tie-breaking rules are necessary.
+    if (matches(image, candidate)) {
+      LOG(INFO) << "Found match for image '" << image.name()
+                << "' in the store";
+
+      // The Appc store current doesn't support dependencies and this is
+      // enforced by manifest validation: if the image's manifest contains
+      // dependencies it would fail the validation and wouldn't be stored
+      // in the store.
+      return vector<string>({candidate.rootfs()});
+    }
+  }
+
+  return Failure("No image named '" + image.name() +
+                 "' can match the requirements");
+}
+
+
+Future<Nothing> StoreProcess::recover()
+{
+  // Recover everything in the store.
+  Try<list<string>> imageIds = os::ls(paths::getImagesDir(root));
+  if (imageIds.isError()) {
+    return Failure(
+        "Failed to list images under '" +
+        paths::getImagesDir(root) + "': " +
+        imageIds.error());
+  }
+
+  foreach (const string& imageId, imageIds.get()) {
+    string path = paths::getImagePath(root, imageId);
+    if (!os::stat::isdir(path)) {
+      LOG(WARNING) << "Unexpected entry in storage: " << imageId;
+      continue;
+    }
+
+    Try<CachedImage> image = createImage(path);
+    if (image.isError()) {
+      LOG(WARNING) << "Unexpected entry in storage: " << image.error();
+      continue;
+    }
+
+    LOG(INFO) << "Restored image '" << image.get().manifest.name() << "'";
+
+    images[image.get().manifest.name()].put(image.get().id, image.get());
+  }
+
+  return Nothing();
+}
+
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/appc/store.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/store.hpp b/src/slave/containerizer/provisioner/appc/store.hpp
new file mode 100644
index 0000000..07218d1
--- /dev/null
+++ b/src/slave/containerizer/provisioner/appc/store.hpp
@@ -0,0 +1,90 @@
+/**
+ * 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 __PROVISIONER_APPC_STORE_HPP__
+#define __PROVISIONER_APPC_STORE_HPP__
+
+#include <string>
+#include <vector>
+
+#include <mesos/mesos.hpp>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+
+#include <stout/try.hpp>
+
+#include "slave/flags.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+namespace appc {
+
+// Forward declaration.
+class StoreProcess;
+
+
+// An image store abstraction that "stores" images. It serves as a read-through
+// cache (cache misses are fetched remotely and transparently) for images.
+// TODO(xujyan): The store currently keeps cached images indefinitely and we
+// should introduce cache eviction policies.
+class Store
+{
+public:
+  static Try<process::Owned<Store>> create(const Flags& flags);
+
+  ~Store();
+
+  process::Future<Nothing> recover();
+
+  // Get the specified image (and all its recursive dependencies) as a list
+  // of rootfs layers in the topological order (dependencies go before
+  // dependents in the list). The images required to build this list are
+  // either retrieved from the local cache or fetched remotely.
+  // NOTE: The returned list should not have duplicates. e.g., in the
+  // following scenario the result should be [C, B, D, A] (B before D in this
+  // example is decided by the order in which A specifies its dependencies).
+  //
+  // A --> B --> C
+  // |           ^
+  // |---> D ----|
+  //
+  // The returned future fails if the requested image or any of its
+  // dependencies cannot be found or failed to be fetched.
+  // TODO(xujyan): Fetching remotely is not implemented for now and until
+  // then the future fails directly if the image is not in the local cache.
+  // TODO(xujyan): The store currently doesn't support images that have
+  // dependencies and we should add it later.
+  process::Future<std::vector<std::string>> get(const Image::Appc& image);
+
+private:
+  Store(process::Owned<StoreProcess> process);
+
+  Store(const Store&); // Not copyable.
+  Store& operator=(const Store&); // Not assignable.
+
+  process::Owned<StoreProcess> process;
+};
+
+} // namespace appc {
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_APPC_STORE_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/backend.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/backend.cpp b/src/slave/containerizer/provisioner/backend.cpp
new file mode 100644
index 0000000..b5d9670
--- /dev/null
+++ b/src/slave/containerizer/provisioner/backend.cpp
@@ -0,0 +1,62 @@
+/**
+ * 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 <glog/logging.h>
+
+#include <stout/os.hpp>
+
+#include "slave/containerizer/provisioner/backend.hpp"
+
+#include "slave/containerizer/provisioner/backends/bind.hpp"
+#include "slave/containerizer/provisioner/backends/copy.hpp"
+
+using namespace process;
+
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+hashmap<string, Owned<Backend>> Backend::create(const Flags& flags)
+{
+  hashmap<string, Try<Owned<Backend>>(*)(const Flags&)> creators;
+
+#ifdef __linux__
+  creators.put("bind", &BindBackend::create);
+#endif // __linux__
+  creators.put("copy", &CopyBackend::create);
+
+  hashmap<string, Owned<Backend>> backends;
+
+  foreachkey (const string& name, creators) {
+    Try<Owned<Backend>> backend = creators[name](flags);
+    if (backend.isError()) {
+      LOG(WARNING) << "Failed to create '" << name << "' backend: "
+                   << backend.error();
+      continue;
+    }
+    backends.put(name, backend.get());
+  }
+
+  return backends;
+}
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/backend.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/backend.hpp b/src/slave/containerizer/provisioner/backend.hpp
new file mode 100644
index 0000000..1c80b79
--- /dev/null
+++ b/src/slave/containerizer/provisioner/backend.hpp
@@ -0,0 +1,67 @@
+/**
+ * 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 __PROVISIONER_BACKEND_HPP__
+#define __PROVISIONER_BACKEND_HPP__
+
+#include <string>
+#include <vector>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+
+#include <stout/hashmap.hpp>
+#include <stout/try.hpp>
+
+#include "slave/flags.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+// Provision a root filesystem for a container.
+class Backend
+{
+public:
+  virtual ~Backend() {}
+
+  // Return a map of all supported backends keyed by their names. Note
+  // that Backends that failed to be created due to incorrect flags are
+  // simply not added to the result.
+  static hashmap<std::string, process::Owned<Backend>> create(
+      const Flags& flags);
+
+  // Provision a root filesystem for a container into the specified 'rootfs'
+  // directory by applying the specified list of root filesystem layers in
+  // the list order, i.e., files in a layer can overwrite/shadow those from
+  // another layer earlier in the list.
+  virtual process::Future<Nothing> provision(
+      const std::vector<std::string>& layers,
+      const std::string& rootfs) = 0;
+
+  // Destroy the root filesystem provisioned at the specified 'rootfs'
+  // directory. Return false if there is no provisioned root filesystem
+  // to destroy for the given directory.
+  virtual process::Future<bool> destroy(const std::string& rootfs) = 0;
+};
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_BACKEND_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/backends/bind.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/backends/bind.cpp b/src/slave/containerizer/provisioner/backends/bind.cpp
new file mode 100644
index 0000000..d853b49
--- /dev/null
+++ b/src/slave/containerizer/provisioner/backends/bind.cpp
@@ -0,0 +1,197 @@
+/**
+ * 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 <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <process/dispatch.hpp>
+#include <process/process.hpp>
+
+#include <stout/foreach.hpp>
+#include <stout/os.hpp>
+
+#include "linux/fs.hpp"
+
+#include "slave/containerizer/provisioner/backends/bind.hpp"
+
+using namespace process;
+
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+class BindBackendProcess : public Process<BindBackendProcess>
+{
+public:
+  Future<Nothing> provision(const vector<string>& layers, const string& rootfs);
+
+  Future<bool> destroy(const string& rootfs);
+};
+
+
+Try<Owned<Backend>> BindBackend::create(const 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("BindBackend requires root privileges");
+  }
+
+  return Owned<Backend>(new BindBackend(
+      Owned<BindBackendProcess>(new BindBackendProcess())));
+}
+
+
+BindBackend::~BindBackend()
+{
+  terminate(process.get());
+  wait(process.get());
+}
+
+
+BindBackend::BindBackend(Owned<BindBackendProcess> _process)
+  : process(_process)
+{
+  spawn(CHECK_NOTNULL(process.get()));
+}
+
+
+Future<Nothing> BindBackend::provision(
+    const vector<string>& layers,
+    const string& rootfs)
+{
+  return dispatch(
+      process.get(), &BindBackendProcess::provision, layers, rootfs);
+}
+
+
+Future<bool> BindBackend::destroy(const string& rootfs)
+{
+  return dispatch(process.get(), &BindBackendProcess::destroy, rootfs);
+}
+
+
+Future<Nothing> BindBackendProcess::provision(
+    const vector<string>& layers,
+    const string& rootfs)
+{
+  if (layers.size() > 1) {
+    return Failure(
+        "Multiple layers are not supported by the bind backend");
+  }
+
+  if (layers.size() == 0) {
+    return Failure("No filesystem layer provided");
+  }
+
+  Try<Nothing> mkdir = os::mkdir(rootfs);
+  if (mkdir.isError()) {
+    return Failure("Failed to create container rootfs at " + rootfs);
+  }
+
+  // TODO(xujyan): Use MS_REC? Does any provisioner use mounts within
+  // its image store in a single layer?
+  Try<Nothing> mount = fs::mount(
+      layers.front(),
+      rootfs,
+      None(),
+      MS_BIND,
+      NULL);
+
+  if (mount.isError()) {
+    return Failure(
+        "Failed to bind mount rootfs '" + layers.front() +
+        "' to '" + rootfs + "': " + mount.error());
+  }
+
+  // And remount it read-only.
+  mount = fs::mount(
+      None(), // Ignored.
+      rootfs,
+      None(),
+      MS_BIND | MS_RDONLY | MS_REMOUNT,
+      NULL);
+
+  if (mount.isError()) {
+    return Failure(
+        "Failed to remount rootfs '" + rootfs + "' read-only: " +
+        mount.error());
+  }
+
+  return Nothing();
+}
+
+
+Future<bool> BindBackendProcess::destroy(const string& rootfs)
+{
+  Try<fs::MountInfoTable> mountTable = fs::MountInfoTable::read();
+
+  if (mountTable.isError()) {
+    return Failure("Failed to read mount table: " + mountTable.error());
+  }
+
+  foreach (const fs::MountInfoTable::Entry& entry, mountTable.get().entries) {
+    // TODO(xujyan): If MS_REC was used in 'provision()' we would need
+    // to check `strings::startsWith(entry.target, rootfs)` here to
+    // unmount all nested mounts.
+    if (entry.target == rootfs) {
+      // NOTE: This would fail if the rootfs is still in use.
+      Try<Nothing> unmount = fs::unmount(entry.target);
+      if (unmount.isError()) {
+        return Failure(
+            "Failed to destroy bind-mounted rootfs '" + rootfs + "': " +
+            unmount.error());
+      }
+
+      // TODO(jieyu): If 'rmdir' here returns EBUSY, we still returns
+      // a success. This is currently possible because the parent
+      // mount of 'rootfs' might not be a shared mount. Thus,
+      // containers in different mount namespaces might hold extra
+      // references to this mount. It is OK to ignore the EBUSY error
+      // because the provisioner will later try to delete all the
+      // rootfses for the terminated containers.
+      if (::rmdir(rootfs.c_str()) != 0) {
+        string message =
+          "Failed to remove rootfs mount point '" + rootfs +
+          "': " + strerror(errno);
+
+        if (errno == EBUSY) {
+          LOG(ERROR) << message;
+        } else {
+          return Failure(message);
+        }
+      }
+
+      return true;
+    }
+  }
+
+  return false;
+}
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/backends/bind.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/backends/bind.hpp b/src/slave/containerizer/provisioner/backends/bind.hpp
new file mode 100644
index 0000000..1685938
--- /dev/null
+++ b/src/slave/containerizer/provisioner/backends/bind.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 __PROVISIONER_BACKENDS_BIND_HPP__
+#define __PROVISIONER_BACKENDS_BIND_HPP__
+
+#include "slave/containerizer/provisioner/backend.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+// Forward declaration.
+class BindBackendProcess;
+
+
+// This is a specialized backend that may be useful for deployments
+// using large (multi-GB) single-layer images *and* where more recent
+// kernel features such as overlayfs are not available (overlayfs-based
+// backend tracked by MESOS-2971). For small images (10's to 100's of MB)
+// the copy backend may be sufficient. NOTE:
+// 1) BindBackend supports only a single layer. Multi-layer images will
+//    fail to provision and the container will fail to launch!
+// 2) The filesystem is read-only because all containers using this
+//    image share the source. Select writable areas can be achieved by
+//    mounting read-write volumes to places like /tmp, /var/tmp,
+//    /home, etc. using the ContainerInfo. These can be relative to
+//    the executor work directory.
+//    N.B. Since the filesystem is read-only, '--sandbox_directory' must
+//    already exist within the filesystem because the filesystem isolator
+//    is unable to create it!
+// 3) It's fast because the bind mount requires (nearly) zero IO.
+class BindBackend : public Backend
+{
+public:
+  virtual ~BindBackend();
+
+  // BindBackend doesn't use any flag.
+  static Try<process::Owned<Backend>> create(const Flags&);
+
+  virtual process::Future<Nothing> provision(
+      const std::vector<std::string>& layers,
+      const std::string& rootfs);
+
+  virtual process::Future<bool> destroy(const std::string& rootfs);
+
+private:
+  explicit BindBackend(process::Owned<BindBackendProcess> process);
+
+  BindBackend(const BindBackend&); // Not copyable.
+  BindBackend& operator=(const BindBackend&); // Not assignable.
+
+  process::Owned<BindBackendProcess> process;
+};
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_BACKENDS_BIND_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/backends/copy.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/backends/copy.cpp b/src/slave/containerizer/provisioner/backends/copy.cpp
new file mode 100644
index 0000000..92fb098
--- /dev/null
+++ b/src/slave/containerizer/provisioner/backends/copy.cpp
@@ -0,0 +1,203 @@
+/**
+ * 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 <list>
+
+#include <process/collect.hpp>
+#include <process/defer.hpp>
+#include <process/dispatch.hpp>
+#include <process/io.hpp>
+#include <process/process.hpp>
+#include <process/subprocess.hpp>
+
+
+#include <stout/foreach.hpp>
+#include <stout/os.hpp>
+
+#include "common/status_utils.hpp"
+
+#include "slave/containerizer/provisioner/backends/copy.hpp"
+
+
+using namespace process;
+
+using std::string;
+using std::list;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+class CopyBackendProcess : public Process<CopyBackendProcess>
+{
+public:
+  Future<Nothing> provision(const vector<string>& layers, const string& rootfs);
+
+  Future<bool> destroy(const string& rootfs);
+
+private:
+  Future<Nothing> _provision(string layer, const string& rootfs);
+};
+
+
+Try<Owned<Backend>> CopyBackend::create(const Flags&)
+{
+  return Owned<Backend>(new CopyBackend(
+      Owned<CopyBackendProcess>(new CopyBackendProcess())));
+}
+
+
+CopyBackend::~CopyBackend()
+{
+  terminate(process.get());
+  wait(process.get());
+}
+
+
+CopyBackend::CopyBackend(Owned<CopyBackendProcess> _process)
+  : process(_process)
+{
+  spawn(CHECK_NOTNULL(process.get()));
+}
+
+
+Future<Nothing> CopyBackend::provision(
+    const vector<string>& layers,
+    const string& rootfs)
+{
+  return dispatch(
+      process.get(), &CopyBackendProcess::provision, layers, rootfs);
+}
+
+
+Future<bool> CopyBackend::destroy(const string& rootfs)
+{
+  return dispatch(process.get(), &CopyBackendProcess::destroy, rootfs);
+}
+
+
+Future<Nothing> CopyBackendProcess::provision(
+    const vector<string>& layers,
+    const string& rootfs)
+{
+  if (layers.size() == 0) {
+    return Failure("No filesystem layers provided");
+  }
+
+  if (os::exists(rootfs)) {
+    return Failure("Rootfs is already provisioned");
+  }
+
+  Try<Nothing> mkdir = os::mkdir(rootfs);
+  if (mkdir.isError()) {
+    return Failure("Failed to create rootfs directory: " + mkdir.error());
+  }
+
+  list<Future<Nothing>> futures{Nothing()};
+
+  foreach (const string layer, layers) {
+    futures.push_back(
+        futures.back().then(
+            defer(self(), &Self::_provision, layer, rootfs)));
+  }
+
+  return collect(futures)
+    .then([]() -> Future<Nothing> { return Nothing(); });
+}
+
+
+Future<Nothing> CopyBackendProcess::_provision(
+  string layer,
+  const string& rootfs)
+{
+  VLOG(1) << "Copying layer path '" << layer << "' to rootfs '" << rootfs
+          << "'";
+
+#ifdef __APPLE__
+  if (!strings::endsWith(layer, "/")) {
+    layer += "/";
+  }
+
+  // OSX cp doesn't support -T flag, but supports source trailing
+  // slash so we only copy the content but not the folder.
+  vector<string> args{"cp", "-a", layer, rootfs};
+#else
+  vector<string> args{"cp", "-aT", layer, rootfs};
+#endif // __APPLE__
+
+  Try<Subprocess> s = subprocess(
+      "cp",
+      args,
+      Subprocess::PATH("/dev/null"),
+      Subprocess::PATH("/dev/null"),
+      Subprocess::PIPE());
+
+  if (s.isError()) {
+    return Failure("Failed to create 'cp' subprocess: " + s.error());
+  }
+
+  Subprocess cp = s.get();
+
+  return cp.status()
+    .then([cp](const Option<int>& status) -> Future<Nothing> {
+      if (status.isNone()) {
+        return Failure("Failed to reap subprocess to copy image");
+      } else if (status.get() != 0) {
+        return io::read(cp.err().get())
+          .then([](const string& err) -> Future<Nothing> {
+            return Failure("Failed to copy layer: " + err);
+          });
+      }
+
+      return Nothing();
+    });
+}
+
+
+Future<bool> CopyBackendProcess::destroy(const string& rootfs)
+{
+  vector<string> argv{"rm", "-rf", rootfs};
+
+  Try<Subprocess> s = subprocess(
+      "rm",
+      argv,
+      Subprocess::PATH("/dev/null"),
+      Subprocess::FD(STDOUT_FILENO),
+      Subprocess::FD(STDERR_FILENO));
+
+  if (s.isError()) {
+    return Failure("Failed to create 'rm' subprocess: " + s.error());
+  }
+
+  return s.get().status()
+    .then([](const Option<int>& status) -> Future<bool> {
+      if (status.isNone()) {
+        return Failure("Failed to reap subprocess to destroy rootfs");
+      } else if (status.get() != 0) {
+        return Failure("Failed to destroy rootfs, exit status: " +
+                       WSTRINGIFY(status.get()));
+      }
+
+      return true;
+    });
+}
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/cc1f8f54/src/slave/containerizer/provisioner/backends/copy.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/backends/copy.hpp b/src/slave/containerizer/provisioner/backends/copy.hpp
new file mode 100644
index 0000000..10d9aee
--- /dev/null
+++ b/src/slave/containerizer/provisioner/backends/copy.hpp
@@ -0,0 +1,61 @@
+/**
+ * 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 __PROVISIONER_BACKENDS_COPY_HPP__
+#define __PROVISIONER_BACKENDS_COPY_HPP__
+
+#include "slave/containerizer/provisioner/backend.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+// Forward declaration.
+class CopyBackendProcess;
+
+
+class CopyBackend : public Backend
+{
+public:
+  virtual ~CopyBackend();
+
+  // CopyBackend doesn't use any flag.
+  static Try<process::Owned<Backend>> create(const Flags&);
+
+  // Provisions a rootfs given the layers' paths and target rootfs
+  // path.
+  virtual process::Future<Nothing> provision(
+      const std::vector<std::string>& layers,
+      const std::string& rootfs);
+
+  virtual process::Future<bool> destroy(const std::string& rootfs);
+
+private:
+  explicit CopyBackend(process::Owned<CopyBackendProcess> process);
+
+  CopyBackend(const CopyBackend&); // Not copyable.
+  CopyBackend& operator=(const CopyBackend&); // Not assignable.
+
+  process::Owned<CopyBackendProcess> process;
+};
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_BACKENDS_COPY_HPP__


[6/6] mesos git commit: Unified the implementations of image provisioners.

Posted by ji...@apache.org.
Unified the implementations of image provisioners.

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


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

Branch: refs/heads/master
Commit: 76861c52772f7c501697ac73637d8a56aa8c9651
Parents: c027c82
Author: Jie Yu <yu...@gmail.com>
Authored: Tue Sep 15 18:04:15 2015 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Sep 16 15:45:50 2015 -0700

----------------------------------------------------------------------
 src/Makefile.am                                 |   4 +-
 .../isolators/filesystem/linux.cpp              |  38 +-
 .../isolators/filesystem/linux.hpp              |   6 +-
 src/slave/containerizer/mesos/containerizer.cpp |  10 +-
 .../provisioner/appc/provisioner.cpp            | 397 -------------------
 .../provisioner/appc/provisioner.hpp            |  78 ----
 .../containerizer/provisioner/appc/store.cpp    | 156 ++++----
 .../containerizer/provisioner/appc/store.hpp    |  51 +--
 src/slave/containerizer/provisioner/paths.cpp   |  14 +-
 src/slave/containerizer/provisioner/paths.hpp   |  25 +-
 .../containerizer/provisioner/provisioner.cpp   | 394 ++++++++++++++++--
 .../containerizer/provisioner/provisioner.hpp   |  33 +-
 src/slave/containerizer/provisioner/store.cpp   |  76 ++++
 src/slave/containerizer/provisioner/store.hpp   |  75 ++++
 src/slave/flags.cpp                             |  21 +-
 src/slave/flags.hpp                             |   5 +-
 src/slave/paths.cpp                             |  17 +-
 src/slave/paths.hpp                             |  12 +-
 .../containerizer/filesystem_isolator_tests.cpp |   8 +-
 src/tests/containerizer/provisioner.hpp         |  10 +-
 .../containerizer/provisioner_appc_tests.cpp    | 144 +++----
 src/tests/paths_tests.cpp                       |  14 +-
 22 files changed, 779 insertions(+), 809 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 60cb10d..2286366 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -505,8 +505,8 @@ libmesos_no_3rdparty_la_SOURCES =					\
 	slave/containerizer/mesos/launch.cpp				\
         slave/containerizer/provisioner/paths.cpp			\
 	slave/containerizer/provisioner/provisioner.cpp			\
+        slave/containerizer/provisioner/store.cpp			\
 	slave/containerizer/provisioner/appc/paths.cpp			\
-	slave/containerizer/provisioner/appc/provisioner.cpp		\
 	slave/containerizer/provisioner/appc/spec.cpp			\
 	slave/containerizer/provisioner/appc/store.cpp			\
 	slave/containerizer/provisioner/backend.cpp			\
@@ -796,8 +796,8 @@ libmesos_no_3rdparty_la_SOURCES +=					\
 	slave/containerizer/linux_launcher.hpp				\
         slave/containerizer/provisioner/paths.hpp			\
 	slave/containerizer/provisioner/provisioner.hpp			\
+	slave/containerizer/provisioner/store.hpp			\
 	slave/containerizer/provisioner/appc/paths.hpp			\
-	slave/containerizer/provisioner/appc/provisioner.hpp		\
 	slave/containerizer/provisioner/appc/spec.hpp			\
 	slave/containerizer/provisioner/appc/store.hpp			\
 	slave/containerizer/provisioner/backend.hpp			\

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/isolators/filesystem/linux.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/isolators/filesystem/linux.cpp b/src/slave/containerizer/isolators/filesystem/linux.cpp
index dbdbf87..297a296 100644
--- a/src/slave/containerizer/isolators/filesystem/linux.cpp
+++ b/src/slave/containerizer/isolators/filesystem/linux.cpp
@@ -55,7 +55,7 @@ namespace slave {
 
 Try<Isolator*> LinuxFilesystemIsolatorProcess::create(
     const Flags& flags,
-    const hashmap<Image::Type, Owned<Provisioner>>& provisioners)
+    const Owned<Provisioner>& provisioner)
 {
   Result<string> user = os::user();
   if (!user.isSome()) {
@@ -68,7 +68,7 @@ Try<Isolator*> LinuxFilesystemIsolatorProcess::create(
   }
 
   Owned<MesosIsolatorProcess> process(
-      new LinuxFilesystemIsolatorProcess(flags, provisioners));
+      new LinuxFilesystemIsolatorProcess(flags, provisioner));
 
   return new MesosIsolator(process);
 }
@@ -76,9 +76,9 @@ Try<Isolator*> LinuxFilesystemIsolatorProcess::create(
 
 LinuxFilesystemIsolatorProcess::LinuxFilesystemIsolatorProcess(
     const Flags& _flags,
-    const hashmap<Image::Type, Owned<Provisioner>>& _provisioners)
+    const Owned<Provisioner>& _provisioner)
   : flags(_flags),
-    provisioners(_provisioners) {}
+    provisioner(_provisioner) {}
 
 
 LinuxFilesystemIsolatorProcess::~LinuxFilesystemIsolatorProcess() {}
@@ -174,12 +174,7 @@ Future<Nothing> LinuxFilesystemIsolatorProcess::_recover(
     const list<ContainerState>& states,
     const hashset<ContainerID>& orphans)
 {
-  list<Future<Nothing>> futures;
-  foreachvalue (const Owned<Provisioner>& provisioner, provisioners) {
-    futures.push_back(provisioner->recover(states, orphans));
-  }
-
-  return collect(futures)
+  return provisioner->recover(states, orphans)
     .then([]() -> Future<Nothing> { return Nothing(); });
 }
 
@@ -209,13 +204,7 @@ Future<Option<ContainerPrepareInfo>> LinuxFilesystemIsolatorProcess::prepare(
 
   const Image& image = executorInfo.container().mesos().image();
 
-  if (!provisioners.contains(image.type())) {
-    return Failure(
-        "No suitable provisioner found for container image type '" +
-        stringify(image.type()) + "'");
-  }
-
-  return provisioners[image.type()]->provision(containerId, image)
+  return provisioner->provision(containerId, image)
     .then(defer(PID<LinuxFilesystemIsolatorProcess>(this),
                 &LinuxFilesystemIsolatorProcess::_prepare,
                 containerId,
@@ -252,14 +241,8 @@ Future<Option<ContainerPrepareInfo>> LinuxFilesystemIsolatorProcess::_prepare(
 
     const Image& image = volume->image();
 
-    if (!provisioners.contains(image.type())) {
-      return Failure(
-          "No suitable provisioner found for image type '" +
-          stringify(image.type()) + "' in a volume");
-    }
-
     futures.push_back(
-        provisioners[image.type()]->provision(containerId, image)
+        provisioner->provision(containerId, image)
           .then([volume](const string& path) -> Future<Nothing> {
             volume->set_host_path(path);
             return Nothing();
@@ -772,12 +755,7 @@ Future<Nothing> LinuxFilesystemIsolatorProcess::cleanup(
   }
 
   // Destroy the provisioned root filesystems.
-  list<Future<bool>> futures;
-  foreachvalue (const Owned<Provisioner>& provisioner, provisioners) {
-    futures.push_back(provisioner->destroy(containerId));
-  }
-
-  return collect(futures)
+  return provisioner->destroy(containerId)
     .then([]() -> Future<Nothing> { return Nothing(); });
 }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/isolators/filesystem/linux.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/isolators/filesystem/linux.hpp b/src/slave/containerizer/isolators/filesystem/linux.hpp
index ff76c89..b0016e5 100644
--- a/src/slave/containerizer/isolators/filesystem/linux.hpp
+++ b/src/slave/containerizer/isolators/filesystem/linux.hpp
@@ -45,7 +45,7 @@ class LinuxFilesystemIsolatorProcess : public MesosIsolatorProcess
 public:
   static Try<mesos::slave::Isolator*> create(
       const Flags& flags,
-      const hashmap<Image::Type, process::Owned<Provisioner>>& provisioners);
+      const process::Owned<Provisioner>& provisioner);
 
   virtual ~LinuxFilesystemIsolatorProcess();
 
@@ -81,7 +81,7 @@ public:
 private:
   LinuxFilesystemIsolatorProcess(
       const Flags& flags,
-      const hashmap<Image::Type, process::Owned<Provisioner>>& provisioners);
+      const process::Owned<Provisioner>& provisioner);
 
   process::Future<Nothing> _recover(
       const std::list<mesos::slave::ContainerState>& states,
@@ -107,6 +107,7 @@ private:
       const Option<std::string>& rootfs);
 
   const Flags flags;
+  const process::Owned<Provisioner> provisioner;
 
   struct Info
   {
@@ -126,7 +127,6 @@ private:
   };
 
   hashmap<ContainerID, process::Owned<Info>> infos;
-  hashmap<Image::Type, process::Owned<Provisioner>> provisioners;
 };
 
 } // namespace slave {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/mesos/containerizer.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/containerizer.cpp b/src/slave/containerizer/mesos/containerizer.cpp
index 6ab4c08..0023f1d 100644
--- a/src/slave/containerizer/mesos/containerizer.cpp
+++ b/src/slave/containerizer/mesos/containerizer.cpp
@@ -162,11 +162,9 @@ Try<MesosContainerizer*> MesosContainerizer::create(
 
 #ifdef __linux__
   // The provisioner will be used by the 'filesystem/linux' isolator.
-  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners =
-    Provisioner::create(flags, fetcher);
-
-  if (provisioners.isError()) {
-    return Error("Failed to create provisioner(s): " + provisioners.error());
+  Try<Owned<Provisioner>> provisioner = Provisioner::create(flags, fetcher);
+  if (provisioner.isError()) {
+    return Error("Failed to create provisioner: " + provisioner.error());
   }
 #endif
 
@@ -178,7 +176,7 @@ Try<MesosContainerizer*> MesosContainerizer::create(
 #ifdef __linux__
     {"filesystem/linux", lambda::bind(&LinuxFilesystemIsolatorProcess::create,
                                       lambda::_1,
-                                      provisioners.get())},
+                                      provisioner.get())},
 
     // TODO(jieyu): Deprecate this in favor of using filesystem/linux.
     {"filesystem/shared", &SharedFilesystemIsolatorProcess::create},

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/appc/provisioner.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/provisioner.cpp b/src/slave/containerizer/provisioner/appc/provisioner.cpp
deleted file mode 100644
index 2e77747..0000000
--- a/src/slave/containerizer/provisioner/appc/provisioner.cpp
+++ /dev/null
@@ -1,397 +0,0 @@
-/**
- * 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 <mesos/type_utils.hpp>
-
-#include <process/collect.hpp>
-#include <process/defer.hpp>
-#include <process/dispatch.hpp>
-#include <process/process.hpp>
-
-#include <stout/foreach.hpp>
-#include <stout/hashset.hpp>
-#include <stout/os.hpp>
-#include <stout/stringify.hpp>
-#include <stout/strings.hpp>
-#include <stout/uuid.hpp>
-
-#include "slave/containerizer/provisioner/backend.hpp"
-#include "slave/containerizer/provisioner/paths.hpp"
-
-#include "slave/containerizer/provisioner/appc/paths.hpp"
-#include "slave/containerizer/provisioner/appc/provisioner.hpp"
-#include "slave/containerizer/provisioner/appc/spec.hpp"
-#include "slave/containerizer/provisioner/appc/store.hpp"
-
-#include "slave/paths.hpp"
-
-using namespace process;
-using namespace mesos::internal::slave;
-
-using std::list;
-using std::string;
-using std::vector;
-
-using mesos::slave::ContainerState;
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-
-class AppcProvisionerProcess : public Process<AppcProvisionerProcess>
-{
-public:
-  AppcProvisionerProcess(
-      const Flags& flags,
-      const string& root,
-      const Owned<Store>& store,
-      const hashmap<string, Owned<Backend>>& backends);
-
-  Future<Nothing> recover(
-      const list<ContainerState>& states,
-      const hashset<ContainerID>& orphans);
-
-  Future<string> provision(const ContainerID& containerId, const Image& image);
-
-  Future<bool> destroy(const ContainerID& containerId);
-
-private:
-  Future<string> _provision(const vector<string>& layers, const string& rootfs);
-
-  const Flags flags;
-
-  // Absolute path to the Appc provisioner root directory. It can be derived
-  // from '--work_dir' but we keep a separate copy here because we converted
-  // it into an absolute path so managed rootfs paths match the ones in
-  // 'mountinfo' (important if mount-based backends are used).
-  const string root;
-
-  const Owned<Store> store;
-  const hashmap<string, Owned<Backend>> backends;
-
-  struct Info
-  {
-    // Mappings: backend -> rootfsId -> rootfsPath.
-    hashmap<string, hashmap<string, string>> rootfses;
-  };
-
-  hashmap<ContainerID, Owned<Info>> infos;
-};
-
-
-// NOTE: Successful creation of the provisioner means its managed
-// directory under --work_dir is also created.
-Try<Owned<Provisioner>> AppcProvisioner::create(
-    const Flags& flags,
-    Fetcher* fetcher)
-{
-  string _root =
-    slave::paths::getProvisionerDir(flags.work_dir, Image::APPC);
-
-  Try<Nothing> mkdir = os::mkdir(_root);
-  if (mkdir.isError()) {
-    return Error("Failed to create provisioner root directory '" +
-                 _root + "': " + mkdir.error());
-  }
-
-  Result<string> root = os::realpath(_root);
-  if (root.isError()) {
-    return Error(
-        "Failed to resolve the realpath of provisioner root directory '" +
-        _root + "': " + root.error());
-  }
-
-  CHECK_SOME(root); // Can't be None since we just created it.
-
-  Try<Owned<Store>> store = Store::create(flags);
-  if (store.isError()) {
-    return Error("Failed to create image store: " + store.error());
-  }
-
-  hashmap<string, Owned<Backend>> backends = Backend::create(flags);
-  if (backends.empty()) {
-    return Error("No usable provisioner backend created");
-  }
-
-  if (!backends.contains(flags.appc_provisioner_backend)) {
-    return Error("The specified provisioner backend '" +
-                 flags.appc_provisioner_backend + "'is unsupported");
-  }
-
-  return Owned<Provisioner>(new AppcProvisioner(
-      Owned<AppcProvisionerProcess>(new AppcProvisionerProcess(
-          flags,
-          root.get(),
-          store.get(),
-          backends))));
-}
-
-
-AppcProvisioner::AppcProvisioner(Owned<AppcProvisionerProcess> _process)
-  : process(_process)
-{
-  spawn(CHECK_NOTNULL(process.get()));
-}
-
-
-AppcProvisioner::~AppcProvisioner()
-{
-  terminate(process.get());
-  wait(process.get());
-}
-
-
-Future<Nothing> AppcProvisioner::recover(
-    const list<ContainerState>& states,
-    const hashset<ContainerID>& orphans)
-{
-  return dispatch(
-      process.get(),
-      &AppcProvisionerProcess::recover,
-      states,
-      orphans);
-}
-
-
-Future<string> AppcProvisioner::provision(
-    const ContainerID& containerId,
-    const Image& image)
-{
-  return dispatch(
-      process.get(),
-      &AppcProvisionerProcess::provision,
-      containerId,
-      image);
-}
-
-
-Future<bool> AppcProvisioner::destroy(const ContainerID& containerId)
-{
-  return dispatch(
-      process.get(),
-      &AppcProvisionerProcess::destroy,
-      containerId);
-}
-
-
-AppcProvisionerProcess::AppcProvisionerProcess(
-    const Flags& _flags,
-    const string& _root,
-    const Owned<Store>& _store,
-    const hashmap<string, Owned<Backend>>& _backends)
-  : flags(_flags),
-    root(_root),
-    store(_store),
-    backends(_backends) {}
-
-
-Future<Nothing> AppcProvisionerProcess::recover(
-    const list<ContainerState>& states,
-    const hashset<ContainerID>& orphans)
-{
-  // Register living containers, including the ones that do not
-  // provision Appc images.
-  hashset<ContainerID> alive;
-  foreach (const ContainerState& state, states) {
-    alive.insert(state.container_id());
-  }
-
-  // List provisioned containers; recover living ones; destroy unknown orphans.
-  // Note that known orphan containers are recovered as well and they will
-  // be destroyed by the containerizer using the normal cleanup path. See
-  // MESOS-2367 for details.
-  Try<hashmap<ContainerID, string>> containers =
-    provisioner::paths::listContainers(root);
-
-  if (containers.isError()) {
-    return Failure("Failed to list the containers managed by Appc "
-                   "provisioner: " + containers.error());
-  }
-
-  // Scan the list of containers, register all of them with 'infos' but
-  // mark unknown orphans for immediate cleanup.
-  hashset<ContainerID> unknownOrphans;
-  foreachkey (const ContainerID& containerId, containers.get()) {
-    Owned<Info> info = Owned<Info>(new Info());
-
-    Try<hashmap<string, hashmap<string, string>>> rootfses =
-      provisioner::paths::listContainerRootfses(root, containerId);
-
-    if (rootfses.isError()) {
-      return Failure("Unable to list rootfses belonged to container '" +
-                     containerId.value() + "': " + rootfses.error());
-    }
-
-    foreachkey (const string& backend, rootfses.get()) {
-      if (!backends.contains(backend)) {
-        return Failure("Found rootfses managed by an unrecognized backend: " +
-                       backend);
-      }
-
-      info->rootfses.put(backend, rootfses.get()[backend]);
-    }
-
-    infos.put(containerId, info);
-
-    if (alive.contains(containerId) || orphans.contains(containerId)) {
-      VLOG(1) << "Recovered container " << containerId;
-      continue;
-    } else {
-      // For immediate cleanup below.
-      unknownOrphans.insert(containerId);
-    }
-  }
-
-  LOG(INFO)
-    << "Recovered living and known orphan containers for Appc provisioner";
-
-  // Destroy unknown orphan containers' rootfses.
-  list<Future<bool>> destroys;
-  foreach (const ContainerID& containerId, unknownOrphans) {
-    destroys.push_back(destroy(containerId));
-  }
-
-  Future<Nothing> cleanup = collect(destroys)
-    .then([]() -> Future<Nothing> {
-      LOG(INFO) << "Cleaned up unknown orphan containers for Appc provisioner";
-      return Nothing();
-    });
-
-  Future<Nothing> recover = store->recover()
-    .then([]() -> Future<Nothing> {
-      LOG(INFO) << "Recovered Appc image store";
-      return Nothing();
-    });
-
-
-  // A successful provisioner recovery depends on:
-  // 1) Recovery of living containers and known orphans (done above).
-  // 2) Successful cleanup of unknown orphans.
-  // 3) Successful store recovery.
-  return collect(cleanup, recover)
-    .then([=]() -> Future<Nothing> {
-      return Nothing();
-    });
-}
-
-
-Future<string> AppcProvisionerProcess::provision(
-    const ContainerID& containerId,
-    const Image& image)
-{
-  if (image.type() != Image::APPC) {
-    return Failure("Unsupported container image type: " +
-                   stringify(image.type()));
-  }
-
-  if (!image.has_appc()) {
-    return Failure("Missing Appc image info");
-  }
-
-  string rootfsId = UUID::random().toString();
-  string rootfs = provisioner::paths::getContainerRootfsDir(
-      root, containerId, flags.appc_provisioner_backend, rootfsId);
-
-  if (!infos.contains(containerId)) {
-    infos.put(containerId, Owned<Info>(new Info()));
-  }
-
-  infos[containerId]->rootfses[flags.appc_provisioner_backend].put(
-      rootfsId, rootfs);
-
-  // Get and then provision image layers from the store.
-  return store->get(image.appc())
-    .then(defer(self(), &Self::_provision, lambda::_1, rootfs));
-}
-
-
-Future<string> AppcProvisionerProcess::_provision(
-     const vector<string>& layers,
-     const string& rootfs)
-{
-  LOG(INFO) << "Provisioning image layers to rootfs '" << rootfs << "'";
-
-  CHECK(backends.contains(flags.appc_provisioner_backend));
-  return backends.get(flags.appc_provisioner_backend).get()->provision(
-      layers,
-      rootfs)
-    .then([rootfs]() -> Future<string> { return rootfs; });
-}
-
-
-Future<bool> AppcProvisionerProcess::destroy(const ContainerID& containerId)
-{
-  if (!infos.contains(containerId)) {
-    LOG(INFO) << "Ignoring destroy request for unknown container: "
-              << containerId;
-
-    return false;
-  }
-
-  // Unregister the container first. If destroy() fails, we can rely
-  // on recover() to retry it later.
-  Owned<Info> info = infos[containerId];
-  infos.erase(containerId);
-
-  list<Future<bool>> futures;
-  foreachkey (const string& backend, info->rootfses) {
-    foreachvalue (const string& rootfs, info->rootfses[backend]) {
-      if (!backends.contains(backend)) {
-        return Failure("Cannot destroy rootfs '" + rootfs +
-                       "' provisioned by an unknown backend '" + backend + "'");
-      }
-
-      LOG(INFO) << "Destroying container rootfs for container '"
-                << containerId << "' at '" << rootfs << "'";
-
-      futures.push_back(
-          backends.get(backend).get()->destroy(rootfs));
-    }
-  }
-
-  // NOTE: We calculate 'containerDir' here so that the following
-  // lambda does not need to bind 'this'.
-  string containerDir =
-    provisioner::paths::getContainerDir(root, containerId);
-
-  // TODO(xujyan): Revisit the usefulness of this return value.
-  return collect(futures)
-    .then([containerDir]() -> Future<bool> {
-      // This should be fairly cheap as the directory should only
-      // contain a few empty sub-directories at this point.
-      //
-      // TODO(jieyu): Currently, it's possible that some directories
-      // cannot be removed due to EBUSY. EBUSY is caused by the race
-      // between cleaning up this container and new containers copying
-      // the host mount table. It's OK to ignore them. The cleanup
-      // will be retried during slave recovery.
-      Try<Nothing> rmdir = os::rmdir(containerDir);
-      if (rmdir.isError()) {
-        LOG(ERROR) << "Failed to remove the provisioned container directory "
-                   << "at '" << containerDir << "'";
-      }
-
-      return true;
-    });
-}
-
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/appc/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/provisioner.hpp b/src/slave/containerizer/provisioner/appc/provisioner.hpp
deleted file mode 100644
index e4d5b8e..0000000
--- a/src/slave/containerizer/provisioner/appc/provisioner.hpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * 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 __APPC_PROVISIONER_HPP__
-#define __APPC_PROVISIONER_HPP__
-
-#include <list>
-#include <string>
-#include <vector>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-
-#include <stout/hashmap.hpp>
-#include <stout/json.hpp>
-#include <stout/nothing.hpp>
-#include <stout/try.hpp>
-
-#include "slave/containerizer/provisioner/provisioner.hpp"
-
-namespace mesos {
-namespace internal {
-namespace slave {
-namespace appc {
-
-// Forward declaration.
-class AppcProvisionerProcess;
-
-
-class AppcProvisioner : public Provisioner
-{
-public:
-  static Try<process::Owned<Provisioner>> create(
-      const Flags& flags,
-      Fetcher* fetcher);
-
-  ~AppcProvisioner();
-
-  virtual process::Future<Nothing> recover(
-      const std::list<mesos::slave::ContainerState>& states,
-      const hashset<ContainerID>& orphans);
-
-  virtual process::Future<std::string> provision(
-      const ContainerID& containerId,
-      const Image& image);
-
-  virtual process::Future<bool> destroy(const ContainerID& containerId);
-
-private:
-  explicit AppcProvisioner(process::Owned<AppcProvisionerProcess> process);
-
-  AppcProvisioner(const AppcProvisioner&); // Not copyable.
-  AppcProvisioner& operator=(const AppcProvisioner&); // Not assignable.
-
-  process::Owned<AppcProvisionerProcess> process;
-};
-
-} // namespace appc {
-} // namespace slave {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __APPC_PROVISIONER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/appc/store.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/store.cpp b/src/slave/containerizer/provisioner/appc/store.cpp
index 327ac9b..a5ef4ea 100644
--- a/src/slave/containerizer/provisioner/appc/store.cpp
+++ b/src/slave/containerizer/provisioner/appc/store.cpp
@@ -46,6 +46,8 @@ namespace appc {
 // Defines a locally cached image (which has passed validation).
 struct CachedImage
 {
+  static Try<CachedImage> create(const string& imagePath);
+
   CachedImage(
       const AppcImageManifest& _manifest,
       const string& _id,
@@ -69,6 +71,34 @@ struct CachedImage
 };
 
 
+Try<CachedImage> CachedImage::create(const string& imagePath)
+{
+  Option<Error> error = spec::validateLayout(imagePath);
+  if (error.isSome()) {
+    return Error("Invalid image layout: " + error.get().message);
+  }
+
+  string imageId = Path(imagePath).basename();
+
+  error = spec::validateImageID(imageId);
+  if (error.isSome()) {
+    return Error("Invalid image ID: " + error.get().message);
+  }
+
+  Try<string> read = os::read(paths::getImageManifestPath(imagePath));
+  if (read.isError()) {
+    return Error("Failed to read manifest: " + read.error());
+  }
+
+  Try<AppcImageManifest> manifest = spec::parse(read.get());
+  if (manifest.isError()) {
+    return Error("Failed to parse manifest: " + manifest.error());
+  }
+
+  return CachedImage(manifest.get(), imageId, imagePath);
+}
+
+
 // Helper that implements this:
 // https://github.com/appc/spec/blob/master/spec/aci.md#dependency-matching
 static bool matches(Image::Appc requirements, const CachedImage& candidate)
@@ -114,25 +144,25 @@ static bool matches(Image::Appc requirements, const CachedImage& candidate)
 class StoreProcess : public Process<StoreProcess>
 {
 public:
-  StoreProcess(const string& root);
+  StoreProcess(const string& rootDir);
 
   ~StoreProcess() {}
 
   Future<Nothing> recover();
 
-  Future<vector<string>> get(const Image::Appc& image);
+  Future<vector<string>> get(const Image& image);
 
 private:
   // Absolute path to the root directory of the store as defined by
   // --appc_store_dir.
-  const string root;
+  const string rootDir;
 
   // Mappings: name -> id -> image.
   hashmap<string, hashmap<string, CachedImage>> images;
 };
 
 
-Try<Owned<Store>> Store::create(const Flags& flags)
+Try<Owned<slave::Store>> Store::create(const Flags& flags)
 {
   Try<Nothing> mkdir = os::mkdir(paths::getImagesDir(flags.appc_store_dir));
   if (mkdir.isError()) {
@@ -141,17 +171,19 @@ Try<Owned<Store>> Store::create(const Flags& flags)
 
   // Make sure the root path is canonical so all image paths derived
   // from it are canonical too.
-  Result<string> root = os::realpath(flags.appc_store_dir);
-  if (!root.isSome()) {
+  Result<string> rootDir = os::realpath(flags.appc_store_dir);
+  if (!rootDir.isSome()) {
     // The above mkdir call recursively creates the store directory
     // if necessary so it cannot be None here.
-    CHECK_ERROR(root);
+    CHECK_ERROR(rootDir);
+
     return Error(
-        "Failed to get the realpath of the store directory: " + root.error());
+        "Failed to get the realpath of the store root directory: " +
+        rootDir.error());
   }
 
-  return Owned<Store>(new Store(
-      Owned<StoreProcess>(new StoreProcess(root.get()))));
+  return Owned<slave::Store>(new Store(
+      Owned<StoreProcess>(new StoreProcess(rootDir.get()))));
 }
 
 
@@ -175,92 +207,34 @@ Future<Nothing> Store::recover()
 }
 
 
-Future<vector<string>> Store::get(const Image::Appc& image)
+Future<vector<string>> Store::get(const Image& image)
 {
   return dispatch(process.get(), &StoreProcess::get, image);
 }
 
 
-StoreProcess::StoreProcess(const string& _root) : root(_root) {}
-
-
-// Implemented as a helper function because it's going to be used for a
-// newly downloaded image too.
-static Try<CachedImage> createImage(const string& imagePath)
-{
-  Option<Error> error = spec::validateLayout(imagePath);
-  if (error.isSome()) {
-    return Error("Invalid image layout: " + error.get().message);
-  }
-
-  string imageId = Path(imagePath).basename();
-
-  error = spec::validateImageID(imageId);
-  if (error.isSome()) {
-    return Error("Invalid image ID: " + error.get().message);
-  }
-
-  Try<string> read = os::read(paths::getImageManifestPath(imagePath));
-  if (read.isError()) {
-    return Error("Failed to read manifest: " + read.error());
-  }
-
-  Try<AppcImageManifest> manifest = spec::parse(read.get());
-  if (manifest.isError()) {
-    return Error("Failed to parse manifest: " + manifest.error());
-  }
-
-  return CachedImage(manifest.get(), imageId, imagePath);
-}
-
-
-Future<vector<string>> StoreProcess::get(const Image::Appc& image)
-{
-  if (!images.contains(image.name())) {
-    return Failure("No image named '" + image.name() + "' can be found");
-  }
-
-  // Get local candidates.
-  vector<CachedImage> candidates;
-  foreach (const CachedImage& candidate, images[image.name()].values()) {
-    // The first match is returned.
-    // TODO(xujyan): Some tie-breaking rules are necessary.
-    if (matches(image, candidate)) {
-      LOG(INFO) << "Found match for image '" << image.name()
-                << "' in the store";
-
-      // The Appc store current doesn't support dependencies and this is
-      // enforced by manifest validation: if the image's manifest contains
-      // dependencies it would fail the validation and wouldn't be stored
-      // in the store.
-      return vector<string>({candidate.rootfs()});
-    }
-  }
-
-  return Failure("No image named '" + image.name() +
-                 "' can match the requirements");
-}
+StoreProcess::StoreProcess(const string& _rootDir) : rootDir(_rootDir) {}
 
 
 Future<Nothing> StoreProcess::recover()
 {
   // Recover everything in the store.
-  Try<list<string>> imageIds = os::ls(paths::getImagesDir(root));
+  Try<list<string>> imageIds = os::ls(paths::getImagesDir(rootDir));
   if (imageIds.isError()) {
     return Failure(
         "Failed to list images under '" +
-        paths::getImagesDir(root) + "': " +
+        paths::getImagesDir(rootDir) + "': " +
         imageIds.error());
   }
 
   foreach (const string& imageId, imageIds.get()) {
-    string path = paths::getImagePath(root, imageId);
+    string path = paths::getImagePath(rootDir, imageId);
     if (!os::stat::isdir(path)) {
       LOG(WARNING) << "Unexpected entry in storage: " << imageId;
       continue;
     }
 
-    Try<CachedImage> image = createImage(path);
+    Try<CachedImage> image = CachedImage::create(path);
     if (image.isError()) {
       LOG(WARNING) << "Unexpected entry in storage: " << image.error();
       continue;
@@ -274,6 +248,40 @@ Future<Nothing> StoreProcess::recover()
   return Nothing();
 }
 
+
+Future<vector<string>> StoreProcess::get(const Image& image)
+{
+  if (image.type() != Image::APPC) {
+    return Failure("Not an Appc image: " + stringify(image.type()));
+  }
+
+  const Image::Appc& appc = image.appc();
+
+  if (!images.contains(appc.name())) {
+    return Failure("No Appc image named '" + appc.name() + "' can be found");
+  }
+
+  // Get local candidates.
+  vector<CachedImage> candidates;
+  foreach (const CachedImage& candidate, images[appc.name()].values()) {
+    // The first match is returned.
+    // TODO(xujyan): Some tie-breaking rules are necessary.
+    if (matches(appc, candidate)) {
+      LOG(INFO) << "Found match for Appc image '" << appc.name()
+                << "' in the store";
+
+      // The Appc store current doesn't support dependencies and this
+      // is enforced by manifest validation: if the image's manifest
+      // contains dependencies it would fail the validation and
+      // wouldn't be stored in the store.
+      return vector<string>({candidate.rootfs()});
+    }
+  }
+
+  return Failure("No Appc image named '" + appc.name() +
+                 "' can match the requirements");
+}
+
 } // namespace appc {
 } // namespace slave {
 } // namespace internal {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/appc/store.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/store.hpp b/src/slave/containerizer/provisioner/appc/store.hpp
index 07218d1..e845519 100644
--- a/src/slave/containerizer/provisioner/appc/store.hpp
+++ b/src/slave/containerizer/provisioner/appc/store.hpp
@@ -19,17 +19,7 @@
 #ifndef __PROVISIONER_APPC_STORE_HPP__
 #define __PROVISIONER_APPC_STORE_HPP__
 
-#include <string>
-#include <vector>
-
-#include <mesos/mesos.hpp>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-
-#include <stout/try.hpp>
-
-#include "slave/flags.hpp"
+#include "slave/containerizer/provisioner/store.hpp"
 
 namespace mesos {
 namespace internal {
@@ -40,44 +30,27 @@ namespace appc {
 class StoreProcess;
 
 
-// An image store abstraction that "stores" images. It serves as a read-through
-// cache (cache misses are fetched remotely and transparently) for images.
-// TODO(xujyan): The store currently keeps cached images indefinitely and we
-// should introduce cache eviction policies.
-class Store
+class Store : public slave::Store
 {
 public:
-  static Try<process::Owned<Store>> create(const Flags& flags);
+  static Try<process::Owned<slave::Store>> create(const Flags& flags);
 
   ~Store();
 
-  process::Future<Nothing> recover();
+  virtual process::Future<Nothing> recover();
 
-  // Get the specified image (and all its recursive dependencies) as a list
-  // of rootfs layers in the topological order (dependencies go before
-  // dependents in the list). The images required to build this list are
-  // either retrieved from the local cache or fetched remotely.
-  // NOTE: The returned list should not have duplicates. e.g., in the
-  // following scenario the result should be [C, B, D, A] (B before D in this
-  // example is decided by the order in which A specifies its dependencies).
-  //
-  // A --> B --> C
-  // |           ^
-  // |---> D ----|
-  //
-  // The returned future fails if the requested image or any of its
-  // dependencies cannot be found or failed to be fetched.
-  // TODO(xujyan): Fetching remotely is not implemented for now and until
-  // then the future fails directly if the image is not in the local cache.
-  // TODO(xujyan): The store currently doesn't support images that have
-  // dependencies and we should add it later.
-  process::Future<std::vector<std::string>> get(const Image::Appc& image);
+  // TODO(xujyan): Fetching remotely is not implemented for now and
+  // until then the future fails directly if the image is not in the
+  // local cache.
+  // TODO(xujyan): The store currently doesn't support images that
+  // have dependencies and we should add it later.
+  virtual process::Future<std::vector<std::string>> get(const Image& image);
 
 private:
   Store(process::Owned<StoreProcess> process);
 
-  Store(const Store&); // Not copyable.
-  Store& operator=(const Store&); // Not assignable.
+  Store(const Store&) = delete; // Not copyable.
+  Store& operator=(const Store&) = delete; // Not assignable.
 
   process::Owned<StoreProcess> process;
 };

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/paths.cpp b/src/slave/containerizer/provisioner/paths.cpp
index 601c51f..8d0f7d3 100644
--- a/src/slave/containerizer/provisioner/paths.cpp
+++ b/src/slave/containerizer/provisioner/paths.cpp
@@ -96,10 +96,10 @@ string getContainerRootfsDir(
 }
 
 
-Try<hashmap<ContainerID, string>> listContainers(
+Try<hashset<ContainerID>> listContainers(
     const string& provisionerDir)
 {
-  hashmap<ContainerID, string> results;
+  hashset<ContainerID> results;
 
   string containersDir = getContainersDir(provisionerDir);
   if (!os::exists(containersDir)) {
@@ -124,18 +124,18 @@ Try<hashmap<ContainerID, string>> listContainers(
 
     ContainerID containerId;
     containerId.set_value(entry);
-    results.put(containerId, containerPath);
+    results.insert(containerId);
   }
 
   return results;
 }
 
 
-Try<hashmap<string, hashmap<string, string>>> listContainerRootfses(
+Try<hashmap<string, hashset<string>>> listContainerRootfses(
     const string& provisionerDir,
     const ContainerID& containerId)
 {
-  hashmap<string, hashmap<string, string>> results;
+  hashmap<string, hashset<string>> results;
 
   string backendsDir = getBackendsDir(
       getContainerDir(
@@ -159,7 +159,7 @@ Try<hashmap<string, hashmap<string, string>>> listContainerRootfses(
       return Error("Unable to list the backend directory: " + rootfses.error());
     }
 
-    hashmap<string, string> backendResults;
+    hashset<string> backendResults;
 
     foreach (const string& rootfsId, rootfses.get()) {
       string rootfs = getRootfsDir(getRootfsesDir(backendDir), rootfsId);
@@ -169,7 +169,7 @@ Try<hashmap<string, hashmap<string, string>>> listContainerRootfses(
         continue;
       }
 
-      backendResults.put(rootfsId, rootfs);
+      backendResults.insert(rootfsId);
     }
 
     if (backendResults.empty()) {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/paths.hpp b/src/slave/containerizer/provisioner/paths.hpp
index 7ec8fee..7ebd36d 100644
--- a/src/slave/containerizer/provisioner/paths.hpp
+++ b/src/slave/containerizer/provisioner/paths.hpp
@@ -24,6 +24,7 @@
 #include <mesos/mesos.hpp>
 
 #include <stout/hashmap.hpp>
+#include <stout/hashset.hpp>
 #include <stout/try.hpp>
 
 namespace mesos {
@@ -35,17 +36,13 @@ namespace paths {
 // The provisioner rootfs directory is as follows:
 // <work_dir> ('--work_dir' flag)
 // |-- provisioner
-//     |-- <image_type> (APPC, DOCKER, etc.)
-//         |-- containers
-//             |-- <container_id>
-//                 |-- backends
-//                     |-- <backend> (copy, bind, etc.)
-//                         |-- rootfses
-//                             |-- <rootfs_id> (the rootfs)
+//     |-- containers
+//         |-- <container_id>
+//             |-- backends
+//                 |-- <backend> (copy, bind, etc.)
+//                     |-- rootfses
+//                         |-- <rootfs_id> (the rootfs)
 //
-// NOTE: Each container could have multiple image types, therefore the
-// same <container_id> directory can be under other directories (e.g.,
-// <work_dir>/provisioner/DOCKER, <work_dir>/provisioner/APPC, etc.).
 // There can be multiple backends due to the change of backend flags.
 // Under each backend a rootfs is identified by the 'rootfs_id' which
 // is a UUID.
@@ -63,15 +60,15 @@ std::string getContainerRootfsDir(
 
 
 // Recursively "ls" the container directory and return a map of
-// backend -> rootfsId -> rootfsPath.
-Try<hashmap<std::string, hashmap<std::string, std::string>>>
+// backend -> {rootfsId, ...}
+Try<hashmap<std::string, hashset<std::string>>>
 listContainerRootfses(
     const std::string& provisionerDir,
     const ContainerID& containerId);
 
 
-// Return a map of containerId -> containerPath;
-Try<hashmap<ContainerID, std::string>> listContainers(
+// Return a set of container IDs.
+Try<hashset<ContainerID>> listContainers(
     const std::string& provisionerDir);
 
 } // namespace paths {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/provisioner.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/provisioner.cpp b/src/slave/containerizer/provisioner/provisioner.cpp
index cb751dc..213f8a6 100644
--- a/src/slave/containerizer/provisioner/provisioner.cpp
+++ b/src/slave/containerizer/provisioner/provisioner.cpp
@@ -16,62 +16,396 @@
  * limitations under the License.
  */
 
+#include <mesos/type_utils.hpp>
+
+#include <process/collect.hpp>
+#include <process/defer.hpp>
+#include <process/dispatch.hpp>
+#include <process/process.hpp>
+
+#include <stout/foreach.hpp>
+#include <stout/hashmap.hpp>
 #include <stout/hashset.hpp>
+#include <stout/os.hpp>
 #include <stout/stringify.hpp>
-#include <stout/strings.hpp>
+#include <stout/uuid.hpp>
 
-#include "slave/containerizer/provisioner/provisioner.hpp"
+#include "slave/paths.hpp"
 
-#include "slave/containerizer/provisioner/appc/provisioner.hpp"
+#include "slave/containerizer/provisioner/backend.hpp"
+#include "slave/containerizer/provisioner/paths.hpp"
+#include "slave/containerizer/provisioner/provisioner.hpp"
+#include "slave/containerizer/provisioner/store.hpp"
 
 using namespace process;
 
+using std::list;
 using std::string;
+using std::vector;
+
+using mesos::slave::ContainerState;
 
 namespace mesos {
 namespace internal {
 namespace slave {
 
-Try<hashmap<Image::Type, Owned<Provisioner>>> Provisioner::create(
+class ProvisionerProcess : public Process<ProvisionerProcess>
+{
+public:
+  ProvisionerProcess(
+      const Flags& flags,
+      const string& rootDir,
+      const hashmap<Image::Type, Owned<Store>>& stores,
+      const hashmap<string, Owned<Backend>>& backends);
+
+  Future<Nothing> recover(
+      const list<ContainerState>& states,
+      const hashset<ContainerID>& orphans);
+
+  Future<string> provision(
+      const ContainerID& containerId,
+      const Image& image);
+
+  Future<bool> destroy(const ContainerID& containerId);
+
+private:
+  Future<string> _provision(
+      const ContainerID& containerId,
+      const vector<string>& layers);
+
+  const Flags flags;
+
+  // Absolute path to the provisioner root directory. It can be
+  // derived from '--work_dir' but we keep a separate copy here
+  // because we converted it into an absolute path so managed rootfs
+  // paths match the ones in 'mountinfo' (important if mount-based
+  // backends are used).
+  const string rootDir;
+
+  const hashmap<Image::Type, Owned<Store>> stores;
+  const hashmap<string, Owned<Backend>> backends;
+
+  struct Info
+  {
+    // Mappings: backend -> {rootfsId, ...}
+    hashmap<string, hashset<string>> rootfses;
+  };
+
+  hashmap<ContainerID, Owned<Info>> infos;
+};
+
+
+Try<Owned<Provisioner>> Provisioner::create(
     const Flags& flags,
     Fetcher* fetcher)
 {
-  if (flags.provisioners.isNone()) {
-    return hashmap<Image::Type, Owned<Provisioner>>();
+  string _rootDir = slave::paths::getProvisionerDir(flags.work_dir);
+
+  Try<Nothing> mkdir = os::mkdir(_rootDir);
+  if (mkdir.isError()) {
+    return Error(
+        "Failed to create provisioner root directory '" +
+        _rootDir + "': " + mkdir.error());
+  }
+
+  Result<string> rootDir = os::realpath(_rootDir);
+  if (rootDir.isError()) {
+    return Error(
+        "Failed to resolve the realpath of provisioner root directory '" +
+        _rootDir + "': " + rootDir.error());
+  }
+
+  CHECK_SOME(rootDir); // Can't be None since we just created it.
+
+  Try<hashmap<Image::Type, Owned<Store>>> stores = Store::create(flags);
+  if (stores.isError()) {
+    return Error("Failed to create image stores: " + stores.error());
+  }
+
+  hashmap<string, Owned<Backend>> backends = Backend::create(flags);
+  if (backends.empty()) {
+    return Error("No usable provisioner backend created");
+  }
+
+  if (!backends.contains(flags.image_provisioner_backend)) {
+    return Error(
+        "The specified provisioner backend '" +
+        flags.image_provisioner_backend + "' is unsupported");
+  }
+
+  return Owned<Provisioner>(new Provisioner(
+      Owned<ProvisionerProcess>(new ProvisionerProcess(
+          flags,
+          rootDir.get(),
+          stores.get(),
+          backends))));
+}
+
+
+Provisioner::Provisioner(Owned<ProvisionerProcess> _process)
+  : process(_process)
+{
+  spawn(CHECK_NOTNULL(process.get()));
+}
+
+
+Provisioner::~Provisioner()
+{
+  if (process.get() != NULL) {
+    terminate(process.get());
+    wait(process.get());
+  }
+}
+
+
+Future<Nothing> Provisioner::recover(
+    const list<ContainerState>& states,
+    const hashset<ContainerID>& orphans)
+{
+  return dispatch(
+      CHECK_NOTNULL(process.get()),
+      &ProvisionerProcess::recover,
+      states,
+      orphans);
+}
+
+
+Future<string> Provisioner::provision(
+    const ContainerID& containerId,
+    const Image& image)
+{
+  return dispatch(
+      CHECK_NOTNULL(process.get()),
+      &ProvisionerProcess::provision,
+      containerId,
+      image);
+}
+
+
+Future<bool> Provisioner::destroy(const ContainerID& containerId)
+{
+  return dispatch(
+      CHECK_NOTNULL(process.get()),
+      &ProvisionerProcess::destroy,
+      containerId);
+}
+
+
+ProvisionerProcess::ProvisionerProcess(
+    const Flags& _flags,
+    const string& _rootDir,
+    const hashmap<Image::Type, Owned<Store>>& _stores,
+    const hashmap<string, Owned<Backend>>& _backends)
+  : flags(_flags),
+    rootDir(_rootDir),
+    stores(_stores),
+    backends(_backends) {}
+
+
+Future<Nothing> ProvisionerProcess::recover(
+    const list<ContainerState>& states,
+    const hashset<ContainerID>& orphans)
+{
+  // Register living containers, including the ones that do not
+  // provision images.
+  hashset<ContainerID> alive;
+  foreach (const ContainerState& state, states) {
+    alive.insert(state.container_id());
   }
 
-  hashmap<Image::Type,
-          Try<Owned<Provisioner>>(*)(const Flags&, Fetcher*)> creators;
+  // List provisioned containers; recover living ones; destroy unknown
+  // orphans. Note that known orphan containers are recovered as well
+  // and they will be destroyed by the containerizer using the normal
+  // cleanup path. See MESOS-2367 for details.
+  Try<hashset<ContainerID>> containers =
+    provisioner::paths::listContainers(rootDir);
 
-  // Register all supported creators.
-  creators.put(Image::APPC, &appc::AppcProvisioner::create);
+  if (containers.isError()) {
+    return Failure(
+        "Failed to list the containers managed by the provisioner: " +
+        containers.error());
+  }
+
+  // Scan the list of containers, register all of them with 'infos'
+  // but mark unknown orphans for immediate cleanup.
+  hashset<ContainerID> unknownOrphans;
+
+  foreach (const ContainerID& containerId, containers.get()) {
+    Owned<Info> info = Owned<Info>(new Info());
 
-  hashmap<Image::Type, Owned<Provisioner>> provisioners;
+    Try<hashmap<string, hashset<string>>> rootfses =
+      provisioner::paths::listContainerRootfses(rootDir, containerId);
 
-  // NOTE: Change in '--provisioners' flag may result in leaked rootfs
-  // files on the disk but it's at least safe because files managed by
-  // different provisioners are totally separated.
-  foreach (const string& type,
-           strings::tokenize(flags.provisioners.get(), ",")) {
-     Image::Type imageType;
-     if (!Image::Type_Parse(strings::upper(type), &imageType)) {
-       return Error("Unknown provisioner '" + type + "'");
-     }
+    if (rootfses.isError()) {
+      return Failure(
+          "Unable to list rootfses belonged to container " +
+          stringify(containerId) + ": " + rootfses.error());
+    }
 
-     if (!creators.contains(imageType)) {
-       return Error("Unsupported provisioner '" + type + "'");
-     }
+    foreachkey (const string& backend, rootfses.get()) {
+      if (!backends.contains(backend)) {
+        return Failure(
+            "Found rootfses managed by an unrecognized backend: " + backend);
+      }
 
-     Try<Owned<Provisioner>> provisioner = creators[imageType](flags, fetcher);
-     if (provisioner.isError()) {
-       return Error("Failed to create '" + stringify(imageType) +
-                    "' provisioner: " + provisioner.error());
-     }
+      info->rootfses.put(backend, rootfses.get()[backend]);
+    }
+
+    infos.put(containerId, info);
+
+    if (alive.contains(containerId) || orphans.contains(containerId)) {
+      LOG(INFO) << "Recovered container " << containerId;
+      continue;
+    } else {
+      // For immediate cleanup below.
+      unknownOrphans.insert(containerId);
+    }
+  }
 
-     provisioners[imageType] = provisioner.get();
+  // Cleanup unknown orphan containers' rootfses.
+  list<Future<bool>> cleanups;
+  foreach (const ContainerID& containerId, unknownOrphans) {
+    LOG(INFO) << "Cleaning up unknown orphan container " << containerId;
+    cleanups.push_back(destroy(containerId));
   }
 
-  return provisioners;
+  Future<Nothing> cleanup = collect(cleanups)
+    .then([]() -> Future<Nothing> { return Nothing(); });
+
+  // Recover stores.
+  list<Future<Nothing>> recovers;
+  foreachvalue (const Owned<Store>& store, stores) {
+    recovers.push_back(store->recover());
+  }
+
+  Future<Nothing> recover = collect(recovers)
+    .then([]() -> Future<Nothing> { return Nothing(); });
+
+  // A successful provisioner recovery depends on:
+  // 1) Recovery of living containers and known orphans (done above).
+  // 2) Successful cleanup of unknown orphans.
+  // 3) Successful store recovery.
+  //
+  // TODO(jieyu): Do not recover 'store' before unknown orphans are
+  // cleaned up. In the future, we may want to cleanup unused rootfses
+  // in 'store', which might fail if there still exist unknown orphans
+  // holding references to them.
+  return collect(cleanup, recover)
+    .then([=]() -> Future<Nothing> {
+      LOG(INFO) << "Provisioner recovery complete";
+      return Nothing();
+    });
+}
+
+
+Future<string> ProvisionerProcess::provision(
+    const ContainerID& containerId,
+    const Image& image)
+{
+  if (!stores.contains(image.type())) {
+    return Failure(
+        "Unsupported container image type: " +
+        stringify(image.type()));
+  }
+
+  // Get and then provision image layers from the store.
+  return stores.get(image.type()).get()->get(image)
+    .then(defer(self(), &Self::_provision, containerId, lambda::_1));
+}
+
+
+Future<string> ProvisionerProcess::_provision(
+    const ContainerID& containerId,
+    const vector<string>& layers)
+{
+  // TODO(jieyu): Choose a backend smartly. For instance, if there is
+  // only one layer returned from the store. prefer to use bind
+  // backend because it's the simplest.
+  const string& backend = flags.image_provisioner_backend;
+  CHECK(backends.contains(backend));
+
+  string rootfsId = UUID::random().toString();
+
+  string rootfs = provisioner::paths::getContainerRootfsDir(
+      rootDir,
+      containerId,
+      backend,
+      rootfsId);
+
+  LOG(INFO) << "Provisioning image rootfs '" << rootfs
+            << "' for container " << containerId;
+
+  // NOTE: It's likely that the container ID already exists in 'infos'
+  // because one container might provision multiple images.
+  if (!infos.contains(containerId)) {
+    infos.put(containerId, Owned<Info>(new Info()));
+  }
+
+  infos[containerId]->rootfses[backend].insert(rootfsId);
+
+  return backends.get(backend).get()->provision(layers, rootfs)
+    .then([rootfs]() -> Future<string> { return rootfs; });
+}
+
+
+Future<bool> ProvisionerProcess::destroy(const ContainerID& containerId)
+{
+  if (!infos.contains(containerId)) {
+    LOG(INFO) << "Ignoring destroy request for unknown container "
+              << containerId;
+
+    return false;
+  }
+
+  // Unregister the container first. If destroy() fails, we can rely
+  // on recover() to retry it later.
+  Owned<Info> info = infos[containerId];
+  infos.erase(containerId);
+
+  list<Future<bool>> futures;
+  foreachkey (const string& backend, info->rootfses) {
+    if (!backends.contains(backend)) {
+      return Failure("Unknown backend '" + backend + "'");
+    }
+
+    foreach (const string& rootfsId, info->rootfses[backend]) {
+      string rootfs = provisioner::paths::getContainerRootfsDir(
+          rootDir,
+          containerId,
+          backend,
+          rootfsId);
+
+      LOG(INFO) << "Destroying container rootfs at '" << rootfs
+                << "' for container " << containerId;
+
+      futures.push_back(backends.get(backend).get()->destroy(rootfs));
+    }
+  }
+
+  // NOTE: We calculate 'containerDir' here so that the following
+  // lambda does not need to bind 'this'.
+  string containerDir =
+    provisioner::paths::getContainerDir(rootDir, containerId);
+
+  // TODO(xujyan): Revisit the usefulness of this return value.
+  return collect(futures)
+    .then([containerDir]() -> Future<bool> {
+      // This should be fairly cheap as the directory should only
+      // contain a few empty sub-directories at this point.
+      //
+      // TODO(jieyu): Currently, it's possible that some directories
+      // cannot be removed due to EBUSY. EBUSY is caused by the race
+      // between cleaning up this container and new containers copying
+      // the host mount table. It's OK to ignore them. The cleanup
+      // will be retried during slave recovery.
+      Try<Nothing> rmdir = os::rmdir(containerDir);
+      if (rmdir.isError()) {
+        LOG(ERROR) << "Failed to remove the provisioned container directory "
+                   << "at '" << containerDir << "': " << rmdir.error();
+      }
+
+      return true;
+    });
 }
 
 } // namespace slave {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/provisioner.hpp b/src/slave/containerizer/provisioner/provisioner.hpp
index c55225e..912fc5a 100644
--- a/src/slave/containerizer/provisioner/provisioner.hpp
+++ b/src/slave/containerizer/provisioner/provisioner.hpp
@@ -25,7 +25,6 @@
 
 #include <mesos/slave/isolator.hpp> // For ContainerState.
 
-#include <stout/hashmap.hpp>
 #include <stout/nothing.hpp>
 #include <stout/try.hpp>
 
@@ -40,15 +39,20 @@ namespace mesos {
 namespace internal {
 namespace slave {
 
+// Forward declaration.
+class ProvisionerProcess;
+
+
 class Provisioner
 {
 public:
-  virtual ~Provisioner() {}
+  // Create the provisioner based on the specified flags.
+  static Try<process::Owned<Provisioner>> create(
+      const Flags& flags,
+      Fetcher* fetcher);
 
-  // Create provisioners based on specified flags. An error is returned if
-  // any of the provisioners specified in --provisioner failed to be created.
-  static Try<hashmap<Image::Type, process::Owned<Provisioner>>>
-    create(const Flags& flags, Fetcher* fetcher);
+  // NOTE: Made 'virtual' for mocking and testing.
+  virtual ~Provisioner();
 
   // Recover root filesystems for containers from the run states and
   // the orphan containers (known to the launcher but not known to the
@@ -57,19 +61,30 @@ public:
   // directories) to not leak anything.
   virtual process::Future<Nothing> recover(
       const std::list<mesos::slave::ContainerState>& states,
-      const hashset<ContainerID>& orphans) = 0;
+      const hashset<ContainerID>& orphans);
 
   // Provision a root filesystem for the container using the specified
   // image and return the absolute path to the root filesystem.
   virtual process::Future<std::string> provision(
       const ContainerID& containerId,
-      const Image& image) = 0;
+      const Image& image);
 
   // Destroy a previously provisioned root filesystem. Assumes that
   // all references (e.g., mounts, open files) to the provisioned
   // filesystem have been removed. Return false if there is no
   // provisioned root filesystem for the given container.
-  virtual process::Future<bool> destroy(const ContainerID& containerId) = 0;
+  virtual process::Future<bool> destroy(const ContainerID& containerId);
+
+protected:
+  Provisioner() {} // For creating mock object.
+
+private:
+  explicit Provisioner(process::Owned<ProvisionerProcess> process);
+
+  Provisioner(const Provisioner&) = delete; // Not copyable.
+  Provisioner& operator=(const Provisioner&) = delete; // Not assignable.
+
+  process::Owned<ProvisionerProcess> process;
 };
 
 } // namespace slave {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/store.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/store.cpp b/src/slave/containerizer/provisioner/store.cpp
new file mode 100644
index 0000000..35d1199
--- /dev/null
+++ b/src/slave/containerizer/provisioner/store.cpp
@@ -0,0 +1,76 @@
+/**
+ * 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 <string>
+
+#include <mesos/type_utils.hpp>
+
+#include <stout/error.hpp>
+#include <stout/foreach.hpp>
+#include <stout/strings.hpp>
+
+#include "slave/containerizer/provisioner/store.hpp"
+
+#include "slave/containerizer/provisioner/appc/store.hpp"
+
+using namespace process;
+
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+Try<hashmap<Image::Type, Owned<Store>>> Store::create(const Flags& flags)
+{
+  if (flags.image_providers.isNone()) {
+    return hashmap<Image::Type, Owned<Store>>();
+  }
+
+  hashmap<Image::Type, Try<Owned<Store>>(*)(const Flags&)> creators;
+  creators.put(Image::APPC, &appc::Store::create);
+
+  hashmap<Image::Type, Owned<Store>> stores;
+
+  foreach (const string& type,
+           strings::tokenize(flags.image_providers.get(), ",")) {
+    Image::Type imageType;
+    if (!Image::Type_Parse(strings::upper(type), &imageType)) {
+      return Error("Unknown image type '" + type + "'");
+    }
+
+    if (!creators.contains(imageType)) {
+      return Error("Unsupported image type '" + type + "'");
+    }
+
+    Try<Owned<Store>> store = creators[imageType](flags);
+    if (store.isError()) {
+      return Error(
+          "Failed to create store for image type '" +
+          type + "': " + store.error());
+    }
+
+    stores.put(imageType, store.get());
+  }
+
+  return stores;
+}
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/containerizer/provisioner/store.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/store.hpp b/src/slave/containerizer/provisioner/store.hpp
new file mode 100644
index 0000000..cf3e7d7
--- /dev/null
+++ b/src/slave/containerizer/provisioner/store.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 __PROVISIONER_STORE_HPP__
+#define __PROVISIONER_STORE_HPP__
+
+#include <string>
+#include <vector>
+
+#include <mesos/mesos.hpp>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+
+#include <stout/try.hpp>
+
+#include "slave/flags.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+// An image store abstraction that "stores" images. It serves as a
+// read-through cache (cache misses are fetched remotely and
+// transparently) for images.
+class Store
+{
+public:
+  static Try<hashmap<Image::Type, process::Owned<Store>>> create(
+      const Flags& flags);
+
+  virtual ~Store() {}
+
+  virtual process::Future<Nothing> recover() = 0;
+
+  // Get the specified image (and all its recursive dependencies) as a
+  // list of rootfs layers in the topological order (dependencies go
+  // before dependents in the list). The images required to build this
+  // list are either retrieved from the local cache or fetched
+  // remotely.
+  //
+  // NOTE: The returned list should not have duplicates. e.g., in the
+  // following scenario the result should be [C, B, D, A] (B before D
+  // in this example is decided by the order in which A specifies its
+  // dependencies).
+  //
+  // A --> B --> C
+  // |           ^
+  // |---> D ----|
+  //
+  // The returned future fails if the requested image or any of its
+  // dependencies cannot be found or failed to be fetched.
+  virtual process::Future<std::vector<std::string>> get(const Image& image) = 0;
+};
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __PROVISIONER_STORE_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/flags.cpp
----------------------------------------------------------------------
diff --git a/src/slave/flags.cpp b/src/slave/flags.cpp
index ff167ec..add4196 100644
--- a/src/slave/flags.cpp
+++ b/src/slave/flags.cpp
@@ -59,21 +59,22 @@ mesos::internal::slave::Flags::Flags()
       "for the Mesos Containerizer.",
       "posix/cpu,posix/mem");
 
-  add(&Flags::provisioners,
-      "provisioners",
-      "Comma separated list of image rootfs provisioners,\n"
-      "e.g., appc,docker");
+  add(&Flags::image_providers,
+      "image_providers",
+      "Comma separated list of supported image providers,\n"
+      "e.g., 'APPC,DOCKER'.");
+
+  add(&Flags::image_provisioner_backend,
+      "image_provisioner_backend",
+      "Strategy for provisioning container rootfs from images,\n"
+      "e.g., 'bind', 'copy'.",
+      "copy");
 
   add(&Flags::appc_store_dir,
       "appc_store_dir",
-      "Directory the appc provisioner will store images in",
+      "Directory the appc provisioner will store images in.",
       "/tmp/mesos/store/appc");
 
-  add(&Flags::appc_provisioner_backend,
-      "appc_provisioner_backend",
-      "Strategy for provisioning container rootfs from appc images",
-      "copy");
-
   add(&Flags::default_role,
       "default_role",
       "Any resources in the --resources flag that\n"

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/flags.hpp
----------------------------------------------------------------------
diff --git a/src/slave/flags.hpp b/src/slave/flags.hpp
index 799c963..e31a418 100644
--- a/src/slave/flags.hpp
+++ b/src/slave/flags.hpp
@@ -47,10 +47,9 @@ public:
   Option<std::string> resources;
   std::string isolation;
 
-  Option<std::string> provisioners;
-
+  Option<std::string> image_providers;
+  std::string image_provisioner_backend;
   std::string appc_store_dir;
-  std::string appc_provisioner_backend;
 
   std::string default_role;
   Option<std::string> attributes;

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/paths.cpp b/src/slave/paths.cpp
index 26b7f88..fb77e64 100644
--- a/src/slave/paths.cpp
+++ b/src/slave/paths.cpp
@@ -68,6 +68,12 @@ string getSandboxRootDir(const string& rootDir)
 }
 
 
+string getProvisionerDir(const string& rootDir)
+{
+  return path::join(rootDir, "provisioner");
+}
+
+
 string getArchiveDir(const string& rootDir)
 {
   return path::join(rootDir, "archive");
@@ -369,17 +375,6 @@ string getPersistentVolumePath(
 }
 
 
-string getProvisionerDir(
-    const string& rootDir,
-    const Image::Type& imageType)
-{
-  return path::join(
-      rootDir,
-      "provisioner",
-      stringify(imageType));
-}
-
-
 string createExecutorDirectory(
     const string& rootDir,
     const SlaveID& slaveId,

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/slave/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/paths.hpp b/src/slave/paths.hpp
index 8873e78..f743fb4 100644
--- a/src/slave/paths.hpp
+++ b/src/slave/paths.hpp
@@ -90,8 +90,7 @@ namespace paths {
 //   |   |-- roles
 //   |       |-- <role>
 //   |           |-- <persistence_id> (persistent volume)
-//   |-- provisioners
-//       |-- <image_type> (as defined in Image::Type)
+//   |-- provisioner
 
 const char LATEST_SYMLINK[] = "latest";
 
@@ -103,6 +102,9 @@ std::string getMetaRootDir(const std::string& rootDir);
 std::string getSandboxRootDir(const std::string& rootDir);
 
 
+std::string getProvisionerDir(const std::string& rootDir);
+
+
 std::string getArchiveDir(const std::string& rootDir);
 
 
@@ -264,12 +266,6 @@ std::string getPersistentVolumePath(
     const std::string& persistenceId);
 
 
-// Contents of the provisioner directory are managed by the provisioner itself.
-std::string getProvisionerDir(
-    const std::string& rootDir,
-    const Image::Type& imageType);
-
-
 std::string createExecutorDirectory(
     const std::string& rootDir,
     const SlaveID& slaveId,

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/tests/containerizer/filesystem_isolator_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/filesystem_isolator_tests.cpp b/src/tests/containerizer/filesystem_isolator_tests.cpp
index ffa371f..4dbfeac 100644
--- a/src/tests/containerizer/filesystem_isolator_tests.cpp
+++ b/src/tests/containerizer/filesystem_isolator_tests.cpp
@@ -92,14 +92,10 @@ public:
       rootfses.put(imageName, rootfs.get().share());
     }
 
-    // Create the TestAppcProvisioner for the above root filesystems.
-    hashmap<Image::Type, Owned<Provisioner>> provisioners;
-    provisioners.put(
-        Image::APPC,
-        Owned<Provisioner>(new TestAppcProvisioner(rootfses)));
+    Owned<Provisioner> provisioner(new TestProvisioner(rootfses));
 
     Try<Isolator*> _isolator =
-      LinuxFilesystemIsolatorProcess::create(flags, provisioners);
+      LinuxFilesystemIsolatorProcess::create(flags, provisioner);
 
     if (_isolator.isError()) {
       return Error(

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/tests/containerizer/provisioner.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/provisioner.hpp b/src/tests/containerizer/provisioner.hpp
index 3ae2400..54aab5f 100644
--- a/src/tests/containerizer/provisioner.hpp
+++ b/src/tests/containerizer/provisioner.hpp
@@ -34,10 +34,10 @@ namespace mesos {
 namespace internal {
 namespace tests {
 
-class TestAppcProvisioner : public slave::Provisioner
+class TestProvisioner : public slave::Provisioner
 {
 public:
-  TestAppcProvisioner(
+  TestProvisioner(
       const hashmap<std::string, process::Shared<Rootfs>>& _rootfses)
     : rootfses(_rootfses)
   {
@@ -46,17 +46,17 @@ public:
     using testing::Invoke;
 
     ON_CALL(*this, recover(_, _))
-      .WillByDefault(Invoke(this, &TestAppcProvisioner::unmocked_recover));
+      .WillByDefault(Invoke(this, &TestProvisioner::unmocked_recover));
     EXPECT_CALL(*this, recover(_, _))
       .WillRepeatedly(DoDefault());
 
     ON_CALL(*this, provision(_, _))
-      .WillByDefault(Invoke(this, &TestAppcProvisioner::unmocked_provision));
+      .WillByDefault(Invoke(this, &TestProvisioner::unmocked_provision));
     EXPECT_CALL(*this, provision(_, _))
       .WillRepeatedly(DoDefault());
 
     ON_CALL(*this, destroy(_))
-      .WillByDefault(Invoke(this, &TestAppcProvisioner::unmocked_destroy));
+      .WillByDefault(Invoke(this, &TestProvisioner::unmocked_destroy));
     EXPECT_CALL(*this, destroy(_))
       .WillRepeatedly(DoDefault());
   }

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/tests/containerizer/provisioner_appc_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/provisioner_appc_tests.cpp b/src/tests/containerizer/provisioner_appc_tests.cpp
index 3318557..bd9b2e8 100644
--- a/src/tests/containerizer/provisioner_appc_tests.cpp
+++ b/src/tests/containerizer/provisioner_appc_tests.cpp
@@ -29,6 +29,9 @@
 #include <stout/stringify.hpp>
 #include <stout/uuid.hpp>
 
+#include "slave/paths.hpp"
+
+#include "slave/containerizer/provisioner/paths.hpp"
 #include "slave/containerizer/provisioner/provisioner.hpp"
 
 #include "slave/containerizer/provisioner/appc/spec.hpp"
@@ -51,10 +54,10 @@ namespace mesos {
 namespace internal {
 namespace tests {
 
-class AppcProvisionerTest : public TemporaryDirectoryTest {};
+class AppcSpecTest : public TemporaryDirectoryTest {};
 
 
-TEST_F(AppcProvisionerTest, ValidateImageManifest)
+TEST_F(AppcSpecTest, ValidateImageManifest)
 {
   JSON::Value manifest = JSON::parse(
       "{"
@@ -97,7 +100,7 @@ TEST_F(AppcProvisionerTest, ValidateImageManifest)
 }
 
 
-TEST_F(AppcProvisionerTest, ValidateLayout)
+TEST_F(AppcSpecTest, ValidateLayout)
 {
   string image = os::getcwd();
 
@@ -120,12 +123,15 @@ TEST_F(AppcProvisionerTest, ValidateLayout)
 }
 
 
-TEST_F(AppcProvisionerTest, StoreRecover)
+class AppcStoreTest : public TemporaryDirectoryTest {};
+
+
+TEST_F(AppcStoreTest, Recover)
 {
   // Create store.
   slave::Flags flags;
   flags.appc_store_dir = path::join(os::getcwd(), "store");
-  Try<Owned<Store>> store = Store::create(flags);
+  Try<Owned<slave::Store>> store = Store::create(flags);
   ASSERT_SOME(store);
 
   // Create a simple image in the store:
@@ -161,8 +167,8 @@ TEST_F(AppcProvisionerTest, StoreRecover)
       "  ]"
       "}").get();
 
-  // The 'imageId' below has the correct format but it's not computed by
-  // hashing the tarball of the image. It's OK here as we assume
+  // The 'imageId' below has the correct format but it's not computed
+  // by hashing the tarball of the image. It's OK here as we assume
   // the images under 'images' have passed such check when they are
   // downloaded and validated.
   string imageId =
@@ -182,7 +188,7 @@ TEST_F(AppcProvisionerTest, StoreRecover)
 
   Image image;
   image.mutable_appc()->set_name("foo.com/bar");
-  Future<vector<string>> layers = store.get()->get(image.appc());
+  Future<vector<string>> layers = store.get()->get(image);
   AWAIT_READY(layers);
 
   EXPECT_EQ(1u, layers.get().size());
@@ -193,23 +199,25 @@ TEST_F(AppcProvisionerTest, StoreRecover)
 }
 
 
+class ProvisionerAppcTest : public TemporaryDirectoryTest {};
+
+
 #ifdef __linux__
-// This test verifies that the provisioner can provision an rootfs from an
-// image that is already put into the store directory.
-TEST_F(AppcProvisionerTest, ROOT_Provision)
+// This test verifies that the provisioner can provision an rootfs
+// from an image that is already put into the store directory.
+TEST_F(ProvisionerAppcTest, ROOT_Provision)
 {
   // Create provisioner.
   slave::Flags flags;
+  flags.image_providers = "APPC";
   flags.appc_store_dir = path::join(os::getcwd(), "store");
-  flags.appc_provisioner_backend = "bind";
-  flags.provisioners = "appc";
+  flags.image_provisioner_backend = "bind";
   flags.work_dir = "work_dir";
 
   Fetcher fetcher;
-  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners =
-    Provisioner::create(flags, &fetcher);
-  ASSERT_SOME(provisioners);
-  ASSERT_TRUE(provisioners.get().contains(Image::APPC));
+
+  Try<Owned<Provisioner>> provisioner = Provisioner::create(flags, &fetcher);
+  ASSERT_SOME(provisioner);
 
   // Create a simple image in the store:
   // <store>
@@ -244,8 +252,8 @@ TEST_F(AppcProvisionerTest, ROOT_Provision)
       "  ]"
       "}").get();
 
-  // The 'imageId' below has the correct format but it's not computed by
-  // hashing the tarball of the image. It's OK here as we assume
+  // The 'imageId' below has the correct format but it's not computed
+  // by hashing the tarball of the image. It's OK here as we assume
   // the images under 'images' have passed such check when they are
   // downloaded and validated.
   string imageId =
@@ -261,7 +269,7 @@ TEST_F(AppcProvisionerTest, ROOT_Provision)
       os::write(path::join(imagePath, "manifest"), stringify(manifest)));
 
   // Recover. This is when the image in the store is loaded.
-  AWAIT_READY(provisioners.get()[Image::APPC]->recover({}, {}));
+  AWAIT_READY(provisioner.get()->recover({}, {}));
 
   // Simulate a task that requires an image.
   Image image;
@@ -270,30 +278,30 @@ TEST_F(AppcProvisionerTest, ROOT_Provision)
   ContainerID containerId;
   containerId.set_value("12345");
 
-  Future<string> rootfs =
-    provisioners.get()[Image::APPC]->provision(containerId, image);
+  Future<string> rootfs = provisioner.get()->provision(containerId, image);
   AWAIT_READY(rootfs);
 
-  string containerDir = path::join(
-      flags.work_dir,
-      "provisioners",
-      stringify(Image::APPC),
-      "containers",
-      containerId.value());
+  string provisionerDir = slave::paths::getProvisionerDir(flags.work_dir);
 
-  Try<list<string>> rootfses = os::ls(path::join(
-      containerDir,
-      "backends",
-      flags.appc_provisioner_backend,
-      "rootfses"));
+  string containerDir =
+    slave::provisioner::paths::getContainerDir(
+        provisionerDir,
+        containerId);
+
+  Try<hashmap<string, hashset<string>>> rootfses =
+    slave::provisioner::paths::listContainerRootfses(
+        provisionerDir,
+        containerId);
 
   ASSERT_SOME(rootfses);
 
   // Verify that the rootfs is successfully provisioned.
-  EXPECT_EQ(1u, rootfses.get().size());
-  EXPECT_EQ(rootfses.get().front(), Path(rootfs.get()).basename());
+  ASSERT_TRUE(rootfses->contains(flags.image_provisioner_backend));
+  ASSERT_EQ(1u, rootfses->get(flags.image_provisioner_backend)->size());
+  EXPECT_EQ(*rootfses->get(flags.image_provisioner_backend)->begin(),
+            Path(rootfs.get()).basename());
 
-  Future<bool> destroy = provisioners.get()[Image::APPC]->destroy(containerId);
+  Future<bool> destroy = provisioner.get()->destroy(containerId);
   AWAIT_READY(destroy);
 
   // One rootfs is destroyed.
@@ -305,23 +313,21 @@ TEST_F(AppcProvisionerTest, ROOT_Provision)
 #endif // __linux__
 
 
-// This test verifies that a provisioner can recover the rootfs provisioned
-// by a previous provisioner and then destroy it. Note that we use the copy
-// backend in this test so Linux is not required.
-TEST_F(AppcProvisionerTest, Recover)
+// This test verifies that a provisioner can recover the rootfs
+// provisioned by a previous provisioner and then destroy it. Note
+// that we use the copy backend in this test so Linux is not required.
+TEST_F(ProvisionerAppcTest, Recover)
 {
   // Create provisioner.
   slave::Flags flags;
+  flags.image_providers = "APPC";
   flags.appc_store_dir = path::join(os::getcwd(), "store");
-  flags.appc_provisioner_backend = "copy";
-  flags.provisioners = "appc";
+  flags.image_provisioner_backend = "copy";
   flags.work_dir = "work_dir";
 
   Fetcher fetcher;
-  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners1 =
-    Provisioner::create(flags, &fetcher);
+  Try<Owned<Provisioner>> provisioners1 = Provisioner::create(flags, &fetcher);
   ASSERT_SOME(provisioners1);
-  ASSERT_TRUE(provisioners1.get().contains(Image::APPC));
 
   // Create a simple image in the store:
   // <store>
@@ -336,8 +342,8 @@ TEST_F(AppcProvisionerTest, Recover)
       "  \"name\": \"foo.com/bar\""
       "}").get();
 
-  // The 'imageId' below has the correct format but it's not computed by
-  // hashing the tarball of the image. It's OK here as we assume
+  // The 'imageId' below has the correct format but it's not computed
+  // by hashing the tarball of the image. It's OK here as we assume
   // the images under 'images' have passed such check when they are
   // downloaded and validated.
   string imageId =
@@ -353,7 +359,7 @@ TEST_F(AppcProvisionerTest, Recover)
       os::write(path::join(imagePath, "manifest"), stringify(manifest)));
 
   // Recover. This is when the image in the store is loaded.
-  AWAIT_READY(provisioners1.get()[Image::APPC]->recover({}, {}));
+  AWAIT_READY(provisioners1.get()->recover({}, {}));
 
   Image image;
   image.mutable_appc()->set_name("foo.com/bar");
@@ -361,48 +367,46 @@ TEST_F(AppcProvisionerTest, Recover)
   ContainerID containerId;
   containerId.set_value(UUID::random().toString());
 
-  Future<string> rootfs =
-    provisioners1.get()[Image::APPC]->provision(containerId, image);
+  Future<string> rootfs = provisioners1.get()->provision(containerId, image);
   AWAIT_READY(rootfs);
 
   // Create a new provisioner to recover the state from the container.
-  Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners2 =
-    Provisioner::create(flags, &fetcher);
+  Try<Owned<Provisioner>> provisioners2 = Provisioner::create(flags, &fetcher);
   ASSERT_SOME(provisioners2);
-  ASSERT_TRUE(provisioners2.get().contains(Image::APPC));
 
   mesos::slave::ContainerState state;
 
   // Here we are using an ExecutorInfo in the ContainerState without a
-  // ContainerInfo. This is the situation where the Image is specified via
-  // --default_container_info so it's not part of the recovered ExecutorInfo.
+  // ContainerInfo. This is the situation where the Image is specified
+  // via --default_container_info so it's not part of the recovered
+  // ExecutorInfo.
   state.mutable_container_id()->CopyFrom(containerId);
 
-  AWAIT_READY(provisioners2.get()[Image::APPC]->recover({state}, {}));
+  AWAIT_READY(provisioners2.get()->recover({state}, {}));
 
   // It's possible for the user to provision two different rootfses
   // from the same image.
-  AWAIT_READY(provisioners2.get()[Image::APPC]->provision(containerId, image));
+  AWAIT_READY(provisioners2.get()->provision(containerId, image));
+
+  string provisionerDir = slave::paths::getProvisionerDir(flags.work_dir);
 
-  string containerDir = path::join(
-      flags.work_dir,
-      "provisioners",
-      stringify(Image::APPC),
-      "containers",
-      containerId.value());
+  string containerDir =
+    slave::provisioner::paths::getContainerDir(
+        provisionerDir,
+        containerId);
 
-  Try<list<string>> rootfses = os::ls(path::join(
-      containerDir,
-      "backends",
-      flags.appc_provisioner_backend,
-      "rootfses"));
+  Try<hashmap<string, hashset<string>>> rootfses =
+    slave::provisioner::paths::listContainerRootfses(
+        provisionerDir,
+        containerId);
 
   ASSERT_SOME(rootfses);
 
   // Verify that the rootfs is successfully provisioned.
-  EXPECT_EQ(2u, rootfses.get().size());
+  ASSERT_TRUE(rootfses->contains(flags.image_provisioner_backend));
+  EXPECT_EQ(2u, rootfses->get(flags.image_provisioner_backend)->size());
 
-  Future<bool> destroy = provisioners2.get()[Image::APPC]->destroy(containerId);
+  Future<bool> destroy = provisioners2.get()->destroy(containerId);
   AWAIT_READY(destroy);
   EXPECT_TRUE(destroy.get());
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/76861c52/src/tests/paths_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/paths_tests.cpp b/src/tests/paths_tests.cpp
index efdc2c0..da01dae 100644
--- a/src/tests/paths_tests.cpp
+++ b/src/tests/paths_tests.cpp
@@ -104,6 +104,13 @@ TEST_F(PathsTest, Meta)
 }
 
 
+TEST_F(PathsTest, ProvisionerDir)
+{
+  EXPECT_EQ(path::join(rootDir, "provisioner"),
+            paths::getProvisionerDir(rootDir));
+}
+
+
 TEST_F(PathsTest, Archive)
 {
   EXPECT_EQ(path::join(rootDir, "archive"), paths::getArchiveDir(rootDir));
@@ -219,13 +226,6 @@ TEST_F(PathsTest, PersistentVolume)
 }
 
 
-TEST_F(PathsTest, ProvisionerDir)
-{
-  string dir = path::join(rootDir, "provisioner", "APPC");
-
-  EXPECT_EQ(dir, paths::getProvisionerDir(rootDir, imageType));
-}
-
 } // namespace paths {
 } // namespace slave {
 } // namespace internal {


[5/6] mesos git commit: Renamed provisioners::path to provisioner::path.

Posted by ji...@apache.org.
Renamed provisioners::path to provisioner::path.

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


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

Branch: refs/heads/master
Commit: c027c82b994b30e1a4bdbf6b4b3511dc8ed12f9e
Parents: cc1f8f5
Author: Jie Yu <yu...@gmail.com>
Authored: Tue Sep 15 11:33:23 2015 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Sep 16 15:45:50 2015 -0700

----------------------------------------------------------------------
 .../provisioner/appc/provisioner.cpp            |  8 +++----
 src/slave/containerizer/provisioner/paths.cpp   |  4 ++--
 src/slave/containerizer/provisioner/paths.hpp   | 20 +++++++++---------
 src/slave/paths.cpp                             | 22 ++++++++++----------
 src/slave/paths.hpp                             | 12 +++++------
 src/tests/paths_tests.cpp                       |  2 +-
 6 files changed, 34 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/c027c82b/src/slave/containerizer/provisioner/appc/provisioner.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/appc/provisioner.cpp b/src/slave/containerizer/provisioner/appc/provisioner.cpp
index 929e42a..2e77747 100644
--- a/src/slave/containerizer/provisioner/appc/provisioner.cpp
+++ b/src/slave/containerizer/provisioner/appc/provisioner.cpp
@@ -217,7 +217,7 @@ Future<Nothing> AppcProvisionerProcess::recover(
   // be destroyed by the containerizer using the normal cleanup path. See
   // MESOS-2367 for details.
   Try<hashmap<ContainerID, string>> containers =
-    provisioners::paths::listContainers(root);
+    provisioner::paths::listContainers(root);
 
   if (containers.isError()) {
     return Failure("Failed to list the containers managed by Appc "
@@ -231,7 +231,7 @@ Future<Nothing> AppcProvisionerProcess::recover(
     Owned<Info> info = Owned<Info>(new Info());
 
     Try<hashmap<string, hashmap<string, string>>> rootfses =
-      provisioners::paths::listContainerRootfses(root, containerId);
+      provisioner::paths::listContainerRootfses(root, containerId);
 
     if (rootfses.isError()) {
       return Failure("Unable to list rootfses belonged to container '" +
@@ -305,7 +305,7 @@ Future<string> AppcProvisionerProcess::provision(
   }
 
   string rootfsId = UUID::random().toString();
-  string rootfs = provisioners::paths::getContainerRootfsDir(
+  string rootfs = provisioner::paths::getContainerRootfsDir(
       root, containerId, flags.appc_provisioner_backend, rootfsId);
 
   if (!infos.contains(containerId)) {
@@ -368,7 +368,7 @@ Future<bool> AppcProvisionerProcess::destroy(const ContainerID& containerId)
   // NOTE: We calculate 'containerDir' here so that the following
   // lambda does not need to bind 'this'.
   string containerDir =
-    provisioners::paths::getContainerDir(root, containerId);
+    provisioner::paths::getContainerDir(root, containerId);
 
   // TODO(xujyan): Revisit the usefulness of this return value.
   return collect(futures)

http://git-wip-us.apache.org/repos/asf/mesos/blob/c027c82b/src/slave/containerizer/provisioner/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/paths.cpp b/src/slave/containerizer/provisioner/paths.cpp
index cb2690e..601c51f 100644
--- a/src/slave/containerizer/provisioner/paths.cpp
+++ b/src/slave/containerizer/provisioner/paths.cpp
@@ -37,7 +37,7 @@ using std::string;
 namespace mesos {
 namespace internal {
 namespace slave {
-namespace provisioners {
+namespace provisioner {
 namespace paths {
 
 static string getContainersDir(const string& provisionerDir)
@@ -186,7 +186,7 @@ Try<hashmap<string, hashmap<string, string>>> listContainerRootfses(
 }
 
 } // namespace paths {
-} // namespace provisioners {
+} // namespace provisioner {
 } // namespace slave {
 } // namespace internal {
 } // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/c027c82b/src/slave/containerizer/provisioner/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/provisioner/paths.hpp b/src/slave/containerizer/provisioner/paths.hpp
index 4ea47e3..7ec8fee 100644
--- a/src/slave/containerizer/provisioner/paths.hpp
+++ b/src/slave/containerizer/provisioner/paths.hpp
@@ -29,13 +29,13 @@
 namespace mesos {
 namespace internal {
 namespace slave {
-namespace provisioners {
+namespace provisioner {
 namespace paths {
 
 // The provisioner rootfs directory is as follows:
 // <work_dir> ('--work_dir' flag)
-// |-- provisioners
-//     |-- <provisioner_type> (APPC, DOCKER, etc.)
+// |-- provisioner
+//     |-- <image_type> (APPC, DOCKER, etc.)
 //         |-- containers
 //             |-- <container_id>
 //                 |-- backends
@@ -43,12 +43,12 @@ namespace paths {
 //                         |-- rootfses
 //                             |-- <rootfs_id> (the rootfs)
 //
-// NOTE: Each container could have multiple image types, therefore there
-// can be the same <container_id> directory under other provisioners e.g.,
-// <work_dir>/provisioners/DOCKER, <work_dir>/provisioners/APPC, etc.
-// Under each provisioner + container there can be multiple backends
-// due to the change of backend flags. Under each backend a rootfs is
-// identified by the 'rootfs_id' which is a UUID.
+// NOTE: Each container could have multiple image types, therefore the
+// same <container_id> directory can be under other directories (e.g.,
+// <work_dir>/provisioner/DOCKER, <work_dir>/provisioner/APPC, etc.).
+// There can be multiple backends due to the change of backend flags.
+// Under each backend a rootfs is identified by the 'rootfs_id' which
+// is a UUID.
 
 std::string getContainerDir(
     const std::string& provisionerDir,
@@ -75,7 +75,7 @@ Try<hashmap<ContainerID, std::string>> listContainers(
     const std::string& provisionerDir);
 
 } // namespace paths {
-} // namespace provisioners {
+} // namespace provisioner {
 } // namespace slave {
 } // namespace internal {
 } // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/c027c82b/src/slave/paths.cpp
----------------------------------------------------------------------
diff --git a/src/slave/paths.cpp b/src/slave/paths.cpp
index f104ecd..26b7f88 100644
--- a/src/slave/paths.cpp
+++ b/src/slave/paths.cpp
@@ -369,6 +369,17 @@ string getPersistentVolumePath(
 }
 
 
+string getProvisionerDir(
+    const string& rootDir,
+    const Image::Type& imageType)
+{
+  return path::join(
+      rootDir,
+      "provisioner",
+      stringify(imageType));
+}
+
+
 string createExecutorDirectory(
     const string& rootDir,
     const SlaveID& slaveId,
@@ -452,17 +463,6 @@ string createSlaveDirectory(
   return directory;
 }
 
-
-string getProvisionerDir(
-    const string& rootDir,
-    const Image::Type& imageType)
-{
-  return path::join(
-      rootDir,
-      "provisioners",
-      stringify(imageType));
-}
-
 } // namespace paths {
 } // namespace slave {
 } // namespace internal {

http://git-wip-us.apache.org/repos/asf/mesos/blob/c027c82b/src/slave/paths.hpp
----------------------------------------------------------------------
diff --git a/src/slave/paths.hpp b/src/slave/paths.hpp
index 43c65af..8873e78 100644
--- a/src/slave/paths.hpp
+++ b/src/slave/paths.hpp
@@ -264,6 +264,12 @@ std::string getPersistentVolumePath(
     const std::string& persistenceId);
 
 
+// Contents of the provisioner directory are managed by the provisioner itself.
+std::string getProvisionerDir(
+    const std::string& rootDir,
+    const Image::Type& imageType);
+
+
 std::string createExecutorDirectory(
     const std::string& rootDir,
     const SlaveID& slaveId,
@@ -277,12 +283,6 @@ std::string createSlaveDirectory(
     const std::string& rootDir,
     const SlaveID& slaveId);
 
-
-// Contents of the provisioner directory are managed by the provisioner itself.
-std::string getProvisionerDir(
-    const std::string& rootDir,
-    const Image::Type& imageType);
-
 } // namespace paths {
 } // namespace slave {
 } // namespace internal {

http://git-wip-us.apache.org/repos/asf/mesos/blob/c027c82b/src/tests/paths_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/paths_tests.cpp b/src/tests/paths_tests.cpp
index 1ccc5c7..efdc2c0 100644
--- a/src/tests/paths_tests.cpp
+++ b/src/tests/paths_tests.cpp
@@ -221,7 +221,7 @@ TEST_F(PathsTest, PersistentVolume)
 
 TEST_F(PathsTest, ProvisionerDir)
 {
-  string dir = path::join(rootDir, "provisioners", "APPC");
+  string dir = path::join(rootDir, "provisioner", "APPC");
 
   EXPECT_EQ(dir, paths::getProvisionerDir(rootDir, imageType));
 }