Rename Docker reference store to metadata manager.
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/2cc0dfb2 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/2cc0dfb2 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/2cc0dfb2 Branch: refs/heads/master Commit: 2cc0dfb20643188570bcc61af6e66c83297ade8d Parents: f392f75 Author: Timothy Chen <[email protected]> Authored: Tue Sep 8 22:19:05 2015 -0700 Committer: Timothy Chen <[email protected]> Committed: Fri Sep 25 09:02:05 2015 -0700 ---------------------------------------------------------------------- src/Makefile.am | 4 +- src/slave/containerizer/provisioners/docker.cpp | 27 +- src/slave/containerizer/provisioners/docker.hpp | 76 ++++-- .../provisioners/docker/local_store.cpp | 65 ++--- .../provisioners/docker/local_store.hpp | 12 +- .../provisioners/docker/metadata_manager.cpp | 254 +++++++++++++++++++ .../provisioners/docker/metadata_manager.hpp | 104 ++++++++ .../provisioners/docker/reference_store.cpp | 249 ------------------ .../provisioners/docker/reference_store.hpp | 102 -------- .../containerizer/provisioners/docker/store.hpp | 4 +- .../containerizer/provisioner_docker_tests.cpp | 27 +- 11 files changed, 492 insertions(+), 432 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index cd8b2ca..a2a64dc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -531,8 +531,8 @@ libmesos_no_3rdparty_la_SOURCES = \ 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/reference_store.cpp \ slave/containerizer/provisioner/docker/registry_client.cpp \ slave/containerizer/provisioner/docker/token_manager.cpp \ slave/resource_estimators/noop.cpp \ @@ -831,8 +831,8 @@ 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/paths.hpp \ - slave/containerizer/provisioner/docker/reference_store.hpp \ slave/containerizer/provisioner/docker/registry_client.hpp \ slave/containerizer/provisioner/docker/store.hpp \ slave/containerizer/provisioner/docker/token_manager.hpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/src/slave/containerizer/provisioners/docker.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker.cpp b/src/slave/containerizer/provisioners/docker.cpp index b1f737f..b5c5a7b 100644 --- a/src/slave/containerizer/provisioners/docker.cpp +++ b/src/slave/containerizer/provisioners/docker.cpp @@ -97,9 +97,10 @@ private: }; -ImageName::ImageName(const std::string& name) +Try<ImageName> ImageName::create(const std::string& name) { - registry = None(); + 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("/")); @@ -107,12 +108,14 @@ ImageName::ImageName(const std::string& name) std::size_t found = components.back().find_last_of(':'); if (found == std::string::npos) { - repo = components.back(); - tag = "latest"; + imageName.repository = components.back(); + imageName.tag = "latest"; } else { - repo = components.back().substr(0, found); - tag = components.back().substr(found + 1); + imageName.repository = components.back().substr(0, found); + imageName.tag = components.back().substr(found + 1); } + + return imageName; } Try<Owned<Provisioner>> DockerProvisioner::create( @@ -191,7 +194,7 @@ Try<Owned<DockerProvisionerProcess>> DockerProvisionerProcess::create( } } - hashmap<string, Owned<Backend>> backends = Backend::create(flags); + const hashmap<string, Owned<Backend>> backends = Backend::create(flags); if (backends.empty()) { return Error("No usable Docker provisioner backend created"); } @@ -345,7 +348,13 @@ Future<string> DockerProvisionerProcess::provision( infos[containerId]->rootfses[flags.docker_backend].put(rootfsId, rootfs); - return store->get(image.docker().name()) + 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)); } @@ -361,7 +370,7 @@ Future<string> DockerProvisionerProcess::_provision( << " to '" << rootfs << "'"; vector<string> layerPaths; - foreach (const string& layerId, image.layers) { + foreach (const string& layerId, image.layerIds) { layerPaths.push_back( paths::getImageLayerRootfsPath(flags.docker_store_dir, layerId)); } http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/src/slave/containerizer/provisioners/docker.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker.hpp b/src/slave/containerizer/provisioners/docker.hpp index d3ada9d..cd7911c 100644 --- a/src/slave/containerizer/provisioners/docker.hpp +++ b/src/slave/containerizer/provisioners/docker.hpp @@ -52,50 +52,83 @@ namespace docker { // Forward declaration. class Store; +/** + * Represents Docker Image Name, which composes of a repository and a + * tag. + */ struct ImageName { - std::string repo; - std::string tag; - Option<std::string> registry; - - ImageName(const std::string& name); + static Try<ImageName> create(const std::string& name); ImageName( - const std::string& repo, - const std::string& tag, - const Option<std::string>& registry = None()) - : repo(repo), tag(tag), registry(registry) {} + 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) { - if (image.registry.isSome()) { - return stream << image.registry.get() - << "/" << image.repo << ":" << image.tag; - } - return stream << image.repo << ":" << image.tag; + 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 std::string& imageName, - const std::list<std::string>& layers) - : imageName(imageName), layers(layers) {} + const ImageName& _imageName, + const std::list<std::string>& _layerIds) + : imageName(_imageName), layerIds(_layerIds) {} - std::string imageName; - std::list<std::string> layers; + 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: @@ -117,8 +150,9 @@ public: private: explicit DockerProvisioner(process::Owned<DockerProvisionerProcess> _process); - DockerProvisioner(const DockerProvisioner&); // Not copyable. - DockerProvisioner& operator=(const DockerProvisioner&); // Not assignable. + + DockerProvisioner& operator=(const DockerProvisioner&) = delete; // Not assignable. + DockerProvisioner(const DockerProvisioner&) = delete; // Not copyable. process::Owned<DockerProvisionerProcess> process; }; http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/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 index 80b5b06..ec0420e 100644 --- a/src/slave/containerizer/provisioners/docker/local_store.cpp +++ b/src/slave/containerizer/provisioners/docker/local_store.cpp @@ -35,8 +35,9 @@ #include "slave/containerizer/fetcher.hpp" -#include "slave/containerizer/provisioners/docker/store.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" @@ -60,22 +61,22 @@ public: const Flags& flags, Fetcher* fetcher); - process::Future<DockerImage> get(const std::string& name); + process::Future<DockerImage> get(const ImageName& name); process::Future<Nothing> recover(); private: LocalStoreProcess( const Flags& _flags, - Owned<ReferenceStore> _refStore) - : flags(_flags), refStore(_refStore) {} + Owned<MetadataManager> _metadataManager) + : flags(_flags), metadataManager(_metadataManager) {} process::Future<Nothing> untarImage( const std::string& tarPath, const std::string& staging); process::Future<DockerImage> putImage( - const std::string& name, + const ImageName& name, const std::string& staging); Result<std::string> getParentId( @@ -84,7 +85,7 @@ private: process::Future<Nothing> putLayers( const std::string& staging, - const std::list<std::string>& layers); + const std::list<std::string>& layerIds); process::Future<Nothing> putLayer( const std::string& staging, @@ -95,7 +96,7 @@ private: const std::string& id); const Flags flags; - process::Owned<ReferenceStore> refStore; + process::Owned<MetadataManager> metadataManager; }; @@ -142,7 +143,7 @@ LocalStore::~LocalStore() } -Future<DockerImage> LocalStore::get(const string& name) +Future<DockerImage> LocalStore::get(const ImageName& name) { return dispatch(process.get(), &LocalStoreProcess::get, name); } @@ -174,18 +175,19 @@ Try<Owned<LocalStoreProcess>> LocalStoreProcess::create( } } - Try<Owned<ReferenceStore>> refStore = ReferenceStore::create(flags); - if (refStore.isError()) { - return Error(refStore.error()); + Try<Owned<MetadataManager>> metadataManager = MetadataManager::create(flags); + if (metadataManager.isError()) { + return Error(metadataManager.error()); } - return Owned<LocalStoreProcess>(new LocalStoreProcess(flags, refStore.get())); + return Owned<LocalStoreProcess>( + new LocalStoreProcess(flags, metadataManager.get())); } -Future<DockerImage> LocalStoreProcess::get(const string& name) +Future<DockerImage> LocalStoreProcess::get(const ImageName& name) { - return refStore->get(name) + return metadataManager->get(name) .then(defer(self(), [this, name]( const Option<DockerImage>& image) -> Future<DockerImage> { @@ -193,9 +195,12 @@ Future<DockerImage> LocalStoreProcess::get(const string& name) return image.get(); } - string tarPath = - paths::getLocalImageTarPath(flags.docker_discovery_local_dir, name); + 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"); } @@ -214,7 +219,7 @@ Future<DockerImage> LocalStoreProcess::get(const string& name) Future<Nothing> LocalStoreProcess::recover() { - return refStore->recover(); + return metadataManager->recover(); } Future<Nothing> LocalStoreProcess::untarImage( @@ -261,11 +266,9 @@ Future<Nothing> LocalStoreProcess::untarImage( Future<DockerImage> LocalStoreProcess::putImage( - const std::string& name, + const ImageName& name, const string& staging) { - ImageName imageName(name); - Try<string> value = os::read(paths::getLocalImageRepositoriesPath(staging)); if (value.isError()) { return Failure("Failed to read repository JSON: " + value.error()); @@ -277,20 +280,20 @@ Future<DockerImage> LocalStoreProcess::putImage( } Result<JSON::Object> repositoryValue = - json.get().find<JSON::Object>(imageName.repo); + 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 '" + imageName.repo + "' is not found"); + 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(imageName.tag); + repositoryJson.values.find(name.tag); if (entry == repositoryJson.values.end()) { - return Failure("Tag '" + imageName.tag + "' is not found"); + 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"); } @@ -308,20 +311,20 @@ Future<DockerImage> LocalStoreProcess::putImage( return Failure("Failed to parse manifest: " + manifestJson.error()); } - list<string> layers; - layers.push_back(layerId); + list<string> layerIds; + layerIds.push_back(layerId); Result<string> parentId = getParentId(staging, layerId); while(parentId.isSome()) { - layers.push_front(parentId.get()); + 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, layers) + return putLayers(staging, layerIds) .then([=]() -> Future<DockerImage> { - return refStore->put(name, layers); + return metadataManager->put(name, layerIds); }); } @@ -353,10 +356,10 @@ Result<string> LocalStoreProcess::getParentId( Future<Nothing> LocalStoreProcess::putLayers( const string& staging, - const list<string>& layers) + const list<string>& layerIds) { list<Future<Nothing>> futures{ Nothing() }; - foreach (const string& layer, layers) { + foreach (const string& layer, layerIds) { futures.push_back( futures.back().then( defer(self(), &Self::putLayer, staging, layer))); http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/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 index b650b5e..64a6fc0 100644 --- a/src/slave/containerizer/provisioners/docker/local_store.hpp +++ b/src/slave/containerizer/provisioners/docker/local_store.hpp @@ -28,7 +28,6 @@ namespace docker { // Forward declaration. class LocalStoreProcess; -class ReferenceStore; /** @@ -40,22 +39,21 @@ class ReferenceStore; class LocalStore : public Store { public: - virtual ~LocalStore(); - static Try<process::Owned<Store>> create( const Flags& flags, Fetcher* fetcher); - virtual process::Future<DockerImage> get(const std::string& name); + virtual ~LocalStore(); + + virtual process::Future<DockerImage> get(const ImageName& name); virtual process::Future<Nothing> recover(); private: explicit LocalStore(process::Owned<LocalStoreProcess> _process); - LocalStore(const LocalStore&); // Not copyable. - - LocalStore& operator=(const LocalStore&); // Not assignable. + LocalStore& operator=(const LocalStore&) = delete; // Not assignable. + LocalStore(const LocalStore&) = delete; // Not copyable. process::Owned<LocalStoreProcess> process; }; http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/src/slave/containerizer/provisioners/docker/metadata_manager.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/metadata_manager.cpp b/src/slave/containerizer/provisioners/docker/metadata_manager.cpp new file mode 100644 index 0000000..55eb382 --- /dev/null +++ b/src/slave/containerizer/provisioners/docker/metadata_manager.cpp @@ -0,0 +1,254 @@ +/** + * 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 "messages/docker_provisioner.hpp" + +#include "slave/containerizer/provisioners/docker/paths.hpp" +#include "slave/containerizer/provisioners/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 ImageName& name, + const std::list<std::string>& layerIds); + + Future<Option<DockerImage>> get(const ImageName& 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 ImageName& name, + const list<string>& layerIds) +{ + return dispatch( + process.get(), &MetadataManagerProcess::put, name, layerIds); +} + + +Future<Option<DockerImage>> MetadataManager::get(const ImageName& 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 ImageName& name, + const list<string>& layerIds) +{ + storedImages[name.name()] = DockerImage(name, layerIds); + + Try<Nothing> status = persist(); + if (status.isError()) { + return Failure("Failed to save state of Docker images" + status.error()); + } + + return storedImages[name.name()]; +} + + +Future<Option<DockerImage>> MetadataManagerProcess::get(const ImageName& name) +{ + if (!storedImages.contains(name.name())) { + return None(); + } + + return storedImages[name.name()]; +} + + +Try<Nothing> MetadataManagerProcess::persist() +{ + DockerProvisionerImages images; + + foreachpair( + const string& name, const DockerImage& dockerImage, storedImages) { + DockerProvisionerImages::Image* image = images.add_images(); + + image->set_name(name); + + foreach (const string& layerId, dockerImage.layerIds) { + image->add_layer_ids(layerId); + } + } + + 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<DockerProvisionerImages> images = + ::protobuf::read<DockerProvisionerImages>(storedImagesPath); + if (images.isError()) { + return Failure("Failed to read protobuf for Docker provisioner image: " + + images.error()); + } + + for (int i = 0; i < images.get().images_size(); i++) { + string name = images.get().images(i).name(); + + list<string> layerIds; + vector<string> missingLayerIds; + for (int j = 0; j < images.get().images(i).layer_ids_size(); j++) { + string layerId = images.get().images(i).layer_ids(j); + + layerIds.push_back(layerId); + + if (!os::exists( + paths::getImageLayerRootfsPath(flags.docker_store_dir, layerId))) { + missingLayerIds.push_back(layerId); + } + } + + if (!missingLayerIds.empty()) { + foreach (const string& layerId, missingLayerIds) { + LOG(WARNING) << "Image layer: " << layerId << " required for Docker " + << "image: " << name << " is not on disk."; + } + LOG(WARNING) << "Skipped loading image: " << name + << " due to missing layers."; + continue; + } + + Try<ImageName> imageName = ImageName::create(name); + if (imageName.isError()) { + return Failure("Unable to parse Docker image name: " + imageName.error()); + } + storedImages[imageName.get().name()] = DockerImage(imageName.get(), layerIds); + } + + 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/2cc0dfb2/src/slave/containerizer/provisioners/docker/metadata_manager.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/metadata_manager.hpp b/src/slave/containerizer/provisioners/docker/metadata_manager.hpp new file mode 100644 index 0000000..9db3f47 --- /dev/null +++ b/src/slave/containerizer/provisioners/docker/metadata_manager.hpp @@ -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. + */ + +#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/provisioners/docker.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 DockerImage, 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 ImageName& 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 ImageName& 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/2cc0dfb2/src/slave/containerizer/provisioners/docker/reference_store.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/reference_store.cpp b/src/slave/containerizer/provisioners/docker/reference_store.cpp deleted file mode 100644 index 4b72319..0000000 --- a/src/slave/containerizer/provisioners/docker/reference_store.cpp +++ /dev/null @@ -1,249 +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 <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 "messages/docker_provisioner.hpp" - -#include "slave/containerizer/provisioners/docker/paths.hpp" -#include "slave/containerizer/provisioners/docker/reference_store.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 ReferenceStoreProcess : public process::Process<ReferenceStoreProcess> -{ -public: - ~ReferenceStoreProcess() {} - - static Try<process::Owned<ReferenceStoreProcess>> create(const Flags& flags); - - Future<DockerImage> put( - const std::string& name, - const std::list<std::string>& layers); - - Future<Option<DockerImage>> get(const std::string& name); - - Future<Nothing> recover(); - - // TODO(chenlily): Implement removal of unreferenced images. - -private: - ReferenceStoreProcess(const Flags& flags); - - // Write out reference store 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<ReferenceStore>> ReferenceStore::create(const Flags& flags) -{ - Try<Owned<ReferenceStoreProcess>> process = - ReferenceStoreProcess::create(flags); - if (process.isError()) { - return Error("Failed to create reference store: " + process.error()); - } - return Owned<ReferenceStore>(new ReferenceStore(process.get())); -} - - -ReferenceStore::ReferenceStore(Owned<ReferenceStoreProcess> process) - : process(process) -{ - process::spawn(CHECK_NOTNULL(process.get())); -} - - -ReferenceStore::~ReferenceStore() -{ - process::terminate(process.get()); - process::wait(process.get()); -} - - -Future<Nothing> ReferenceStore::recover() -{ - return process::dispatch(process.get(), &ReferenceStoreProcess::recover); -} - - -Future<DockerImage> ReferenceStore::put( - const string& name, - const list<string>& layers) -{ - return dispatch( - process.get(), &ReferenceStoreProcess::put, name, layers); -} - - -Future<Option<DockerImage>> ReferenceStore::get(const string& name) -{ - return dispatch(process.get(), &ReferenceStoreProcess::get, name); -} - - -ReferenceStoreProcess::ReferenceStoreProcess(const Flags& flags) - : flags(flags) {} - - -Try<Owned<ReferenceStoreProcess>> ReferenceStoreProcess::create( - const Flags& flags) -{ - Owned<ReferenceStoreProcess> referenceStore = - Owned<ReferenceStoreProcess>(new ReferenceStoreProcess(flags)); - - return referenceStore; -} - - -Future<DockerImage> ReferenceStoreProcess::put( - const string& name, - const list<string>& layers) -{ - storedImages[name] = DockerImage(name, layers); - - Try<Nothing> status = persist(); - if (status.isError()) { - return Failure("Failed to save state of Docker images" + status.error()); - } - - return storedImages[name]; -} - - -Future<Option<DockerImage>> ReferenceStoreProcess::get(const string& name) -{ - if (!storedImages.contains(name)) { - return None(); - } - - return storedImages[name]; -} - - -Try<Nothing> ReferenceStoreProcess::persist() -{ - DockerProvisionerImages images; - - foreachpair( - const string& name, const DockerImage& dockerImage, storedImages) { - DockerProvisionerImages::Image* image = images.add_images(); - - image->set_name(name); - - foreach (const string& layer, dockerImage.layers) { - image->add_layer_ids(layer); - } - } - - 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> ReferenceStoreProcess::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<DockerProvisionerImages> images = - ::protobuf::read<DockerProvisionerImages>(storedImagesPath); - if (images.isError()) { - return Failure("Failed to read protobuf for Docker provisioner image: " + - images.error()); - } - - for (int i = 0; i < images.get().images_size(); i++) { - string imageName = images.get().images(i).name(); - - list<string> layers; - vector<string> missingLayers; - for (int j = 0; j < images.get().images(i).layer_ids_size(); j++) { - string layerId = images.get().images(i).layer_ids(j); - - layers.push_back(layerId); - - if (!os::exists( - paths::getImageLayerRootfsPath(flags.docker_store_dir, layerId))) { - missingLayers.push_back(layerId); - } - } - - if (!missingLayers.empty()) { - foreach (const string& layer, missingLayers) { - LOG(WARNING) << "Image layer: " << layer << " required for Docker " - << "image: " << imageName << " is not on disk."; - } - LOG(WARNING) << "Skipped loading image: " << imageName - << " due to missing layers."; - continue; - } - - storedImages[imageName] = DockerImage(imageName, layers); - } - - 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/2cc0dfb2/src/slave/containerizer/provisioners/docker/reference_store.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/reference_store.hpp b/src/slave/containerizer/provisioners/docker/reference_store.hpp deleted file mode 100644 index be652ae..0000000 --- a/src/slave/containerizer/provisioners/docker/reference_store.hpp +++ /dev/null @@ -1,102 +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_REFERENCE_STORE_HPP__ -#define __MESOS_DOCKER_REFERENCE_STORE_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/provisioners/docker.hpp" -#include "slave/flags.hpp" - -namespace mesos { -namespace internal { -namespace slave { -namespace docker { - -// Forward Declaration. -class ReferenceStoreProcess; - -/** - * The Reference Store is a way to track the Docker images used by the - * provisioner that are stored in 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 ReferenceStore -{ -public: - ~ReferenceStore(); - - static Try<process::Owned<ReferenceStore>> create(const Flags& flags); - - /** - * Create a DockerImage, put it in reference store and persist the reference - * store state to disk. - * - * @param name the name of the Docker image to place in the reference store. - * @param layers 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 std::string& name, - const std::list<std::string>& layers); - - /** - * 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 std::string& name); - - /** - * Recover all stored DockerImage and its layer references. - */ - process::Future<Nothing> recover(); - -private: - explicit ReferenceStore(process::Owned<ReferenceStoreProcess> process); - - ReferenceStore(const ReferenceStore&); // Not copyable. - ReferenceStore& operator=(const ReferenceStore&); // Not assignable. - - process::Owned<ReferenceStoreProcess> process; -}; - - -} // namespace docker { -} // namespace slave { -} // namespace internal { -} // namespace mesos { - -#endif // __MESOS_DOCKER_REFERENCE_STORE_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/src/slave/containerizer/provisioners/docker/store.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/store.hpp b/src/slave/containerizer/provisioners/docker/store.hpp index a9201d5..03958bf 100644 --- a/src/slave/containerizer/provisioners/docker/store.hpp +++ b/src/slave/containerizer/provisioners/docker/store.hpp @@ -30,7 +30,7 @@ #include "slave/containerizer/fetcher.hpp" #include "slave/containerizer/provisioners/docker.hpp" -#include "slave/containerizer/provisioners/docker/reference_store.hpp" + #include "slave/flags.hpp" namespace mesos { @@ -55,7 +55,7 @@ public: * * @return The DockerImage that holds the Docker layers. */ - virtual process::Future<DockerImage> get(const std::string& name) = 0; + virtual process::Future<DockerImage> get(const ImageName& name) = 0; /** * Recover all stored images http://git-wip-us.apache.org/repos/asf/mesos/blob/2cc0dfb2/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 index e1a311b..dd4f813 100644 --- a/src/tests/containerizer/provisioner_docker_tests.cpp +++ b/src/tests/containerizer/provisioner_docker_tests.cpp @@ -37,12 +37,15 @@ #include <process/ssl/gtest.hpp> +#include "slave/containerizer/provisioner/docker/metadata_manager.hpp" #include "slave/containerizer/provisioner/docker/registry_client.hpp" +#include "slave/containerizer/provisioner/docker/store.hpp" #include "slave/containerizer/provisioner/docker/token_manager.hpp" #include "tests/mesos.hpp" #include "tests/utils.hpp" +using std::list; using std::map; using std::string; using std::vector; @@ -714,11 +717,12 @@ public: os::read(path::join(layersPath, "456", "rootfs", "temp"))); // Verify the Docker Image provided. - EXPECT_EQ(dockerImage.imageName, "abc"); + EXPECT_EQ(dockerImage.imageName.repository, "abc"); + EXPECT_EQ(dockerImage.imageName.tag, "latest"); list<string> expectedLayers; expectedLayers.push_back("123"); expectedLayers.push_back("456"); - EXPECT_EQ(dockerImage.layers, expectedLayers); + EXPECT_EQ(dockerImage.layerIds, expectedLayers); } protected: @@ -727,7 +731,7 @@ protected: TemporaryDirectoryTest::SetUp(); string imageDir = path::join(os::getcwd(), "images"); - string image = path::join(imageDir, "abc"); + string image = path::join(imageDir, "abc:latest"); ASSERT_SOME(os::mkdir(imageDir)); ASSERT_SOME(os::mkdir(image)); @@ -775,7 +779,7 @@ protected: ASSERT_SOME(os::rmdir(path::join(image, "456", "layer"))); ASSERT_SOME(os::chdir(image)); - ASSERT_SOME(os::tar(".", "../abc.tar")); + ASSERT_SOME(os::tar(".", "../abc:latest.tar")); ASSERT_SOME(os::chdir(cwd)); ASSERT_SOME(os::rmdir(image)); } @@ -787,7 +791,7 @@ protected: TEST_F(DockerProvisionerLocalStoreTest, LocalStoreTestWithTar) { string imageDir = path::join(os::getcwd(), "images"); - string image = path::join(imageDir, "abc"); + string image = path::join(imageDir, "abc:latest"); ASSERT_SOME(os::mkdir(imageDir)); ASSERT_SOME(os::mkdir(image)); @@ -804,7 +808,10 @@ TEST_F(DockerProvisionerLocalStoreTest, LocalStoreTestWithTar) string sandbox = path::join(os::getcwd(), "sandbox"); ASSERT_SOME(os::mkdir(sandbox)); - Future<DockerImage> dockerImage = store.get()->get("abc"); + Try<ImageName> imageName = ImageName::create("abc"); + ASSERT_SOME(imageName); + + Future<DockerImage> dockerImage = store.get()->get(imageName.get()); AWAIT_READY(dockerImage); verifyLocalDockerImage(flags, dockerImage.get()); @@ -812,7 +819,7 @@ TEST_F(DockerProvisionerLocalStoreTest, LocalStoreTestWithTar) // This tests the ability of the reference store to recover the images it has // already stored on disk when it is initialized. -TEST_F(DockerProvisionerLocalStoreTest, ReferenceStoreInitialization) +TEST_F(DockerProvisionerLocalStoreTest, MetadataManagerInitialization) { slave::Flags flags; flags.docker_store = "local"; @@ -827,7 +834,9 @@ TEST_F(DockerProvisionerLocalStoreTest, ReferenceStoreInitialization) string sandbox = path::join(os::getcwd(), "sandbox"); ASSERT_SOME(os::mkdir(sandbox)); - Future<DockerImage> dockerImage = store.get()->get("abc"); + Try<ImageName> imageName = ImageName::create("abc"); + ASSERT_SOME(imageName); + Future<DockerImage> dockerImage = store.get()->get(imageName.get()); AWAIT_READY(dockerImage); // Store is deleted and recreated. Reference Store is initialized upon @@ -836,7 +845,7 @@ TEST_F(DockerProvisionerLocalStoreTest, ReferenceStoreInitialization) store = Store::create(flags, &fetcher); ASSERT_SOME(store); - dockerImage = store.get()->get("abc"); + dockerImage = store.get()->get(imageName.get()); AWAIT_READY(dockerImage); verifyLocalDockerImage(flags, dockerImage.get()); }
