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));
}