Restructure Docker provisioner and protos.
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/4dedbf4a Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/4dedbf4a Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/4dedbf4a Branch: refs/heads/master Commit: 4dedbf4aec2183af94ee083d2f6fb22057f9dfc8 Parents: 2cc0dfb Author: Timothy Chen <[email protected]> Authored: Fri Sep 11 00:19:42 2015 -0700 Committer: Timothy Chen <[email protected]> Committed: Fri Sep 25 09:02:05 2015 -0700 ---------------------------------------------------------------------- src/Makefile.am | 9 +- src/messages/docker_provisioner.hpp | 24 - src/messages/docker_provisioner.proto | 35 -- src/slave/containerizer/provisioner.cpp | 1 + .../provisioner/docker/local_store.cpp | 460 ++++++++++++++++++ .../provisioner/docker/local_store.hpp | 64 +++ .../provisioner/docker/message.hpp | 71 +++ .../provisioner/docker/message.proto | 43 ++ .../provisioner/docker/metadata_manager.cpp | 251 ++++++++++ .../provisioner/docker/metadata_manager.hpp | 106 +++++ .../containerizer/provisioner/docker/paths.cpp | 104 ++++ .../containerizer/provisioner/docker/paths.hpp | 86 ++++ .../containerizer/provisioner/docker/store.cpp | 47 ++ .../containerizer/provisioner/docker/store.hpp | 58 +++ .../containerizer/provisioner/provisioner.hpp | 2 +- src/slave/containerizer/provisioners/docker.cpp | 425 ----------------- src/slave/containerizer/provisioners/docker.hpp | 166 ------- .../provisioners/docker/local_store.cpp | 471 ------------------- .../provisioners/docker/local_store.hpp | 66 --- .../provisioners/docker/metadata_manager.cpp | 254 ---------- .../provisioners/docker/metadata_manager.hpp | 104 ---- .../containerizer/provisioners/docker/paths.cpp | 104 ---- .../containerizer/provisioners/docker/paths.hpp | 86 ---- .../containerizer/provisioners/docker/store.hpp | 76 --- src/slave/flags.cpp | 12 +- src/slave/flags.hpp | 7 +- .../containerizer/provisioner_docker_tests.cpp | 59 ++- 27 files changed, 1336 insertions(+), 1855 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index a2a64dc..818d62d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -250,8 +250,9 @@ BUILT_SOURCES += $(STATE_PROTOS) CLEANFILES += $(STATE_PROTOS) DOCKER_PROVISIONER_PROTOS = \ - messages/docker_provisioner.pb.cc \ - messages/docker_provisioner.pb.h + slave/containerizer/provisioner/docker/message.pb.cc \ + slave/containerizer/provisioner/docker/message.pb.h + BUILT_SOURCES += $(DOCKER_PROVISIONER_PROTOS) CLEANFILES += $(DOCKER_PROVISIONER_PROTOS) @@ -529,11 +530,11 @@ libmesos_no_3rdparty_la_SOURCES = \ slave/containerizer/provisioner/appc/store.cpp \ slave/containerizer/provisioner/backend.cpp \ slave/containerizer/provisioner/backends/copy.cpp \ - slave/containerizer/provisioner/docker.cpp \ slave/containerizer/provisioner/docker/local_store.cpp \ slave/containerizer/provisioner/docker/metadata_manager.cpp \ slave/containerizer/provisioner/docker/paths.cpp \ slave/containerizer/provisioner/docker/registry_client.cpp \ + slave/containerizer/provisioner/docker/store.cpp \ slave/containerizer/provisioner/docker/token_manager.cpp \ slave/resource_estimators/noop.cpp \ usage/usage.cpp \ @@ -831,7 +832,7 @@ libmesos_no_3rdparty_la_SOURCES += \ slave/containerizer/provisioner/backends/copy.hpp \ slave/containerizer/provisioner/docker.hpp \ slave/containerizer/provisioner/docker/local_store.hpp \ - slave/containerizer/provisioners/docker/metadata_manager.hpp \ + slave/containerizer/provisioner/docker/metadata_manager.hpp \ slave/containerizer/provisioner/docker/paths.hpp \ slave/containerizer/provisioner/docker/registry_client.hpp \ slave/containerizer/provisioner/docker/store.hpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/messages/docker_provisioner.hpp ---------------------------------------------------------------------- diff --git a/src/messages/docker_provisioner.hpp b/src/messages/docker_provisioner.hpp deleted file mode 100644 index 684bd46..0000000 --- a/src/messages/docker_provisioner.hpp +++ /dev/null @@ -1,24 +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 __MESSAGES_DOCKER_PROVISIONER_HPP__ -#define __MESSAGES_DOCKER_PROVISIONER_HPP__ - -#include "messages/docker_provisioner.pb.h" - -#endif // __MESSAGES_DOCKER_PROVISIONER_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/messages/docker_provisioner.proto ---------------------------------------------------------------------- diff --git a/src/messages/docker_provisioner.proto b/src/messages/docker_provisioner.proto deleted file mode 100644 index 9de6707..0000000 --- a/src/messages/docker_provisioner.proto +++ /dev/null @@ -1,35 +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. - */ - -import "mesos/mesos.proto"; - -package mesos.internal; - -/** - * A Docker Image name and the layer ids of the layers that comprise the image. - * The layerIds are ordered, with the root layer id (no parent layer id) first - * and the leaf layer id last. - */ -message DockerProvisionerImages { - message Image { - required string name = 1; - repeated string layer_ids = 2; - } - - repeated Image images = 1; -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner.cpp b/src/slave/containerizer/provisioner.cpp index 1ff3f10..6a0faac 100644 --- a/src/slave/containerizer/provisioner.cpp +++ b/src/slave/containerizer/provisioner.cpp @@ -23,6 +23,7 @@ #include "slave/containerizer/provisioner.hpp" #include "slave/containerizer/provisioners/appc/provisioner.hpp" + #include "slave/containerizer/provisioners/docker/provisioner.hpp" using namespace process; http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/local_store.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/local_store.cpp b/src/slave/containerizer/provisioner/docker/local_store.cpp new file mode 100644 index 0000000..6a73dbb --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/local_store.cpp @@ -0,0 +1,460 @@ +/** + * 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 <vector> + +#include <glog/logging.h> + +#include <stout/json.hpp> +#include <stout/os.hpp> +#include <stout/result.hpp> + +#include <process/collect.hpp> +#include <process/defer.hpp> +#include <process/dispatch.hpp> + +#include "common/status_utils.hpp" + +#include "slave/containerizer/provisioner/docker/local_store.hpp" +#include "slave/containerizer/provisioner/docker/metadata_manager.hpp" +#include "slave/containerizer/provisioner/docker/paths.hpp" +#include "slave/containerizer/provisioner/docker/store.hpp" + +#include "slave/flags.hpp" + +using namespace process; + +using std::list; +using std::string; +using std::vector; + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { + +class LocalStoreProcess : public process::Process<LocalStoreProcess> +{ +public: + LocalStoreProcess( + const Flags& _flags, + Owned<MetadataManager> _metadataManager) + : flags(_flags), metadataManager(_metadataManager) {} + + ~LocalStoreProcess() {} + + static Try<process::Owned<LocalStoreProcess>> create(const Flags& flags); + + process::Future<vector<string>> get(const Image& image); + + process::Future<Nothing> recover(); + +private: + process::Future<DockerImage> _get( + const DockerImage::Name& name, + const Option<DockerImage>& image); + + process::Future<Nothing> untarImage( + const std::string& tarPath, + const std::string& staging); + + process::Future<DockerImage> putImage( + const DockerImage::Name& name, + const std::string& staging); + + Result<std::string> getParentId( + const std::string& staging, + const std::string& layerId); + + process::Future<Nothing> putLayers( + const std::string& staging, + const std::list<std::string>& layerIds); + + process::Future<Nothing> putLayer( + const std::string& staging, + const std::string& id); + + process::Future<Nothing> moveLayer( + const std::string& staging, + const std::string& id); + + const Flags flags; + process::Owned<MetadataManager> metadataManager; +}; + + +Try<Owned<slave::Store>> LocalStore::create(const Flags& flags) +{ + if (!os::exists(flags.docker_store_dir)) { + Try<Nothing> mkdir = os::mkdir(flags.docker_store_dir); + if (mkdir.isError()) { + return Error("Failed to create Docker store directory: " + mkdir.error()); + } + } + + if (!os::exists(paths::getStagingDir(flags.docker_store_dir))) { + Try<Nothing> mkdir = + os::mkdir(paths::getStagingDir(flags.docker_store_dir)); + if (mkdir.isError()) { + return Error("Failed to create Docker store staging directory: " + + mkdir.error()); + } + } + + Try<Owned<MetadataManager>> metadataManager = MetadataManager::create(flags); + if (metadataManager.isError()) { + return Error(metadataManager.error()); + } + + Owned<LocalStoreProcess> process( + new LocalStoreProcess(flags, metadataManager.get())); + + return Owned<slave::Store>(new LocalStore(process)); +} + + +LocalStore::LocalStore(Owned<LocalStoreProcess> _process) : process(_process) +{ + process::spawn(CHECK_NOTNULL(process.get())); +} + + +LocalStore::~LocalStore() +{ + process::terminate(process.get()); + process::wait(process.get()); +} + + +Future<vector<string>> LocalStore::get(const Image& image) +{ + return dispatch(process.get(), &LocalStoreProcess::get, image); +} + + +Future<Nothing> LocalStore::recover() +{ + return dispatch(process.get(), &LocalStoreProcess::recover); +} + + +Future<vector<string>> LocalStoreProcess::get(const Image& image) +{ + CHECK_EQ(image.type(), Image::DOCKER); + + Try<DockerImage::Name> dockerName = parseName(image.docker().name()); + if (dockerName.isError()) { + return Failure("Unable to parse docker image name: " + dockerName.error()); + } + + return metadataManager->get(dockerName.get()) + .then(defer(self(), &Self::_get, dockerName.get(), lambda::_1)) + .then([](const DockerImage& dockerImage) { + vector<string> layers; + foreach (const string& layer, dockerImage.layer_ids()) { + layers.push_back(layer); + } + + return layers; + }); +} + + +Future<DockerImage> LocalStoreProcess::_get( + const DockerImage::Name& name, + const Option<DockerImage>& image) +{ + if (image.isSome()) { + return image.get(); + } + + string tarPath = paths::getLocalImageTarPath( + flags.docker_store_discovery_local_dir, + stringify(name)); + + if (!os::exists(tarPath)) { + VLOG(1) << "Unable to find image in local store with path: " << tarPath; + return Failure("No Docker image tar archive found"); + } + + // Create a temporary staging directory. + Try<string> staging = + os::mkdtemp(paths::getTempStaging(flags.docker_store_dir)); + if (staging.isError()) { + return Failure("Failed to create a staging directory"); + } + + return untarImage(tarPath, staging.get()) + .then(defer(self(), &Self::putImage, name, staging.get())); +} + + +Future<Nothing> LocalStoreProcess::recover() +{ + return metadataManager->recover(); +} + + +Future<Nothing> LocalStoreProcess::untarImage( + const string& tarPath, + const string& staging) +{ + VLOG(1) << "Untarring image at: " << tarPath; + + // Untar store_discovery_local_dir/name.tar into staging/. + vector<string> argv = { + "tar", + "-C", + staging, + "-x", + "-f", + tarPath + }; + + Try<Subprocess> s = subprocess( + "tar", + argv, + Subprocess::PATH("/dev/null"), + Subprocess::PATH("/dev/null"), + Subprocess::PATH("/dev/null")); + + if (s.isError()) { + return Failure("Failed to create tar subprocess: " + s.error()); + } + + return s.get().status() + .then([=](const Option<int>& status) -> Future<Nothing> { + if (status.isNone()) { + return Failure("Failed to reap status for tar subprocess in " + + tarPath); + } + if (!WIFEXITED(status.get()) || WEXITSTATUS(status.get()) != 0) { + return Failure("Untar image failed with exit code: " + + WSTRINGIFY(status.get())); + } + + return Nothing(); + }); +} + + +Future<DockerImage> LocalStoreProcess::putImage( + const DockerImage::Name& name, + const string& staging) +{ + Try<string> value = os::read(paths::getLocalImageRepositoriesPath(staging)); + if (value.isError()) { + return Failure("Failed to read repository JSON: " + value.error()); + } + + Try<JSON::Object> json = JSON::parse<JSON::Object>(value.get()); + if (json.isError()) { + return Failure("Failed to parse JSON: " + json.error()); + } + + Result<JSON::Object> repositoryValue = + json.get().find<JSON::Object>(name.repository()); + if (repositoryValue.isError()) { + return Failure("Failed to find repository: " + repositoryValue.error()); + } else if (repositoryValue.isNone()) { + return Failure("Repository '" + name.repository() + "' is not found"); + } + + JSON::Object repositoryJson = repositoryValue.get(); + + // We don't use JSON find here because a tag might contain a '.'. + std::map<string, JSON::Value>::const_iterator entry = + repositoryJson.values.find(name.tag()); + if (entry == repositoryJson.values.end()) { + return Failure("Tag '" + name.tag() + "' is not found"); + } else if (!entry->second.is<JSON::String>()) { + return Failure("Tag JSON value expected to be JSON::String"); + } + + string layerId = entry->second.as<JSON::String>().value; + + Try<string> manifest = + os::read(paths::getLocalImageLayerManifestPath(staging, layerId)); + if (manifest.isError()) { + return Failure("Failed to read manifest: " + manifest.error()); + } + + Try<JSON::Object> manifestJson = JSON::parse<JSON::Object>(manifest.get()); + if (manifestJson.isError()) { + return Failure("Failed to parse manifest: " + manifestJson.error()); + } + + list<string> layerIds; + layerIds.push_back(layerId); + Result<string> parentId = getParentId(staging, layerId); + while(parentId.isSome()) { + layerIds.push_front(parentId.get()); + parentId = getParentId(staging, parentId.get()); + } + if (parentId.isError()) { + return Failure("Failed to obtain parent layer id: " + parentId.error()); + } + + return putLayers(staging, layerIds) + .then([=]() -> Future<DockerImage> { + return metadataManager->put(name, layerIds); + }); +} + + +Result<string> LocalStoreProcess::getParentId( + const string& staging, + const string& layerId) +{ + Try<string> manifest = + os::read(paths::getLocalImageLayerManifestPath(staging, layerId)); + if (manifest.isError()) { + return Error("Failed to read manifest: " + manifest.error()); + } + + Try<JSON::Object> json = JSON::parse<JSON::Object>(manifest.get()); + if (json.isError()) { + return Error("Failed to parse manifest: " + json.error()); + } + + Result<JSON::String> parentId = json.get().find<JSON::String>("parent"); + if (parentId.isNone() || (parentId.isSome() && parentId.get() == "")) { + return None(); + } else if (parentId.isError()) { + return Error("Failed to read parent of layer: " + parentId.error()); + } + return parentId.get().value; +} + + +Future<Nothing> LocalStoreProcess::putLayers( + const string& staging, + const list<string>& layerIds) +{ + list<Future<Nothing>> futures{ Nothing() }; + foreach (const string& layer, layerIds) { + futures.push_back( + futures.back().then( + defer(self(), &Self::putLayer, staging, layer))); + } + + return collect(futures) + .then([]() -> Future<Nothing> { return Nothing(); }); +} + + +Future<Nothing> LocalStoreProcess::putLayer( + const string& staging, + const string& id) +{ + // We untar the layer from source into a staging directory, then + // move the layer into store. We do this instead of untarring + // directly to store to make sure we don't end up with partially + // untarred layer rootfs. + + // Check if image layer rootfs is already in store. + if (os::exists(paths::getImageLayerRootfsPath(flags.docker_store_dir, id))) { + VLOG(1) << "Image layer '" << id << "' rootfs already in store. " + << "Skipping put."; + return Nothing(); + } + + const string imageLayerPath = + paths::getImageLayerPath(flags.docker_store_dir, id); + if (!os::exists(imageLayerPath)) { + Try<Nothing> mkdir = os::mkdir(imageLayerPath); + if (mkdir.isError()) { + return Failure("Failed to create Image layer directory '" + + imageLayerPath + "': " + mkdir.error()); + } + } + + // Image layer has been untarred but is not present in the store directory. + string localRootfsPath = paths::getLocalImageLayerRootfsPath(staging, id); + if (os::exists(localRootfsPath)) { + LOG(WARNING) << "Image layer '" << id << "' rootfs present at but not in " + << "store directory '" << localRootfsPath << "'. Removing " + << "staged rootfs and untarring layer again."; + Try<Nothing> rmdir = os::rmdir(localRootfsPath); + if (rmdir.isError()) { + return Failure("Failed to remove incomplete staged rootfs for layer '" + + id + "': " + rmdir.error()); + } + } + + Try<Nothing> mkdir = os::mkdir(localRootfsPath); + if (mkdir.isError()) { + return Failure("Failed to create rootfs path '" + localRootfsPath + "': " + + mkdir.error()); + } + // Untar staging/id/layer.tar into staging/id/rootfs. + vector<string> argv = { + "tar", + "-C", + localRootfsPath, + "-x", + "-f", + paths::getLocalImageLayerTarPath(staging, id) + }; + + Try<Subprocess> s = subprocess( + "tar", + argv, + Subprocess::PATH("/dev/null"), + Subprocess::PATH("/dev/null"), + Subprocess::PATH("/dev/null")); + if (s.isError()) { + return Failure("Failed to create tar subprocess: " + s.error()); + } + + return s.get().status() + .then([=](const Option<int>& status) -> Future<Nothing> { + if (status.isNone()) { + return Failure("Failed to reap subprocess to untar image"); + } else if (!WIFEXITED(status.get()) || WEXITSTATUS(status.get()) != 0) { + return Failure("Untar failed with exit code: " + + WSTRINGIFY(status.get())); + } + + return moveLayer(staging, id); + }); +} + + +Future<Nothing> LocalStoreProcess::moveLayer( + const string& staging, + const string& id) +{ + Try<Nothing> status = os::rename( + paths::getLocalImageLayerRootfsPath(staging, id), + paths::getImageLayerRootfsPath(flags.docker_store_dir, id)); + + if (status.isError()) { + return Failure("Failed to move layer to store directory: " + + status.error()); + } + + return Nothing(); +} + + +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/local_store.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/local_store.hpp b/src/slave/containerizer/provisioner/docker/local_store.hpp new file mode 100644 index 0000000..5f6152b --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/local_store.hpp @@ -0,0 +1,64 @@ +/** + * 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_DOCKER_LOCAL_STORE_HPP__ +#define __MESOS_DOCKER_LOCAL_STORE_HPP__ + +#include "slave/containerizer/provisioner/store.hpp" + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { + +// Forward declaration. +class LocalStoreProcess; + + +/** + * LocalStore assumes Docker images are stored in a local directory + * (configured with flags.docker_discovery_local_dir), with all the + * images saved as tar with the name as the image name with tag (e.g: + * ubuntu:14.04.tar). + */ +class LocalStore : public slave::Store +{ +public: + static Try<process::Owned<Store>> create(const Flags& flags); + + virtual ~LocalStore(); + + virtual process::Future<Nothing> recover(); + + virtual process::Future<std::vector<std::string>> get(const Image& image); + +private: + explicit LocalStore(process::Owned<LocalStoreProcess> _process); + + LocalStore& operator=(const LocalStore&) = delete; // Not assignable. + LocalStore(const LocalStore&) = delete; // Not copyable. + + process::Owned<LocalStoreProcess> process; +}; + +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __MESOS_DOCKER_LOCAL_STORE_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/message.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/message.hpp b/src/slave/containerizer/provisioner/docker/message.hpp new file mode 100644 index 0000000..c1596df --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/message.hpp @@ -0,0 +1,71 @@ +/** + * 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 __MESSAGES_DOCKER_PROVISIONER_HPP__ +#define __MESSAGES_DOCKER_PROVISIONER_HPP__ + +#include <stout/strings.hpp> + +// ONLY USEFUL AFTER RUNNING PROTOC. +#include "slave/containerizer/provisioner/docker/message.pb.h" + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { + +inline DockerImage::Name parseName(const std::string& value) +{ + DockerImage::Name imageName; + Option<std::string> registry = None(); + std::vector<std::string> components = strings::split(value, "/"); + if (components.size() > 2) { + imageName.set_registry(value.substr(0, value.find_last_of("/"))); + } + + std::size_t found = components.back().find_last_of(':'); + if (found == std::string::npos) { + imageName.set_repository(components.back()); + imageName.set_tag("latest"); + } else { + imageName.set_repository(components.back().substr(0, found)); + imageName.set_tag(components.back().substr(found + 1)); + } + + return imageName; +} + + +inline std::ostream& operator<<( + std::ostream& stream, + const DockerImage::Name& name) +{ + if (name.has_registry()) { + return stream << name.registry() << "/" << name.repository() << ":" + << name.tag(); + } + + return stream << name.repository() << ":" << name.tag(); +} + +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __MESSAGES_DOCKER_PROVISIONER_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/message.proto ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/message.proto b/src/slave/containerizer/provisioner/docker/message.proto new file mode 100644 index 0000000..d771968 --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/message.proto @@ -0,0 +1,43 @@ +/** + * 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. + */ + +import "mesos/mesos.proto"; + +package mesos.internal.slave.docker; + +/** + * A Docker Image name and the layer ids of the layers that comprise the image. + * The layerIds are ordered, with the root layer id (no parent layer id) first + * and the leaf layer id last. + */ +message DockerImage { + message Name { + optional string registry = 1; + required string repository = 2; + required string tag = 3; + } + + required Name name = 1; + + // The order of the layers represents the dependency between layers. + repeated string layer_ids = 2; +} + +message DockerImages { + repeated DockerImage images = 1; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/metadata_manager.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/metadata_manager.cpp b/src/slave/containerizer/provisioner/docker/metadata_manager.cpp new file mode 100644 index 0000000..197931c --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/metadata_manager.cpp @@ -0,0 +1,251 @@ +/** + * 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 <glog/logging.h> + +#include <stout/foreach.hpp> +#include <stout/hashset.hpp> +#include <stout/os.hpp> +#include <stout/protobuf.hpp> + +#include <process/defer.hpp> +#include <process/dispatch.hpp> +#include <process/owned.hpp> + +#include "common/status_utils.hpp" + +#include "slave/containerizer/provisioner/docker/paths.hpp" +#include "slave/containerizer/provisioner/docker/message.hpp" +#include "slave/containerizer/provisioner/docker/metadata_manager.hpp" + +#include "slave/state.hpp" + +using namespace process; + +using std::list; +using std::string; +using std::vector; + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { + + +class MetadataManagerProcess : public process::Process<MetadataManagerProcess> +{ +public: + ~MetadataManagerProcess() {} + + static Try<process::Owned<MetadataManagerProcess>> create( + const Flags& flags); + + Future<DockerImage> put( + const DockerImage::Name& name, + const std::list<std::string>& layerIds); + + Future<Option<DockerImage>> get(const DockerImage::Name& name); + + Future<Nothing> recover(); + + // TODO(chenlily): Implement removal of unreferenced images. + +private: + MetadataManagerProcess(const Flags& flags); + + // Write out metadata manager state to persistent store. + Try<Nothing> persist(); + + const Flags flags; + + // This is a lookup table for images that are stored in memory. It is keyed + // by the name of the DockerImage. + // For example, "ubuntu:14.04" -> ubuntu14:04 DockerImage. + hashmap<std::string, DockerImage> storedImages; +}; + + +Try<Owned<MetadataManager>> MetadataManager::create(const Flags& flags) +{ + Try<Owned<MetadataManagerProcess>> process = + MetadataManagerProcess::create(flags); + if (process.isError()) { + return Error("Failed to create Metadata Manager: " + process.error()); + } + return Owned<MetadataManager>(new MetadataManager(process.get())); +} + + +MetadataManager::MetadataManager(Owned<MetadataManagerProcess> process) + : process(process) +{ + process::spawn(CHECK_NOTNULL(process.get())); +} + + +MetadataManager::~MetadataManager() +{ + process::terminate(process.get()); + process::wait(process.get()); +} + + +Future<Nothing> MetadataManager::recover() +{ + return process::dispatch(process.get(), &MetadataManagerProcess::recover); +} + + +Future<DockerImage> MetadataManager::put( + const DockerImage::Name& name, + const list<string>& layerIds) +{ + return dispatch( + process.get(), &MetadataManagerProcess::put, name, layerIds); +} + + +Future<Option<DockerImage>> MetadataManager::get(const DockerImage::Name& name) +{ + return dispatch(process.get(), &MetadataManagerProcess::get, name); +} + + +MetadataManagerProcess::MetadataManagerProcess(const Flags& flags) + : flags(flags) {} + + +Try<Owned<MetadataManagerProcess>> MetadataManagerProcess::create( + const Flags& flags) +{ + Owned<MetadataManagerProcess> metadataManager = + Owned<MetadataManagerProcess>(new MetadataManagerProcess(flags)); + + return metadataManager; +} + + +Future<DockerImage> MetadataManagerProcess::put( + const DockerImage::Name& name, + const list<string>& layerIds) +{ + const string imageName = stringify(name); + + DockerImage dockerImage; + dockerImage.mutable_name()->CopyFrom(name); + foreach (const string& layerId, layerIds) { + dockerImage.add_layer_ids(layerId); + } + + storedImages[imageName] = dockerImage; + + Try<Nothing> status = persist(); + if (status.isError()) { + return Failure("Failed to save state of Docker images" + status.error()); + } + + return dockerImage; +} + + +Future<Option<DockerImage>> MetadataManagerProcess::get( + const DockerImage::Name& name) +{ + const string imageName = stringify(name); + + if (!storedImages.contains(imageName)) { + return None(); + } + + return storedImages[imageName]; +} + + +Try<Nothing> MetadataManagerProcess::persist() +{ + DockerImages images; + + foreachvalue (const DockerImage& image, storedImages) { + images.add_images()->CopyFrom(image); + } + + Try<Nothing> status = mesos::internal::slave::state::checkpoint( + paths::getStoredImagesPath(flags.docker_store_dir), images); + if (status.isError()) { + return Error("Failed to perform checkpoint: " + status.error()); + } + + return Nothing(); +} + + +Future<Nothing> MetadataManagerProcess::recover() +{ + string storedImagesPath = paths::getStoredImagesPath(flags.docker_store_dir); + + storedImages.clear(); + if (!os::exists(storedImagesPath)) { + LOG(INFO) << "No images to load from disk. Docker provisioner image " + << "storage path: " << storedImagesPath << " does not exist."; + return Nothing(); + } + + Result<DockerImages> images = + ::protobuf::read<DockerImages>(storedImagesPath); + if (images.isError()) { + return Failure("Failed to read protobuf for Docker provisioner image: " + + images.error()); + } + + foreach (const DockerImage image, images.get().images()) { + vector<string> missingLayerIds; + foreach (const string layerId, image.layer_ids()) { + const string rootfsPath = + paths::getImageLayerRootfsPath(flags.docker_store_dir, layerId); + + if (!os::exists(rootfsPath)) { + missingLayerIds.push_back(layerId); + } + } + + if (!missingLayerIds.empty()) { + LOG(WARNING) << "Skipped loading image: " << stringify(image.name()) + << " due to missing layers: " << stringify(missingLayerIds); + continue; + } + + const string imageName = stringify(image.name()); + if (storedImages.contains(imageName)) { + LOG(WARNING) << "Found duplicate image in recovery for image name '" << imageName + << "'"; + } else { + storedImages[imageName] = image; + } + } + + LOG(INFO) << "Loaded " << storedImages.size() << " Docker images."; + + return Nothing(); +} + +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/metadata_manager.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/metadata_manager.hpp b/src/slave/containerizer/provisioner/docker/metadata_manager.hpp new file mode 100644 index 0000000..647a478 --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/metadata_manager.hpp @@ -0,0 +1,106 @@ +/** + * 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_DOCKER_METADATA_MANAGER_HPP__ +#define __MESOS_DOCKER_METADATA_MANAGER_HPP__ + +#include <list> +#include <string> + +#include <stout/hashmap.hpp> +#include <stout/json.hpp> +#include <stout/option.hpp> +#include <stout/protobuf.hpp> +#include <stout/try.hpp> + +#include <process/future.hpp> +#include <process/owned.hpp> +#include <process/process.hpp> + +#include "slave/containerizer/provisioner/provisioner.hpp" + +#include "slave/containerizer/provisioner/docker/message.hpp" + +#include "slave/flags.hpp" + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { + +// Forward Declaration. +class MetadataManagerProcess; + +/** + * The MetadataManager tracks the Docker images cached by the + * provisioner that are stored on disk. It keeps track of the layers + * that Docker images are composed of and recovers DockerImage objects + * upon initialization by checking for dependent layers stored on disk. + * Currently, image layers are stored indefinitely, with no garbage + * collection of unreferenced image layers. + */ +class MetadataManager +{ +public: + static Try<process::Owned<MetadataManager>> create(const Flags& flags); + + ~MetadataManager(); + + /** + * Create a Image, put it in metadata manager and persist the reference + * store state to disk. + * + * @param name the name of the Docker image to place in the reference + * store. + * @param layerIds the list of layer ids that comprise the Docker image in + * order where the root layer's id (no parent layer) is first + * and the leaf layer's id is last. + */ + process::Future<DockerImage> put( + const DockerImage::Name& name, + const std::list<std::string>& layerIds); + + /** + * Retrieve DockerImage based on image name if it is among the DockerImages + * stored in memory. + * + * @param name the name of the Docker image to retrieve + */ + process::Future<Option<DockerImage>> get(const DockerImage::Name& name); + + /** + * Recover all stored DockerImage and its layer references. + */ + process::Future<Nothing> recover(); + +private: + explicit MetadataManager(process::Owned<MetadataManagerProcess> process); + + MetadataManager(const MetadataManager&); // Not copyable. + MetadataManager& operator=(const MetadataManager&); // Not assignable. + + process::Owned<MetadataManagerProcess> process; +}; + + +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __MESOS_DOCKER_METADATA_MANAGER_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/paths.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/paths.cpp b/src/slave/containerizer/provisioner/docker/paths.cpp new file mode 100644 index 0000000..81a2176 --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/paths.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 "slave/containerizer/provisioner/docker/paths.hpp" + +#include <stout/path.hpp> + +using std::string; + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { +namespace paths { + +string getStagingDir(const string& storeDir) +{ + return path::join(storeDir, "staging"); +} + +string getTempStaging(const string& storeDir) +{ + return path::join(getStagingDir(storeDir), "XXXXXX"); +} + +string getLocalImageTarPath( + const string& discoveryDir, + const string& name) +{ + return path::join(discoveryDir, name + ".tar"); +} + +string getLocalImageRepositoriesPath(const string& staging) +{ + return path::join(staging, "repositories"); +} + +std::string getLocalImageLayerPath( + const string& staging, + const string& layerId) +{ + return path::join(staging, layerId); +} + +string getLocalImageLayerManifestPath( + const string& staging, + const string& layerId) +{ + return path::join(getLocalImageLayerPath(staging, layerId), "json"); +} + +string getLocalImageLayerTarPath( + const string& staging, + const string& layerId) +{ + return path::join(getLocalImageLayerPath(staging, layerId), "layer.tar"); +} + +string getLocalImageLayerRootfsPath( + const string& staging, + const string& layerId) +{ + return path::join(getLocalImageLayerPath(staging, layerId), "rootfs"); +} + +string getImageLayerPath( + const string& storeDir, + const string& layerId) +{ + return path::join(storeDir, "layers", layerId); +} + +string getImageLayerRootfsPath( + const string& storeDir, + const string& layerId) +{ + return path::join(getImageLayerPath(storeDir, layerId), "rootfs"); +} + +string getStoredImagesPath(const string& storeDir) +{ + return path::join(storeDir, "storedImages"); +} + +} // namespace paths { +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/paths.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/paths.hpp b/src/slave/containerizer/provisioner/docker/paths.hpp new file mode 100644 index 0000000..02f129f --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/paths.hpp @@ -0,0 +1,86 @@ +/** + * 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_DOCKER_PATHS_HPP__ +#define __MESOS_DOCKER_PATHS_HPP__ + +#include <list> +#include <string> + +#include <mesos/mesos.hpp> + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { +namespace paths { + +/** + * The Docker store file system layout is as follows: + * Image store dir ('--docker_store_dir' slave flag) + * |--staging (contains temp directories for downloads and extract) + * |--layers + * |--<layer_id> + * |--rootfs + * |--rootfses + * |--storedImages (file holding on cached images) + */ + +std::string getStagingDir(const std::string& storeDir); + +std::string getTempStaging(const std::string& storeDir); + +std::string getLocalImageTarPath( + const std::string& discoveryDir, + const std::string& name); + +std::string getLocalImageRepositoriesPath(const std::string& staging); + +std::string getLocalImageLayerPath( + const std::string& staging, + const std::string& layerId); + +std::string getLocalImageLayerManifestPath( + const std::string& staging, + const std::string& layerId); + +std::string getLocalImageLayerTarPath( + const std::string& staging, + const std::string& layerId); + +std::string getLocalImageLayerRootfsPath( + const std::string& staging, + const std::string& layerId); + +std::string getImageLayerPath( + const std::string& storeDir, + const std::string& layerId); + +std::string getImageLayerRootfsPath( + const std::string& storeDir, + const std::string& layerId); + +std::string getStoredImagesPath(const std::string& storeDir); + +} // namespace paths { +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __MESOS_DOCKER_PATHS_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/store.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/store.cpp b/src/slave/containerizer/provisioner/docker/store.cpp new file mode 100644 index 0000000..51f4d6b --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/store.cpp @@ -0,0 +1,47 @@ +/** + * 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 "slave/containerizer/provisioner/docker/local_store.hpp" +#include "slave/containerizer/provisioner/docker/store.hpp" + +using process::Owned; + +using std::string; + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { + +Try<Owned<slave::Store>> Store::create(const Flags& flags) +{ + hashmap<string, Try<Owned<slave::Store>>(*)(const Flags&)> creators{ + {"local", &LocalStore::create} + }; + + if (!creators.contains(flags.docker_store_discovery)) { + return Error("Unknown Docker store: " + flags.docker_store_discovery); + } + + return creators[flags.docker_store_discovery](flags); +} + +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/docker/store.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/docker/store.hpp b/src/slave/containerizer/provisioner/docker/store.hpp new file mode 100644 index 0000000..ae06706 --- /dev/null +++ b/src/slave/containerizer/provisioner/docker/store.hpp @@ -0,0 +1,58 @@ +/** + * 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_DOCKER_STORE_HPP__ +#define __MESOS_DOCKER_STORE_HPP__ + +#include <string> + +#include <stout/hashmap.hpp> +#include <stout/nothing.hpp> +#include <stout/option.hpp> +#include <stout/try.hpp> + +#include <process/future.hpp> + +#include "slave/containerizer/fetcher.hpp" + +#include "slave/containerizer/provisioner/store.hpp" + +#include "slave/containerizer/provisioner/docker/message.hpp" + +#include "slave/flags.hpp" + +namespace mesos { +namespace internal { +namespace slave { +namespace docker { + +// Store fetches the Docker images and stores them on disk. +// TODO(tnachen): Make this store the only docker store +// implementation, and move local and remote to different pullers. +class Store +{ +public: + static Try<process::Owned<slave::Store>> create(const Flags& flags); +}; + +} // namespace docker { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __MESOS_DOCKER_STORE_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioner/provisioner.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner/provisioner.hpp b/src/slave/containerizer/provisioner/provisioner.hpp index d42822b..912fc5a 100644 --- a/src/slave/containerizer/provisioner/provisioner.hpp +++ b/src/slave/containerizer/provisioner/provisioner.hpp @@ -67,7 +67,7 @@ public: // 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 http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioners/docker.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker.cpp b/src/slave/containerizer/provisioners/docker.cpp deleted file mode 100644 index b5c5a7b..0000000 --- a/src/slave/containerizer/provisioners/docker.cpp +++ /dev/null @@ -1,425 +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 "slave/containerizer/provisioners/docker.hpp" - -#include <glog/logging.h> - -#include <stout/json.hpp> -#include <stout/nothing.hpp> -#include <stout/os.hpp> - -#include <process/collect.hpp> -#include <process/defer.hpp> -#include <process/dispatch.hpp> -#include <process/owned.hpp> -#include <process/sequence.hpp> - -#include "slave/containerizer/provisioners/backend.hpp" -#include "slave/containerizer/provisioners/paths.hpp" - -#include "slave/containerizer/provisioners/docker/paths.hpp" -#include "slave/containerizer/provisioners/docker/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 docker { - -class DockerProvisionerProcess : - public process::Process<DockerProvisionerProcess> -{ -public: - static Try<process::Owned<DockerProvisionerProcess>> create( - const Flags& flags, - Fetcher* fetcher); - - process::Future<Nothing> recover( - const std::list<mesos::slave::ContainerState>& states, - const hashset<ContainerID>& orphans); - - process::Future<std::string> provision( - const ContainerID& containerId, - const Image& image); - - process::Future<bool> destroy(const ContainerID& containerId); - -private: - DockerProvisionerProcess( - const string& _rootDir, - const Flags& _flags, - const process::Owned<Store>& _store, - const hashmap<std::string, process::Owned<Backend>>& _backends); - - process::Future<std::string> _provision( - const DockerImage& image, - const ContainerID& containerId, - const string& rootfs); - - const string& rootDir; - const Flags flags; - process::Owned<Store> store; - hashmap<string, process::Owned<Backend>> backends; - - struct Info - { - // Mappings: backend -> rootfsId -> rootfsPath. - hashmap<string, hashmap<string, string>> rootfses; - }; - - hashmap<ContainerID, Owned<Info>> infos; -}; - - -Try<ImageName> ImageName::create(const std::string& name) -{ - ImageName imageName; - Option<string> registry = None(); - std::vector<std::string> components = strings::split(name, "/"); - if (components.size() > 2) { - registry = name.substr(0, name.find_last_of("/")); - } - - std::size_t found = components.back().find_last_of(':'); - if (found == std::string::npos) { - imageName.repository = components.back(); - imageName.tag = "latest"; - } else { - imageName.repository = components.back().substr(0, found); - imageName.tag = components.back().substr(found + 1); - } - - return imageName; -} - -Try<Owned<Provisioner>> DockerProvisioner::create( - const Flags& flags, - Fetcher* fetcher) -{ - Try<Owned<DockerProvisionerProcess>> create = - DockerProvisionerProcess::create(flags, fetcher); - if (create.isError()) { - return Error(create.error()); - } - - return Owned<Provisioner>(new DockerProvisioner(create.get())); -} - - -DockerProvisioner::DockerProvisioner(Owned<DockerProvisionerProcess> _process) - : process(_process) -{ - process::spawn(CHECK_NOTNULL(process.get())); -} - - -DockerProvisioner::~DockerProvisioner() -{ - process::terminate(process.get()); - process::wait(process.get()); -} - - -Future<Nothing> DockerProvisioner::recover( - const list<ContainerState>& states, - const hashset<ContainerID>& orphans) -{ - return dispatch( - process.get(), - &DockerProvisionerProcess::recover, - states, - orphans); -} - - -Future<string> DockerProvisioner::provision( - const ContainerID& containerId, - const Image& image) -{ - return dispatch( - process.get(), - &DockerProvisionerProcess::provision, - containerId, - image); -} - - -Future<bool> DockerProvisioner::destroy(const ContainerID& containerId) -{ - return dispatch( - process.get(), - &DockerProvisionerProcess::destroy, - containerId); -} - - -Try<Owned<DockerProvisionerProcess>> DockerProvisionerProcess::create( - const Flags& flags, - Fetcher* fetcher) -{ - string rootDir = - slave::paths::getProvisionerDir(flags.work_dir, Image::DOCKER); - - if (!os::exists(rootDir)) { - Try<Nothing> mkdir = os::mkdir(rootDir); - if (mkdir.isError()) { - return Error("Failed to create Docker provisioner root directory '" + - rootDir + "': " + mkdir.error()); - } - } - - const hashmap<string, Owned<Backend>> backends = Backend::create(flags); - if (backends.empty()) { - return Error("No usable Docker provisioner backend created"); - } - - if (!backends.contains(flags.docker_backend)) { - return Error("The specified Docker provisioner backend '" + - flags.docker_backend + "'is unsupported"); - } - - Try<Owned<Store>> store = Store::create(flags, fetcher); - if (store.isError()) { - return Error("Failed to create image store: " + store.error()); - } - - return Owned<DockerProvisionerProcess>( - new DockerProvisionerProcess( - rootDir, - flags, - store.get(), - backends)); -} - - -DockerProvisionerProcess::DockerProvisionerProcess( - const string& _rootDir, - const Flags& _flags, - const Owned<Store>& _store, - const hashmap<string, Owned<Backend>>& _backends) - : rootDir(_rootDir), - flags(_flags), - store(_store), - backends(_backends) {} - - -Future<Nothing> DockerProvisionerProcess::recover( - const list<ContainerState>& states, - const hashset<ContainerID>& orphans) -{ - // TODO(tnachen): Consider merging this with - // AppcProvisionerProcess::recover. - - // Register living containers, including the ones that do not - // provision Docker images. - hashset<ContainerID> alive; - - foreach (const ContainerState& state, states) { - if (state.executor_info().has_container() && - state.executor_info().container().type() == ContainerInfo::MESOS) { - 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(rootDir); - - if (containers.isError()) { - return Failure("Failed to list the containers managed by Docker " - "provisioner: " + containers.error()); - } - - // If no container has been launched the 'containers' directory will be empty. - foreachkey (const ContainerID& containerId, containers.get()) { - if (alive.contains(containerId) || orphans.contains(containerId)) { - Owned<Info> info = Owned<Info>(new Info()); - - Try<hashmap<string, hashmap<string, string>>> rootfses = - provisioners::paths::listContainerRootfses(rootDir, 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]); - } - - VLOG(1) << "Recovered container " << containerId; - infos.put(containerId, info); - - continue; - } - - // Destroy (unknown) orphan container's rootfses. - Try<hashmap<string, hashmap<string, string>>> rootfses = - provisioners::paths::listContainerRootfses(rootDir, containerId); - - if (rootfses.isError()) { - return Failure("Unable to find rootfses for 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); - } - - foreachvalue (const string& rootfs, rootfses.get()[backend]) { - VLOG(1) << "Destroying orphan rootfs " << rootfs; - - // Not waiting for the destruction and we don't care about - // the return value. - backends.get(backend).get()->destroy(rootfs) - .onFailed([rootfs](const std::string& error) { - LOG(WARNING) << "Failed to destroy orphan rootfs '" << rootfs - << "': "<< error; - }); - } - } - } - - LOG(INFO) << "Recovered Docker provisioner rootfses"; - - return store->recover() - .then([]() -> Future<Nothing> { - LOG(INFO) << "Recovered Docker image store"; - return Nothing(); - }); -} - - -Future<string> DockerProvisionerProcess::provision( - const ContainerID& containerId, - const Image& image) -{ - if (image.type() != Image::DOCKER) { - return Failure("Unsupported container image type"); - } - - if (!image.has_docker()) { - return Failure("Missing Docker image info"); - } - - string rootfsId = UUID::random().toString(); - string rootfs = provisioners::paths::getContainerRootfsDir( - rootDir, containerId, flags.docker_backend, rootfsId); - - if (!infos.contains(containerId)) { - infos.put(containerId, Owned<Info>(new Info())); - } - - infos[containerId]->rootfses[flags.docker_backend].put(rootfsId, rootfs); - - Try<ImageName> imageName = ImageName::create(image.docker().name()); - if (imageName.isError()) { - return Failure("Unable to able to parse Docker image name '" + - image.docker().name() + "': " + imageName.error()); - } - - return store->get(imageName.get()) - .then(defer(self(), &Self::_provision, lambda::_1, containerId, rootfs)); -} - - -Future<string> DockerProvisionerProcess::_provision( - const DockerImage& image, - const ContainerID& containerId, - const string& rootfs) -{ - CHECK(backends.contains(flags.docker_backend)); - - LOG(INFO) << "Provisioning rootfs for container '" << containerId << "'" - << " to '" << rootfs << "'"; - - vector<string> layerPaths; - foreach (const string& layerId, image.layerIds) { - layerPaths.push_back( - paths::getImageLayerRootfsPath(flags.docker_store_dir, layerId)); - } - - return backends[flags.docker_backend]->provision(layerPaths, rootfs) - .then([rootfs]() -> Future<string> { return rootfs; }); -} - - -Future<bool> DockerProvisionerProcess::destroy( - const ContainerID& containerId) -{ - // TODO(tnachen): Consider merging this with - // AppcProvisionerProcess::destroy. - 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)); - } - } - - return collect(futures) - .then([=](const list<bool>& results) -> Future<bool> { - return true; - }); -} - -} // namespace docker { -} // namespace slave { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioners/docker.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker.hpp b/src/slave/containerizer/provisioners/docker.hpp deleted file mode 100644 index cd7911c..0000000 --- a/src/slave/containerizer/provisioners/docker.hpp +++ /dev/null @@ -1,166 +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_DOCKER_HPP__ -#define __MESOS_DOCKER_HPP__ - -#include <list> -#include <ostream> -#include <sstream> -#include <string> -#include <utility> -#include <vector> - -#include <stout/hashmap.hpp> -#include <stout/json.hpp> -#include <stout/nothing.hpp> -#include <stout/option.hpp> -#include <stout/strings.hpp> -#include <stout/try.hpp> - -#include <process/future.hpp> -#include <process/owned.hpp> -#include <process/process.hpp> -#include <process/shared.hpp> - -#include <mesos/resources.hpp> - -#include "slave/containerizer/provisioner.hpp" - -#include "slave/flags.hpp" - -namespace mesos { -namespace internal { -namespace slave { -namespace docker { - -// Forward declaration. -class Store; - -/** - * Represents Docker Image Name, which composes of a repository and a - * tag. - */ -struct ImageName -{ - static Try<ImageName> create(const std::string& name); - - ImageName( - const std::string& _repository, - const std::string& _tag, - const Option<std::string>& _registry = None()) - : repository(_repository), - tag(_tag), - registry(_registry) {} - - ImageName() {} - - /** - * The string representation of this image. - */ - std::string name() const - { - if (registry.isSome()) { - return registry.get() + "/" + repository + ":" + tag; - } - - return repository + ":" + tag; - } - - /** - * Repository of this image (e.g, ubuntu). - */ - std::string repository; - - /** - * Tag of this image (e.g: 14.04). - */ - std::string tag; - - /** - * Custom registry that the image points to. - */ - Option<std::string> registry; -}; - - -inline std::ostream& operator<<(std::ostream& stream, const ImageName& image) -{ - return stream << image.name(); -} - - -/** - * Represents a Docker Image that holds its name and all of its layers - * sorted by its dependency. - */ -struct DockerImage -{ - DockerImage() {} - - DockerImage( - const ImageName& _imageName, - const std::list<std::string>& _layerIds) - : imageName(_imageName), layerIds(_layerIds) {} - - ImageName imageName; - std::list<std::string> layerIds; -}; - -// Forward declaration. -class DockerProvisionerProcess; - -/** - * Docker Provisioner is responsible to provision rootfs for - * containers with Docker images. - */ -class DockerProvisioner : public mesos::internal::slave::Provisioner -{ -public: - static Try<process::Owned<mesos::internal::slave::Provisioner>> create( - const Flags& flags, - Fetcher* fetcher); - - virtual ~DockerProvisioner(); - - 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 DockerProvisioner(process::Owned<DockerProvisionerProcess> _process); - - DockerProvisioner& operator=(const DockerProvisioner&) = delete; // Not assignable. - DockerProvisioner(const DockerProvisioner&) = delete; // Not copyable. - - process::Owned<DockerProvisionerProcess> process; -}; - - -} // namespace docker { -} // namespace slave { -} // namespace internal { -} // namespace mesos { - -#endif // __MESOS_DOCKER_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioners/docker/local_store.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/local_store.cpp b/src/slave/containerizer/provisioners/docker/local_store.cpp deleted file mode 100644 index ec0420e..0000000 --- a/src/slave/containerizer/provisioners/docker/local_store.cpp +++ /dev/null @@ -1,471 +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 "slave/containerizer/provisioners/docker/local_store.hpp" - -#include <list> -#include <vector> - -#include <glog/logging.h> - -#include <stout/json.hpp> -#include <stout/os.hpp> -#include <stout/result.hpp> - -#include <process/collect.hpp> -#include <process/defer.hpp> -#include <process/dispatch.hpp> - -#include "common/status_utils.hpp" - -#include "slave/containerizer/fetcher.hpp" - -#include "slave/containerizer/provisioners/docker/metadata_manager.hpp" -#include "slave/containerizer/provisioners/docker/paths.hpp" -#include "slave/containerizer/provisioners/docker/store.hpp" - -#include "slave/flags.hpp" - -using namespace process; - -using std::list; -using std::string; -using std::vector; - -namespace mesos { -namespace internal { -namespace slave { -namespace docker { - -class LocalStoreProcess : public process::Process<LocalStoreProcess> -{ -public: - ~LocalStoreProcess() {} - - static Try<process::Owned<LocalStoreProcess>> create( - const Flags& flags, - Fetcher* fetcher); - - process::Future<DockerImage> get(const ImageName& name); - - process::Future<Nothing> recover(); - -private: - LocalStoreProcess( - const Flags& _flags, - Owned<MetadataManager> _metadataManager) - : flags(_flags), metadataManager(_metadataManager) {} - - process::Future<Nothing> untarImage( - const std::string& tarPath, - const std::string& staging); - - process::Future<DockerImage> putImage( - const ImageName& name, - const std::string& staging); - - Result<std::string> getParentId( - const std::string& staging, - const std::string& layerId); - - process::Future<Nothing> putLayers( - const std::string& staging, - const std::list<std::string>& layerIds); - - process::Future<Nothing> putLayer( - const std::string& staging, - const std::string& id); - - process::Future<Nothing> moveLayer( - const std::string& staging, - const std::string& id); - - const Flags flags; - process::Owned<MetadataManager> metadataManager; -}; - - -Try<Owned<Store>> Store::create( - const Flags& flags, - Fetcher* fetcher) -{ - hashmap<string, Try<Owned<Store>>(*)(const Flags&, Fetcher*)> creators{ - {"local", &LocalStore::create} - }; - - if (!creators.contains(flags.docker_store)) { - return Error("Unknown Docker store: " + flags.docker_store); - } - - return creators[flags.docker_store](flags, fetcher); -} - - -Try<Owned<Store>> LocalStore::create( - const Flags& flags, - Fetcher* fetcher) -{ - Try<Owned<LocalStoreProcess>> process = - LocalStoreProcess::create(flags, fetcher); - if (process.isError()) { - return Error(process.error()); - } - - return Owned<Store>(new LocalStore(process.get())); -} - - -LocalStore::LocalStore(Owned<LocalStoreProcess> _process) : process(_process) -{ - process::spawn(CHECK_NOTNULL(process.get())); -} - - -LocalStore::~LocalStore() -{ - process::terminate(process.get()); - process::wait(process.get()); -} - - -Future<DockerImage> LocalStore::get(const ImageName& name) -{ - return dispatch(process.get(), &LocalStoreProcess::get, name); -} - - -Future<Nothing> LocalStore::recover() -{ - return dispatch(process.get(), &LocalStoreProcess::recover); -} - - -Try<Owned<LocalStoreProcess>> LocalStoreProcess::create( - const Flags& flags, - Fetcher* fetcher) -{ - if (!os::exists(flags.docker_store_dir)) { - Try<Nothing> mkdir = os::mkdir(flags.docker_store_dir); - if (mkdir.isError()) { - return Error("Failed to create Docker store directory: " + mkdir.error()); - } - } - - if (!os::exists(paths::getStagingDir(flags.docker_store_dir))) { - Try<Nothing> mkdir = - os::mkdir(paths::getStagingDir(flags.docker_store_dir)); - if (mkdir.isError()) { - return Error("Failed to create Docker store staging directory: " + - mkdir.error()); - } - } - - Try<Owned<MetadataManager>> metadataManager = MetadataManager::create(flags); - if (metadataManager.isError()) { - return Error(metadataManager.error()); - } - - return Owned<LocalStoreProcess>( - new LocalStoreProcess(flags, metadataManager.get())); -} - - -Future<DockerImage> LocalStoreProcess::get(const ImageName& name) -{ - return metadataManager->get(name) - .then(defer(self(), - [this, name]( - const Option<DockerImage>& image) -> Future<DockerImage> { - if (image.isSome()) { - return image.get(); - } - - string tarPath = paths::getLocalImageTarPath( - flags.docker_discovery_local_dir, - name.name()); - - if (!os::exists(tarPath)) { - VLOG(1) << "Unable to find image in local store with path: " << tarPath; - return Failure("No Docker image tar archive found"); - } - - // Create a temporary staging directory. - Try<string> staging = - os::mkdtemp(paths::getTempStaging(flags.docker_store_dir)); - if (staging.isError()) { - return Failure("Failed to create a staging directory"); - } - - return untarImage(tarPath, staging.get()) - .then(defer(self(), &Self::putImage, name, staging.get())); - })); -} - - -Future<Nothing> LocalStoreProcess::recover() -{ - return metadataManager->recover(); -} - -Future<Nothing> LocalStoreProcess::untarImage( - const string& tarPath, - const string& staging) -{ - VLOG(1) << "Untarring image at: " << tarPath; - - // Untar discovery_local_dir/name.tar into staging/. - vector<string> argv = { - "tar", - "-C", - staging, - "-x", - "-f", - tarPath - }; - - Try<Subprocess> s = subprocess( - "tar", - argv, - Subprocess::PATH("/dev/null"), - Subprocess::PATH("/dev/null"), - Subprocess::PATH("/dev/null")); - - if (s.isError()) { - return Failure("Failed to create tar subprocess: " + s.error()); - } - - return s.get().status() - .then([=](const Option<int>& status) -> Future<Nothing> { - if (status.isNone()) { - return Failure("Failed to reap status for tar subprocess in " + - tarPath); - } - if (!WIFEXITED(status.get()) || WEXITSTATUS(status.get()) != 0) { - return Failure("Untar image failed with exit code: " + - WSTRINGIFY(status.get())); - } - - return Nothing(); - }); -} - - -Future<DockerImage> LocalStoreProcess::putImage( - const ImageName& name, - const string& staging) -{ - Try<string> value = os::read(paths::getLocalImageRepositoriesPath(staging)); - if (value.isError()) { - return Failure("Failed to read repository JSON: " + value.error()); - } - - Try<JSON::Object> json = JSON::parse<JSON::Object>(value.get()); - if (json.isError()) { - return Failure("Failed to parse JSON: " + json.error()); - } - - Result<JSON::Object> repositoryValue = - json.get().find<JSON::Object>(name.repository); - if (repositoryValue.isError()) { - return Failure("Failed to find repository: " + repositoryValue.error()); - } else if (repositoryValue.isNone()) { - return Failure("Repository '" + name.repository + "' is not found"); - } - - JSON::Object repositoryJson = repositoryValue.get(); - - // We don't use JSON find here because a tag might contain a '.'. - std::map<string, JSON::Value>::const_iterator entry = - repositoryJson.values.find(name.tag); - if (entry == repositoryJson.values.end()) { - return Failure("Tag '" + name.tag + "' is not found"); - } else if (!entry->second.is<JSON::String>()) { - return Failure("Tag JSON value expected to be JSON::String"); - } - - string layerId = entry->second.as<JSON::String>().value; - - Try<string> manifest = - os::read(paths::getLocalImageLayerManifestPath(staging, layerId)); - if (manifest.isError()) { - return Failure("Failed to read manifest: " + manifest.error()); - } - - Try<JSON::Object> manifestJson = JSON::parse<JSON::Object>(manifest.get()); - if (manifestJson.isError()) { - return Failure("Failed to parse manifest: " + manifestJson.error()); - } - - list<string> layerIds; - layerIds.push_back(layerId); - Result<string> parentId = getParentId(staging, layerId); - while(parentId.isSome()) { - layerIds.push_front(parentId.get()); - parentId = getParentId(staging, parentId.get()); - } - if (parentId.isError()) { - return Failure("Failed to obtain parent layer id: " + parentId.error()); - } - - return putLayers(staging, layerIds) - .then([=]() -> Future<DockerImage> { - return metadataManager->put(name, layerIds); - }); -} - - -Result<string> LocalStoreProcess::getParentId( - const string& staging, - const string& layerId) -{ - Try<string> manifest = - os::read(paths::getLocalImageLayerManifestPath(staging, layerId)); - if (manifest.isError()) { - return Error("Failed to read manifest: " + manifest.error()); - } - - Try<JSON::Object> json = JSON::parse<JSON::Object>(manifest.get()); - if (json.isError()) { - return Error("Failed to parse manifest: " + json.error()); - } - - Result<JSON::String> parentId = json.get().find<JSON::String>("parent"); - if (parentId.isNone() || (parentId.isSome() && parentId.get() == "")) { - return None(); - } else if (parentId.isError()) { - return Error("Failed to read parent of layer: " + parentId.error()); - } - return parentId.get().value; -} - - -Future<Nothing> LocalStoreProcess::putLayers( - const string& staging, - const list<string>& layerIds) -{ - list<Future<Nothing>> futures{ Nothing() }; - foreach (const string& layer, layerIds) { - futures.push_back( - futures.back().then( - defer(self(), &Self::putLayer, staging, layer))); - } - - return collect(futures) - .then([]() -> Future<Nothing> { return Nothing(); }); -} - - -Future<Nothing> LocalStoreProcess::putLayer( - const string& staging, - const string& id) -{ - // We untar the layer from source into a staging directory, then - // move the layer into store. We do this instead of untarring - // directly to store to make sure we don't end up with partially - // untarred layer rootfs. - - // Check if image layer rootfs is already in store. - if (os::exists(paths::getImageLayerRootfsPath(flags.docker_store_dir, id))) { - VLOG(1) << "Image layer '" << id << "' rootfs already in store. " - << "Skipping put."; - return Nothing(); - } - - const string imageLayerPath = - paths::getImageLayerPath(flags.docker_store_dir, id); - if (!os::exists(imageLayerPath)) { - Try<Nothing> mkdir = os::mkdir(imageLayerPath); - if (mkdir.isError()) { - return Failure("Failed to create Image layer directory '" + - imageLayerPath + "': " + mkdir.error()); - } - } - - // Image layer has been untarred but is not present in the store directory. - string localRootfsPath = paths::getLocalImageLayerRootfsPath(staging, id); - if (os::exists(localRootfsPath)) { - LOG(WARNING) << "Image layer '" << id << "' rootfs present at but not in " - << "store directory '" << localRootfsPath << "'. Removing " - << "staged rootfs and untarring layer again."; - Try<Nothing> rmdir = os::rmdir(localRootfsPath); - if (rmdir.isError()) { - return Failure("Failed to remove incomplete staged rootfs for layer '" + - id + "': " + rmdir.error()); - } - } - - Try<Nothing> mkdir = os::mkdir(localRootfsPath); - if (mkdir.isError()) { - return Failure("Failed to create rootfs path '" + localRootfsPath + "': " + - mkdir.error()); - } - // Untar staging/id/layer.tar into staging/id/rootfs. - vector<string> argv = { - "tar", - "-C", - localRootfsPath, - "-x", - "-f", - paths::getLocalImageLayerTarPath(staging, id) - }; - - Try<Subprocess> s = subprocess( - "tar", - argv, - Subprocess::PATH("/dev/null"), - Subprocess::PATH("/dev/null"), - Subprocess::PATH("/dev/null")); - if (s.isError()) { - return Failure("Failed to create tar subprocess: " + s.error()); - } - - return s.get().status() - .then([=](const Option<int>& status) -> Future<Nothing> { - if (status.isNone()) { - return Failure("Failed to reap subprocess to untar image"); - } else if (!WIFEXITED(status.get()) || WEXITSTATUS(status.get()) != 0) { - return Failure("Untar failed with exit code: " + - WSTRINGIFY(status.get())); - } - - return moveLayer(staging, id); - }); -} - - -Future<Nothing> LocalStoreProcess::moveLayer( - const string& staging, - const string& id) -{ - Try<Nothing> status = os::rename( - paths::getLocalImageLayerRootfsPath(staging, id), - paths::getImageLayerRootfsPath(flags.docker_store_dir, id)); - - if (status.isError()) { - return Failure("Failed to move layer to store directory: " - + status.error()); - } - - return Nothing(); -} - - -} // namespace docker { -} // namespace slave { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/4dedbf4a/src/slave/containerizer/provisioners/docker/local_store.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/local_store.hpp b/src/slave/containerizer/provisioners/docker/local_store.hpp deleted file mode 100644 index 64a6fc0..0000000 --- a/src/slave/containerizer/provisioners/docker/local_store.hpp +++ /dev/null @@ -1,66 +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_DOCKER_LOCAL_STORE_HPP__ -#define __MESOS_DOCKER_LOCAL_STORE_HPP__ - -#include "slave/containerizer/provisioners/docker/store.hpp" - -namespace mesos { -namespace internal { -namespace slave { -namespace docker { - -// Forward declaration. -class LocalStoreProcess; - - -/** - * LocalStore assumes Docker images are stored in a local directory - * (configured with flags.docker_discovery_local_dir), with all the - * images saved as tar with the name as the image name with tag (e.g: - * ubuntu:14.04.tar). - */ -class LocalStore : public Store -{ -public: - static Try<process::Owned<Store>> create( - const Flags& flags, - Fetcher* fetcher); - - virtual ~LocalStore(); - - virtual process::Future<DockerImage> get(const ImageName& name); - - virtual process::Future<Nothing> recover(); - -private: - explicit LocalStore(process::Owned<LocalStoreProcess> _process); - - LocalStore& operator=(const LocalStore&) = delete; // Not assignable. - LocalStore(const LocalStore&) = delete; // Not copyable. - - process::Owned<LocalStoreProcess> process; -}; - -} // namespace docker { -} // namespace slave { -} // namespace internal { -} // namespace mesos { - -#endif // __MESOS_DOCKER_LOCAL_STORE_HPP__
