Repository: mesos Updated Branches: refs/heads/master fb631516c -> 313591aa6
Implemented AppcProvisioner. Review: https://reviews.apache.org/r/37881 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/313591aa Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/313591aa Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/313591aa Branch: refs/heads/master Commit: 313591aa677b8781b40ea7bfed0deed9c84deba7 Parents: d45ccc5 Author: Jiang Yan Xu <[email protected]> Authored: Sat Aug 29 21:19:53 2015 -0700 Committer: Jiang Yan Xu <[email protected]> Committed: Thu Sep 3 17:29:16 2015 -0700 ---------------------------------------------------------------------- src/Makefile.am | 2 + src/slave/containerizer/provisioner.cpp | 46 ++- src/slave/containerizer/provisioner.hpp | 2 + src/slave/containerizer/provisioners/appc.cpp | 378 +++++++++++++++++++ src/slave/containerizer/provisioners/appc.hpp | 78 ++++ .../containerizer/provisioners/appc/paths.cpp | 161 +++++++- .../containerizer/provisioners/appc/paths.hpp | 19 +- src/slave/flags.cpp | 6 + src/slave/flags.hpp | 2 + .../containerizer/appc_provisioner_tests.cpp | 112 ++++++ 10 files changed, 790 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index 4b643a3..5fdca0f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -482,6 +482,7 @@ libmesos_no_3rdparty_la_SOURCES = \ slave/containerizer/mesos/containerizer.cpp \ slave/containerizer/mesos/launch.cpp \ slave/containerizer/provisioner.cpp \ + slave/containerizer/provisioners/appc.cpp \ slave/containerizer/provisioners/appc/paths.cpp \ slave/containerizer/provisioners/appc/spec.cpp \ slave/containerizer/provisioners/appc/store.cpp \ @@ -760,6 +761,7 @@ libmesos_no_3rdparty_la_SOURCES += \ slave/containerizer/launcher.hpp \ slave/containerizer/linux_launcher.hpp \ slave/containerizer/provisioner.hpp \ + slave/containerizer/provisioners/appc.hpp \ slave/containerizer/provisioners/appc/paths.hpp \ slave/containerizer/provisioners/appc/spec.hpp \ slave/containerizer/provisioners/appc/store.hpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/containerizer/provisioner.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner.cpp b/src/slave/containerizer/provisioner.cpp index efc7e69..95894c0 100644 --- a/src/slave/containerizer/provisioner.cpp +++ b/src/slave/containerizer/provisioner.cpp @@ -16,10 +16,18 @@ * limitations under the License. */ +#include <stout/hashset.hpp> +#include <stout/stringify.hpp> +#include <stout/strings.hpp> + #include "slave/containerizer/provisioner.hpp" +#include "slave/containerizer/provisioners/appc.hpp" + using namespace process; +using std::string; + namespace mesos { namespace internal { namespace slave { @@ -28,8 +36,42 @@ Try<hashmap<Image::Type, Owned<Provisioner>>> Provisioner::create( const Flags& flags, Fetcher* fetcher) { - // TODO(tnachen): Load provisioners when one of them is available. - return hashmap<Image::Type, Owned<Provisioner>>(); + if (flags.provisioners.isNone()) { + return hashmap<Image::Type, Owned<Provisioner>>(); + } + + hashmap<Image::Type, + Try<Owned<Provisioner>>(*)(const Flags&, Fetcher*)> creators; + + // Register all supported creators. + creators.put(Image::APPC, &appc::AppcProvisioner::create); + + hashmap<Image::Type, Owned<Provisioner>> provisioners; + + // NOTE: Change in '--provisioners' flag may result in leaked rootfs + // files on the disk but it's at least safe because files managed by + // different provisioners are totally separated. + foreach (const string& type, + strings::tokenize(flags.provisioners.get(), ",")) { + Image::Type imageType; + if (!Image::Type_Parse(strings::upper(type), &imageType)) { + return Error("Unknown provisioner '" + type + "'"); + } + + if (!creators.contains(imageType)) { + return Error("Unsupported provisioner '" + type + "'"); + } + + Try<Owned<Provisioner>> provisioner = creators[imageType](flags, fetcher); + if (provisioner.isError()) { + return Error("Failed to create '" + stringify(imageType) + + "' provisioner: " + provisioner.error()); + } + + provisioners[imageType] = provisioner.get(); + } + + return provisioners; } } // namespace slave { http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/containerizer/provisioner.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioner.hpp b/src/slave/containerizer/provisioner.hpp index 541dd4e..9e0e0b8 100644 --- a/src/slave/containerizer/provisioner.hpp +++ b/src/slave/containerizer/provisioner.hpp @@ -45,6 +45,8 @@ class Provisioner public: virtual ~Provisioner() {} + // Create provisioners based on specified flags. An error is returned if + // any of the provisioners specified in --provisioner failed to be created. static Try<hashmap<Image::Type, process::Owned<Provisioner>>> create(const Flags& flags, Fetcher* fetcher); http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/containerizer/provisioners/appc.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/appc.cpp b/src/slave/containerizer/provisioners/appc.cpp new file mode 100644 index 0000000..fc5ee19 --- /dev/null +++ b/src/slave/containerizer/provisioners/appc.cpp @@ -0,0 +1,378 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <mesos/type_utils.hpp> + +#include <process/collect.hpp> +#include <process/defer.hpp> +#include <process/dispatch.hpp> +#include <process/process.hpp> + +#include <stout/foreach.hpp> +#include <stout/hashset.hpp> +#include <stout/os.hpp> +#include <stout/stringify.hpp> +#include <stout/strings.hpp> +#include <stout/uuid.hpp> + +#include "slave/containerizer/provisioners/appc.hpp" + +#include "slave/containerizer/provisioners/backend.hpp" + +#include "slave/containerizer/provisioners/appc/paths.hpp" +#include "slave/containerizer/provisioners/appc/spec.hpp" +#include "slave/containerizer/provisioners/appc/store.hpp" + +#include "slave/paths.hpp" + +using namespace process; + +using std::list; +using std::string; +using std::vector; + +using mesos::slave::ContainerState; + +namespace mesos { +namespace internal { +namespace slave { +namespace appc { + +class AppcProvisionerProcess : public Process<AppcProvisionerProcess> +{ +public: + AppcProvisionerProcess( + const Flags& flags, + const string& root, + const Owned<Store>& store, + const hashmap<string, Owned<Backend>>& backends); + + Future<Nothing> recover( + const list<ContainerState>& states, + const hashset<ContainerID>& orphans); + + Future<string> provision(const ContainerID& containerId, const Image& image); + + Future<bool> destroy(const ContainerID& containerId); + +private: + Future<string> _provision(const vector<string>& layers, const string& rootfs); + + const Flags flags; + + // Absolute path to the Appc provisioner root directory. It can be derived + // from '--work_dir' but we keep a separate copy here because we converted + // it into an absolute path so managed rootfs paths match the ones in + // 'mountinfo' (important if mount-based backends are used). + const string root; + + const Owned<Store> store; + const hashmap<string, Owned<Backend>> backends; + + struct Info + { + // Mappings: backend -> rootfsId -> rootfsPath. + hashmap<string, hashmap<string, string>> rootfses; + }; + + hashmap<ContainerID, Owned<Info>> infos; +}; + + +// NOTE: Successful creation of the provisioner means its managed +// directory under --work_dir is also created. +Try<Owned<Provisioner>> AppcProvisioner::create( + const Flags& flags, + Fetcher* fetcher) +{ + string _root = + slave::paths::getProvisionerDir(flags.work_dir, Image::APPC); + + Try<Nothing> mkdir = os::mkdir(_root); + if (mkdir.isError()) { + return Error("Failed to create provisioner root directory '" + + _root + "': " + mkdir.error()); + } + + Result<string> root = os::realpath(_root); + if (root.isError()) { + return Error( + "Failed to resolve the realpath of provisioner root directory '" + + _root + "': " + root.error()); + } + + CHECK_SOME(root); // Can't be None since we just created it. + + Try<Owned<Store>> store = Store::create(flags); + if (store.isError()) { + return Error("Failed to create image store: " + store.error()); + } + + hashmap<string, Owned<Backend>> backends = Backend::create(flags); + if (backends.empty()) { + return Error("No usable provisioner backend created"); + } + + if (!backends.contains(flags.appc_backend)) { + return Error("The specified provisioner backend '" + flags.appc_backend + + "'is unsupported"); + } + + return Owned<Provisioner>(new AppcProvisioner( + Owned<AppcProvisionerProcess>(new AppcProvisionerProcess( + flags, + root.get(), + store.get(), + backends)))); +} + + +AppcProvisioner::AppcProvisioner(Owned<AppcProvisionerProcess> _process) + : process(_process) +{ + spawn(CHECK_NOTNULL(process.get())); +} + + +AppcProvisioner::~AppcProvisioner() +{ + terminate(process.get()); + wait(process.get()); +} + + +Future<Nothing> AppcProvisioner::recover( + const list<ContainerState>& states, + const hashset<ContainerID>& orphans) +{ + return dispatch( + process.get(), + &AppcProvisionerProcess::recover, + states, + orphans); +} + + +Future<string> AppcProvisioner::provision( + const ContainerID& containerId, + const Image& image) +{ + return dispatch( + process.get(), + &AppcProvisionerProcess::provision, + containerId, + image); +} + + +Future<bool> AppcProvisioner::destroy(const ContainerID& containerId) +{ + return dispatch( + process.get(), + &AppcProvisionerProcess::destroy, + containerId); +} + + +AppcProvisionerProcess::AppcProvisionerProcess( + const Flags& _flags, + const string& _root, + const Owned<Store>& _store, + const hashmap<string, Owned<Backend>>& _backends) + : flags(_flags), + root(_root), + store(_store), + backends(_backends) {} + + +Future<Nothing> AppcProvisionerProcess::recover( + const list<ContainerState>& states, + const hashset<ContainerID>& orphans) +{ + // Register living containers, including the ones that do not + // provision Appc images. + hashset<ContainerID> alive; + + foreach (const ContainerState& state, states) { + 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 = + paths::listContainers(root); + + if (containers.isError()) { + return Failure("Failed to list the containers managed by Appc " + "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 = + paths::listContainerRootfses(root, containerId); + + if (rootfses.isError()) { + return Failure("Unable to list rootfses belonged to container '" + + containerId.value() + "': " + rootfses.error()); + } + + foreachkey (const string& backend, rootfses.get()) { + if (!backends.contains(backend)) { + return Failure("Found rootfses managed by an unrecognized backend: " + + backend); + } + + info->rootfses.put(backend, rootfses.get()[backend]); + } + + VLOG(1) << "Recovered container " << containerId; + infos.put(containerId, info); + + continue; + } + + // Destroy (unknown) orphan container's rootfses. + Try<hashmap<string, hashmap<string, string>>> rootfses = + paths::listContainerRootfses(root, 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 Appc provisioner rootfses"; + + return store->recover() + .then([]() -> Future<Nothing> { + LOG(INFO) << "Recovered Appc image store"; + return Nothing(); + }); +} + + +Future<string> AppcProvisionerProcess::provision( + const ContainerID& containerId, + const Image& image) +{ + if (image.type() != Image::APPC) { + return Failure("Unsupported container image type: " + + stringify(image.type())); + } + + if (!image.has_appc()) { + return Failure("Missing Appc image info"); + } + + string rootfsId = UUID::random().toString(); + string rootfs = paths::getContainerRootfsDir( + root, containerId, flags.appc_backend, rootfsId); + + if (!infos.contains(containerId)) { + infos.put(containerId, Owned<Info>(new Info())); + } + + infos[containerId]->rootfses[flags.appc_backend].put(rootfsId, rootfs); + + // Get and then provision image layers from the store. + return store->get(image.appc()) + .then(defer(self(), &Self::_provision, lambda::_1, rootfs)); +} + + +Future<string> AppcProvisionerProcess::_provision( + const vector<string>& layers, + const string& rootfs) +{ + LOG(INFO) << "Provisioning image layers to rootfs '" << rootfs << "'"; + + CHECK(backends.contains(flags.appc_backend)); + return backends.get(flags.appc_backend).get()->provision(layers, rootfs) + .then([rootfs]() -> Future<string> { return rootfs; }); +} + + +Future<bool> AppcProvisionerProcess::destroy(const ContainerID& containerId) +{ + if (!infos.contains(containerId)) { + LOG(INFO) << "Ignoring destroy request for unknown container: " + << containerId; + + return false; + } + + // Unregister the container first. If destroy() fails, we can rely on + // recover() to retry it later. + Owned<Info> info = infos[containerId]; + infos.erase(containerId); + + list<Future<bool>> futures; + foreachkey (const string& backend, info->rootfses) { + foreachvalue (const string& rootfs, info->rootfses[backend]) { + if (!backends.contains(backend)) { + return Failure("Cannot destroy rootfs '" + rootfs + + "' provisioned by an unknown backend '" + backend + "'"); + } + + LOG(INFO) << "Destroying container rootfs for container '" + << containerId << "' at '" << rootfs << "'"; + + futures.push_back( + backends.get(backend).get()->destroy(rootfs)); + } + } + + // TODO(xujyan): Revisit the usefulness of this return value. + return collect(futures) + .then([=](const list<bool>& results) -> Future<bool> { + return true; + }); +} + +} // namespace appc { +} // namespace slave { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/containerizer/provisioners/appc.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/appc.hpp b/src/slave/containerizer/provisioners/appc.hpp new file mode 100644 index 0000000..68e82e3 --- /dev/null +++ b/src/slave/containerizer/provisioners/appc.hpp @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MESOS_APPC_HPP__ +#define __MESOS_APPC_HPP__ + +#include <list> +#include <string> +#include <vector> + +#include <process/future.hpp> +#include <process/owned.hpp> + +#include <stout/hashmap.hpp> +#include <stout/json.hpp> +#include <stout/nothing.hpp> +#include <stout/try.hpp> + +#include "slave/containerizer/provisioner.hpp" + +namespace mesos { +namespace internal { +namespace slave { +namespace appc { + +// Forward declaration. +class AppcProvisionerProcess; + + +class AppcProvisioner : public Provisioner +{ +public: + static Try<process::Owned<Provisioner>> create( + const Flags& flags, + Fetcher* fetcher); + + ~AppcProvisioner(); + + virtual process::Future<Nothing> recover( + const std::list<mesos::slave::ContainerState>& states, + const hashset<ContainerID>& orphans); + + virtual process::Future<std::string> provision( + const ContainerID& containerId, + const Image& image); + + virtual process::Future<bool> destroy(const ContainerID& containerId); + +private: + explicit AppcProvisioner(process::Owned<AppcProvisionerProcess> process); + + AppcProvisioner(const AppcProvisioner&); // Not copyable. + AppcProvisioner& operator=(const AppcProvisioner&); // Not assignable. + + process::Owned<AppcProvisionerProcess> process; +}; + +} // namespace appc { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __MESOS_APPC_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/containerizer/provisioners/appc/paths.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/appc/paths.cpp b/src/slave/containerizer/provisioners/appc/paths.cpp index 271116d..e6be851 100644 --- a/src/slave/containerizer/provisioners/appc/paths.cpp +++ b/src/slave/containerizer/provisioners/appc/paths.cpp @@ -16,11 +16,21 @@ * limitations under the License. */ +#include <list> + +#include <glog/logging.h> + +#include <mesos/type_utils.hpp> + +#include <stout/os.hpp> #include <stout/path.hpp> +#include <stout/os/stat.hpp> + #include "slave/containerizer/provisioners/appc/paths.hpp" #include "slave/paths.hpp" +using std::list; using std::string; namespace mesos { @@ -55,7 +65,7 @@ string getImageRootfsPath( } -string getImageRootfsPath(const std::string& imagePath) +string getImageRootfsPath(const string& imagePath) { return path::join(imagePath, "rootfs"); } @@ -69,29 +79,158 @@ string getImageManifestPath( } -string getImageManifestPath(const std::string& imagePath) +string getImageManifestPath(const string& imagePath) { return path::join(imagePath, "manifest"); } +// Internal helpers for traversing the directory hierarchy. +static string getContainersDir(const string& provisionerDir) +{ + return path::join(provisionerDir, "containers"); +} + + +static string getContainerDir( + const string& containersDir, + const ContainerID& containerId) +{ + return path::join(containersDir, containerId.value()); +} + + +static string getBackendsDir(const string& containerDir) +{ + return path::join(containerDir, "backends"); +} + + +static string getBackendDir(const string& backendsDir, const string& backend) +{ + return path::join(backendsDir, backend); +} + + +static string getRootfsesDir(const string& backendDir) +{ + return path::join(backendDir, "rootfses"); +} + + +static string getRootfsDir(const string& rootfsesDir, const string& roofsId) +{ + return path::join(rootfsesDir, roofsId); +} + + string getContainerRootfsDir( - const string& rootDir, - const Image::Type& imageType, + const string& provisionerDir, const ContainerID& containerId, const string& backend, const string& rootfsId) { - return path::join( - slave::paths::getProvisionerDir(rootDir, imageType), - "containers", - containerId.value(), - "backends", - backend, - "rootfses", + return getRootfsDir( + getRootfsesDir( + getBackendDir( + getBackendsDir( + getContainerDir( + getContainersDir(provisionerDir), + containerId)), + backend)), rootfsId); } + +Try<hashmap<ContainerID, string>> listContainers( + const string& provisionerDir) +{ + hashmap<ContainerID, string> results; + + string containersDir = getContainersDir(provisionerDir); + if (!os::exists(containersDir)) { + // No container has been created yet. + return results; + } + + Try<list<string>> containerIds = os::ls(containersDir); + if (containerIds.isError()) { + return Error("Unable to list the containers directory: " + + containerIds.error()); + } + + foreach (const string& entry, containerIds.get()) { + string containerPath = path::join(containersDir, entry); + + if (!os::stat::isdir(containerPath)) { + LOG(WARNING) << "Ignoring unexpected container entry at: " + << containerPath; + continue; + } + + ContainerID containerId; + containerId.set_value(entry); + results.put(containerId, containerPath); + } + + return results; +} + + +Try<hashmap<string, hashmap<string, string>>> listContainerRootfses( + const string& provisionerDir, + const ContainerID& containerId) +{ + hashmap<string, hashmap<string, string>> results; + + string backendsDir = getBackendsDir( + getContainerDir( + getContainersDir(provisionerDir), + containerId)); + + Try<list<string>> backends = os::ls(backendsDir); + if (backends.isError()) { + return Error("Unable to list the container directory: " + backends.error()); + } + + foreach (const string& backend, backends.get()) { + string backendDir = getBackendDir(backendsDir, backend); + if (!os::stat::isdir(backendDir)) { + LOG(WARNING) << "Ignoring unexpected backend entry at: " << backendDir; + continue; + } + + Try<list<string>> rootfses = os::ls(getRootfsesDir(backendDir)); + if (rootfses.isError()) { + return Error("Unable to list the backend directory: " + rootfses.error()); + } + + hashmap<string, string> backendResults; + + foreach (const string& rootfsId, rootfses.get()) { + string rootfs = getRootfsDir(getRootfsesDir(backendDir), rootfsId); + + if (!os::stat::isdir(rootfs)) { + LOG(WARNING) << "Ignoring unexpected rootfs entry at: " << backendDir; + continue; + } + + backendResults.put(rootfsId, rootfs); + } + + if (backendResults.empty()) { + LOG(WARNING) << "Ignoring a backend directory with no rootfs in it: " + << backendDir; + continue; + } + + // The rootfs directory has passed validation. + results.put(backend, backendResults); + } + + return results; +} + } // namespace paths { } // namespace appc { } // namespace slave { http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/containerizer/provisioners/appc/paths.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/appc/paths.hpp b/src/slave/containerizer/provisioners/appc/paths.hpp index 6364737..54bab1f 100644 --- a/src/slave/containerizer/provisioners/appc/paths.hpp +++ b/src/slave/containerizer/provisioners/appc/paths.hpp @@ -19,11 +19,13 @@ #ifndef __MESOS_APPC_PATHS__ #define __MESOS_APPC_PATHS__ -#include <list> #include <string> #include <mesos/mesos.hpp> +#include <stout/hashmap.hpp> +#include <stout/try.hpp> + namespace mesos { namespace internal { namespace slave { @@ -92,12 +94,23 @@ std::string getImageManifestPath(const std::string& imagePath); std::string getContainerRootfsDir( - const std::string& rootDir, - const Image::Type& imageType, + const std::string& provisionerDir, const ContainerID& containerId, const std::string& backend, const std::string& rootfsId); + +// Recursively "ls" the container directory and return a map of +// backend -> rootfsId -> rootfsPath. +Try<hashmap<std::string, hashmap<std::string, std::string>>> +listContainerRootfses( + const std::string& provisionerDir, + const ContainerID& containerId); + +// Return a map of containerId -> containerPath; +Try<hashmap<ContainerID, std::string>> listContainers( + const std::string& provisionerDir); + } // namespace paths { } // namespace appc { } // namespace slave { http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/flags.cpp ---------------------------------------------------------------------- diff --git a/src/slave/flags.cpp b/src/slave/flags.cpp index b36710d..49fb000 100644 --- a/src/slave/flags.cpp +++ b/src/slave/flags.cpp @@ -69,6 +69,12 @@ mesos::internal::slave::Flags::Flags() "Directory the appc provisioner will store images in", "/tmp/mesos/store/appc"); + // TODO(xujyan): Change the default to 'copy' once it's added. + add(&Flags::appc_backend, + "appc_backend", + "Strategy for provisioning container rootfs from appc images", + "bind"); + add(&Flags::default_role, "default_role", "Any resources in the --resources flag that\n" http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/slave/flags.hpp ---------------------------------------------------------------------- diff --git a/src/slave/flags.hpp b/src/slave/flags.hpp index e56738e..b8335aa 100644 --- a/src/slave/flags.hpp +++ b/src/slave/flags.hpp @@ -48,7 +48,9 @@ public: std::string isolation; Option<std::string> provisioners; + std::string appc_store_dir; + std::string appc_backend; std::string default_role; Option<std::string> attributes; http://git-wip-us.apache.org/repos/asf/mesos/blob/313591aa/src/tests/containerizer/appc_provisioner_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/containerizer/appc_provisioner_tests.cpp b/src/tests/containerizer/appc_provisioner_tests.cpp index a4526e9..f30e1a1 100644 --- a/src/tests/containerizer/appc_provisioner_tests.cpp +++ b/src/tests/containerizer/appc_provisioner_tests.cpp @@ -26,11 +26,14 @@ #include <stout/path.hpp> #include <stout/stringify.hpp> +#include "slave/containerizer/provisioner.hpp" + #include "slave/containerizer/provisioners/appc/spec.hpp" #include "slave/containerizer/provisioners/appc/store.hpp" #include "tests/utils.hpp" +using std::list; using std::string; using std::vector; @@ -38,6 +41,9 @@ using namespace process; using namespace mesos::internal::slave::appc; +using mesos::internal::slave::Fetcher; +using mesos::internal::slave::Provisioner; + namespace mesos { namespace internal { namespace tests { @@ -181,6 +187,112 @@ TEST_F(AppcProvisionerTest, StoreRecover) EXPECT_EQ(os::realpath(imagePath).get(), layers.get().front()); } + +#ifdef __linux__ +// This test verifies that the provisioner can provision an rootfs from an +// image that is already put into the store directory. +TEST_F(AppcProvisionerTest, ROOT_Provision) +{ + // Create provisioner. + slave::Flags flags; + flags.appc_store_dir = path::join(os::getcwd(), "store"); + flags.appc_backend = "bind"; + flags.provisioners = "appc"; + flags.work_dir = "work_dir"; + + Fetcher fetcher; + Try<hashmap<Image::Type, Owned<Provisioner>>> provisioners = + Provisioner::create(flags, &fetcher); + ASSERT_SOME(provisioners); + ASSERT_TRUE(provisioners.get().contains(Image::APPC)); + + // Create a simple image in the store: + // <store> + // |--images + // |--<id> + // |--manifest + // |--rootfs/tmp/test + JSON::Value manifest = JSON::parse( + "{" + " \"acKind\": \"ImageManifest\"," + " \"acVersion\": \"0.6.1\"," + " \"name\": \"foo.com/bar\"," + " \"labels\": [" + " {" + " \"name\": \"version\"," + " \"value\": \"1.0.0\"" + " }," + " {" + " \"name\": \"arch\"," + " \"value\": \"amd64\"" + " }," + " {" + " \"name\": \"os\"," + " \"value\": \"linux\"" + " }" + " ]," + " \"annotations\": [" + " {" + " \"name\": \"created\"," + " \"value\": \"1438983392\"" + " }" + " ]" + "}").get(); + + // The 'imageId' below has the correct format but it's not computed by + // hashing the tarball of the image. It's OK here as we assume + // the images under 'images' have passed such check when they are + // downloaded and validated. + string imageId = + "sha512-e77d96aa0240eedf134b8c90baeaf76dca8e78691836301d7498c84020446042e" + "797b296d6ab296e0954c2626bfb264322ebeb8f447dac4fac6511ea06bc61f0"; + + string imagePath = path::join(flags.appc_store_dir, "images", imageId); + + ASSERT_SOME(os::mkdir(path::join(imagePath, "rootfs", "tmp"))); + ASSERT_SOME( + os::write(path::join(imagePath, "rootfs", "tmp", "test"), "test")); + ASSERT_SOME( + os::write(path::join(imagePath, "manifest"), stringify(manifest))); + + // Recover. This is when the image in the store is loaded. + AWAIT_READY(provisioners.get()[Image::APPC]->recover({}, {})); + + // Simulate a task that requires an image. + Image image; + image.mutable_appc()->set_name("foo.com/bar"); + + ContainerID containerId; + containerId.set_value("12345"); + + Future<string> rootfs = + provisioners.get()[Image::APPC]->provision(containerId, image); + AWAIT_READY(rootfs); + + Try<list<string>> rootfses = os::ls(path::join( + flags.work_dir, + "provisioners", + stringify(Image::APPC), + "containers", + containerId.value(), + "backends", + flags.appc_backend, + "rootfses")); + + ASSERT_SOME(rootfses); + + // Verify that the rootfs is successfully provisioned. + EXPECT_EQ(1u, rootfses.get().size()); + EXPECT_EQ(rootfses.get().front(), Path(rootfs.get()).basename()); + + Future<bool> destroy = provisioners.get()[Image::APPC]->destroy(containerId); + AWAIT_READY(destroy); + + // One rootfs is destroyed. + EXPECT_TRUE(destroy.get()); +} +#endif // __linux__ + } // namespace tests { } // namespace internal { } // namespace mesos {
