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 {

Reply via email to