Repository: mesos Updated Branches: refs/heads/master 36fd0111e -> fae5a6020
Abstracted out invoking 'mesos-fetcher'. Manually rebasing and re-editing https://reviews.apache.org/r/21233/, which is supposed to be replaced now by this patch. Original description: "To test the mesos-fetcher (and the setting of the environment) more cleanly I did some refactoring into a 'fetcher' namespace." Also moved fetcher environment tests to fetcher test file. Added two fetcher tests. Review: https://reviews.apache.org/r/27516 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/fae5a602 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/fae5a602 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/fae5a602 Branch: refs/heads/master Commit: fae5a6020ab90414bff3511ee355b634a0253e38 Parents: 36fd011 Author: Bernd Mathiske <[email protected]> Authored: Fri Nov 7 18:50:17 2014 -0800 Committer: Benjamin Hindman <[email protected]> Committed: Fri Nov 7 19:27:18 2014 -0800 ---------------------------------------------------------------------- src/Makefile.am | 2 + src/launcher/fetcher.cpp | 2 +- src/slave/containerizer/containerizer.cpp | 35 -- src/slave/containerizer/containerizer.hpp | 8 - src/slave/containerizer/docker.cpp | 6 +- src/slave/containerizer/fetcher.cpp | 118 +++++++ src/slave/containerizer/fetcher.hpp | 77 ++++ src/slave/containerizer/mesos/containerizer.cpp | 118 ++----- src/slave/containerizer/mesos/containerizer.hpp | 22 +- src/tests/containerizer_tests.cpp | 205 ----------- src/tests/fetcher_tests.cpp | 354 ++++++++++++++++++- 11 files changed, 600 insertions(+), 347 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index 443554a..8ecaa54 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -294,6 +294,7 @@ libmesos_no_3rdparty_la_SOURCES = \ slave/containerizer/docker.cpp \ slave/containerizer/docker.hpp \ slave/containerizer/external_containerizer.cpp \ + slave/containerizer/fetcher.cpp \ slave/containerizer/isolator.cpp \ slave/containerizer/launcher.cpp \ slave/containerizer/mesos/containerizer.cpp \ @@ -443,6 +444,7 @@ libmesos_no_3rdparty_la_SOURCES += \ slave/state.hpp \ slave/status_update_manager.hpp \ slave/containerizer/containerizer.hpp \ + slave/containerizer/fetcher.hpp \ slave/containerizer/external_containerizer.hpp \ slave/containerizer/isolator.hpp \ slave/containerizer/launcher.hpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/launcher/fetcher.cpp ---------------------------------------------------------------------- diff --git a/src/launcher/fetcher.cpp b/src/launcher/fetcher.cpp index bd95928..400fadf 100644 --- a/src/launcher/fetcher.cpp +++ b/src/launcher/fetcher.cpp @@ -309,7 +309,7 @@ int main(int argc, char* argv[]) EXIT(1) << "Failed to chmod " << fetched.get() << ": " << chmod.error(); } } else if (uri.extract()) { - //TODO(idownes): Consider removing the archive once extracted. + // TODO(idownes): Consider removing the archive once extracted. // Try to extract the file if it's recognized as an archive. Try<bool> extracted = extract(fetched.get(), directory); if (extracted.isError()) { http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/slave/containerizer/containerizer.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/containerizer.cpp b/src/slave/containerizer/containerizer.cpp index 0254679..f234835 100644 --- a/src/slave/containerizer/containerizer.cpp +++ b/src/slave/containerizer/containerizer.cpp @@ -285,41 +285,6 @@ map<string, string> executorEnvironment( } -// Helper method to build the environment map used to launch fetcher. -map<string, string> fetcherEnvironment( - const CommandInfo& commandInfo, - const std::string& directory, - const Option<std::string>& user, - const Flags& flags) -{ - // Prepare the environment variables to pass to mesos-fetcher. - string uris = ""; - foreach (const CommandInfo::URI& uri, commandInfo.uris()) { - uris += uri.value() + "+" + - (uri.has_executable() && uri.executable() ? "1" : "0") + - (uri.extract() ? "X" : "N"); - uris += " "; - } - // Remove extra space at the end. - uris = strings::trim(uris); - - map<string, string> environment; - environment["MESOS_EXECUTOR_URIS"] = uris; - environment["MESOS_WORK_DIRECTORY"] = directory; - if (user.isSome()) { - environment["MESOS_USER"] = user.get(); - } - if (!flags.frameworks_home.empty()) { - environment["MESOS_FRAMEWORKS_HOME"] = flags.frameworks_home; - } - if (!flags.hadoop_home.empty()) { - environment["HADOOP_HOME"] = flags.hadoop_home; - } - - return environment; -} - - } // namespace slave { } // namespace internal { } // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/slave/containerizer/containerizer.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/containerizer.hpp b/src/slave/containerizer/containerizer.hpp index 8a66412..02754cd 100644 --- a/src/slave/containerizer/containerizer.hpp +++ b/src/slave/containerizer/containerizer.hpp @@ -134,14 +134,6 @@ std::map<std::string, std::string> executorEnvironment( bool checkpoint, const Duration& recoveryTimeout); - -std::map<std::string, std::string> fetcherEnvironment( - const CommandInfo& commandInfo, - const std::string& directory, - const Option<std::string>& user, - const Flags& flags); - - } // namespace slave { } // namespace internal { } // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/slave/containerizer/docker.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/docker.cpp b/src/slave/containerizer/docker.cpp index a668920..37f422a 100644 --- a/src/slave/containerizer/docker.cpp +++ b/src/slave/containerizer/docker.cpp @@ -46,6 +46,8 @@ #include "slave/containerizer/containerizer.hpp" #include "slave/containerizer/docker.hpp" +#include "slave/containerizer/fetcher.hpp" + #include "slave/containerizer/isolators/cgroups/constants.hpp" @@ -554,7 +556,7 @@ Future<Nothing> DockerContainerizerProcess::fetch( return Failure("Could not fetch URIs: failed to find mesos-fetcher"); } - map<string, string> fetcherEnv = fetcherEnvironment( + map<string, string> environment = fetcher::environment( commandInfo, container->directory, None(), @@ -568,7 +570,7 @@ Future<Nothing> DockerContainerizerProcess::fetch( Subprocess::PIPE(), Subprocess::PATH(path::join(container->directory, "stdout")), Subprocess::PATH(path::join(container->directory, "stderr")), - fetcherEnv); + environment); if (fetcher.isError()) { return Failure("Failed to execute mesos-fetcher: " + fetcher.error()); http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/slave/containerizer/fetcher.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/fetcher.cpp b/src/slave/containerizer/fetcher.cpp new file mode 100644 index 0000000..a04f0a8 --- /dev/null +++ b/src/slave/containerizer/fetcher.cpp @@ -0,0 +1,118 @@ +/** + * 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/slave.hpp" + +#include "slave/containerizer/fetcher.hpp" + +using std::map; +using std::string; + +namespace mesos { +namespace internal { +namespace slave { +namespace fetcher { + +map<string, string> environment( + const CommandInfo& commandInfo, + const string& directory, + const Option<string>& user, + const Flags& flags) +{ + // Prepare the environment variables to pass to mesos-fetcher. + string uris = ""; + foreach (const CommandInfo::URI& uri, commandInfo.uris()) { + uris += uri.value() + "+" + + (uri.has_executable() && uri.executable() ? "1" : "0") + + (uri.extract() ? "X" : "N"); + uris += " "; + } + + // Remove extra space at the end. + uris = strings::trim(uris); + + map<string, string> result; + + result["MESOS_EXECUTOR_URIS"] = uris; + result["MESOS_WORK_DIRECTORY"] = directory; + + if (user.isSome()) { + result["MESOS_USER"] = user.get(); + } + + if (!flags.frameworks_home.empty()) { + result["MESOS_FRAMEWORKS_HOME"] = flags.frameworks_home; + } + + if (!flags.hadoop_home.empty()) { + result["HADOOP_HOME"] = flags.hadoop_home; + } + + return result; +} + + +process::Future<Option<int>> run( + const CommandInfo& commandInfo, + const string& directory, + const Option<string>& user, + const Flags& flags, + const Option<int>& stdout, + const Option<int>& stderr) +{ + // Determine path for mesos-fetcher. + Result<string> realpath = os::realpath( + path::join(flags.launcher_dir, "mesos-fetcher")); + + if (!realpath.isSome()) { + LOG(ERROR) << "Failed to determine the canonical path " + << "for the mesos-fetcher '" + << path::join(flags.launcher_dir, "mesos-fetcher") + << "': " + << (realpath.isError() ? realpath.error() + : "No such file or directory"); + return Failure("Could not fetch URIs: failed to find mesos-fetcher"); + } + + // Now the actual mesos-fetcher command. + string command = realpath.get(); + + LOG(INFO) << "Fetching URIs using command '" << command << "'"; + + Try<Subprocess> fetcher = subprocess( + command, + Subprocess::PIPE(), + stdout.isSome() + ? Subprocess::FD(stdout.get()) + : Subprocess::PIPE(), + stderr.isSome() + ? Subprocess::FD(stderr.get()) + : Subprocess::PIPE(), + environment(commandInfo, directory, user, flags)); + + if (fetcher.isError()) { + return Failure("Failed to execute mesos-fetcher: " + fetcher.error()); + } + + return fetcher.get().status(); +} + +} // namespace fetcher { +} // namespace slave { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/slave/containerizer/fetcher.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/fetcher.hpp b/src/slave/containerizer/fetcher.hpp new file mode 100644 index 0000000..7c57809 --- /dev/null +++ b/src/slave/containerizer/fetcher.hpp @@ -0,0 +1,77 @@ +/** + * 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 __SLAVE_FETCHER_HPP__ +#define __SLAVE_FETCHER_HPP__ + +#include <map> +#include <string> + +#include <process/future.hpp> +#include <process/io.hpp> +#include <process/subprocess.hpp> + +#include <stout/lambda.hpp> +#include <stout/option.hpp> +#include <stout/os.hpp> +#include <stout/path.hpp> +#include <stout/result.hpp> +#include <stout/strings.hpp> +#include <stout/try.hpp> + +#include <mesos/mesos.hpp> + +#include "slave/flags.hpp" + +namespace mesos { +namespace internal { +namespace slave { +namespace fetcher { + +// Defines helpers for running the mesos-fetcher. +// TODO(benh): Consider moving this into a 'fetcher' subdirectory as +// well as the actual mesos-fetcher program (in launcher/fetcher.cpp +// as of the writing of this comment). + +// Helper method to build the environment used to run mesos-fetcher. +std::map<std::string, std::string> environment( + const CommandInfo& commandInfo, + const std::string& directory, + const Option<std::string>& user, + const Flags& flags); + +// Run the mesos-fetcher for the specified arguments. Note that if +// 'stdout' and 'stderr' file descriptors are provided then respective +// output from the mesos-fetcher will be redirected to the file +// descriptors. The file descriptors are duplicated (via dup) because +// redirecting might still be occuring even after the mesos-fetcher has +// terminated since there still might be data to be read. +process::Future<Option<int>> run( + const CommandInfo& commandInfo, + const std::string& directory, + const Option<std::string>& user, + const Flags& flags, + const Option<int>& stdout = None(), + const Option<int>& stderr = None()); + +} // namespace fetcher { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __SLAVE_FETCHER_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/slave/containerizer/mesos/containerizer.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/mesos/containerizer.cpp b/src/slave/containerizer/mesos/containerizer.cpp index 4bd2665..562b03b 100644 --- a/src/slave/containerizer/mesos/containerizer.cpp +++ b/src/slave/containerizer/mesos/containerizer.cpp @@ -31,6 +31,7 @@ #include "slave/slave.hpp" #include "slave/containerizer/containerizer.hpp" +#include "slave/containerizer/fetcher.hpp" #include "slave/containerizer/isolator.hpp" #include "slave/containerizer/launcher.hpp" #ifdef __linux__ @@ -115,7 +116,7 @@ Try<MesosContainerizer*> MesosContainerizer::create( creators["network/port_mapping"] = &PortMappingIsolatorProcess::create; #endif - vector<Owned<Isolator> > isolators; + vector<Owned<Isolator>> isolators; foreach (const string& type, strings::split(isolation, ",")) { if (creators.contains(type)) { @@ -171,7 +172,7 @@ MesosContainerizer::MesosContainerizer( const Flags& flags, bool local, const Owned<Launcher>& launcher, - const vector<Owned<Isolator> >& isolators) + const vector<Owned<Isolator>>& isolators) { process = new MesosContainerizerProcess( flags, local, launcher, isolators); @@ -269,7 +270,7 @@ void MesosContainerizer::destroy(const ContainerID& containerId) } -Future<hashset<ContainerID> > MesosContainerizer::containers() +Future<hashset<ContainerID>> MesosContainerizer::containers() { return dispatch(process, &MesosContainerizerProcess::containers); } @@ -339,7 +340,7 @@ Future<Nothing> MesosContainerizerProcess::_recover( const list<RunState>& recoverable) { // Then recover the isolators. - list<Future<Nothing> > futures; + list<Future<Nothing>> futures; foreach (const Owned<Isolator>& isolator, isolators) { futures.push_back(isolator->recover(recoverable)); } @@ -357,12 +358,12 @@ Future<Nothing> MesosContainerizerProcess::__recover( CHECK_SOME(run.id); const ContainerID& containerId = run.id.get(); - Owned<Promise<containerizer::Termination> > promise( + Owned<Promise<containerizer::Termination>> promise( new Promise<containerizer::Termination>()); promises.put(containerId, promise); CHECK_SOME(run.forkedPid); - Future<Option<int > > status = process::reap(run.forkedPid.get()); + Future<Option<int>> status = process::reap(run.forkedPid.get()); statuses[containerId] = status; status.onAny(defer(self(), &Self::reaped, containerId)); @@ -414,7 +415,7 @@ Future<bool> MesosContainerizerProcess::launch( return false; } - Owned<Promise<containerizer::Termination> > promise( + Owned<Promise<containerizer::Termination>> promise( new Promise<containerizer::Termination>()); promises.put(containerId, promise); @@ -527,7 +528,8 @@ Future<Nothing> _fetch( if (status.isNone() || (status.get() != 0)) { return Failure("Failed to fetch URIs for container '" + stringify(containerId) + "': exit status " + - (status.isNone() ? "none" : stringify(status.get()))); + (status.isNone() ? + "none" : stringify(status.get()))); } // Chown the work directory if a user is provided. @@ -548,55 +550,23 @@ Future<Nothing> MesosContainerizerProcess::fetch( const string& directory, const Option<string>& user) { - // Determine path for mesos-fetcher. - Result<string> realpath = os::realpath( - path::join(flags.launcher_dir, "mesos-fetcher")); - - if (!realpath.isSome()) { - LOG(ERROR) << "Failed to determine the canonical path " - << "for the mesos-fetcher '" - << path::join(flags.launcher_dir, "mesos-fetcher") - << "': " - << (realpath.isError() ? realpath.error() - : "No such file or directory"); - return Failure("Could not fetch URIs: failed to find mesos-fetcher"); - } - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, user, flags); - - // Now the actual mesos-fetcher command. - string command = realpath.get(); - - LOG(INFO) << "Fetching URIs for container '" << containerId - << "' using command '" << command << "'"; - - Try<Subprocess> fetcher = subprocess( - command, - Subprocess::PIPE(), - Subprocess::PIPE(), - Subprocess::PIPE(), - environment); - - if (fetcher.isError()) { - return Failure("Failed to execute mesos-fetcher: " + fetcher.error()); - } + // Before we fetch let's make sure we create 'stdout' and 'stderr' + // files into which we can redirect the output of the mesos-fetcher + // (and later redirect the child's stdout/stderr). - // Redirect output (stdout and stderr) from the fetcher to log files - // in the executor work directory, chown'ing them if a user is - // specified. // TODO(tillt): Consider adding O_CLOEXEC for atomic close-on-exec. - // TODO(tillt): Consider adding an overload to io::redirect - // that accepts a file path as 'to' for further reducing code. We - // would however also need an owner user parameter for such overload - // to perfectly replace the below. + // TODO(tillt): Considering updating fetcher::run to take paths + // instead of file descriptors and then use Subprocess::PATH() + // instead of Subprocess::FD(). The reason this can't easily be done + // today is because we not only need to open the files but also + // chown them. Try<int> out = os::open( path::join(directory, "stdout"), O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, S_IRUSR | S_IWUSR | S_IRGRP | S_IRWXO); if (out.isError()) { - return Failure("Failed to redirect stdout: " + out.error()); + return Failure("Failed to create 'stdout' file: " + out.error()); } if (user.isSome()) { @@ -604,20 +574,10 @@ Future<Nothing> MesosContainerizerProcess::fetch( user.get(), path::join(directory, "stdout")); if (chown.isError()) { os::close(out.get()); - return Failure( - "Failed to redirect stdout: Failed to chown: " + - chown.error()); + return Failure("Failed to chown 'stdout' file: " + chown.error()); } } - // Redirect takes care of nonblocking and close-on-exec for the - // supplied file descriptors. - io::redirect(fetcher.get().out().get(), out.get()); - - // Redirect does 'dup' the file descriptor, hence we can close the - // original now. - os::close(out.get()); - // Repeat for stderr. Try<int> err = os::open( path::join(directory, "stderr"), @@ -625,9 +585,7 @@ Future<Nothing> MesosContainerizerProcess::fetch( S_IRUSR | S_IWUSR | S_IRGRP | S_IRWXO); if (err.isError()) { - return Failure( - "Failed to redirect stderr: Failed to open: " + - err.error()); + return Failure("Failed to create 'stderr' file: " + err.error()); } if (user.isSome()) { @@ -635,17 +593,19 @@ Future<Nothing> MesosContainerizerProcess::fetch( user.get(), path::join(directory, "stderr")); if (chown.isError()) { os::close(err.get()); - return Failure( - "Failed to redirect stderr: Failed to chown: " + - chown.error()); + return Failure("Failed to chown 'stderr' file: " + chown.error()); } } - io::redirect(fetcher.get().err().get(), err.get()); - - os::close(err.get()); - - return fetcher.get().status() + return fetcher::run( + commandInfo, + directory, + user, + flags, + out.get(), + err.get()) + .onAny(lambda::bind(&os::close, out.get())) + .onAny(lambda::bind(&os::close, err.get())) .then(lambda::bind(&_fetch, containerId, directory, user, lambda::_1)); } @@ -658,7 +618,7 @@ Future<bool> MesosContainerizerProcess::_launch( const SlaveID& slaveId, const PID<Slave>& slavePid, bool checkpoint, - const list<Option<CommandInfo> >& commands) + const list<Option<CommandInfo>>& commands) { // Prepare environment variables for the executor. map<string, string> env = executorEnvironment( @@ -752,7 +712,7 @@ Future<bool> MesosContainerizerProcess::_launch( // Monitor the executor's pid. We keep the future because we'll // refer to it again during container destroy. - Future<Option<int> > status = process::reap(pid); + Future<Option<int>> status = process::reap(pid); statuses.put(containerId, status); status.onAny(defer(self(), &Self::reaped, containerId)); @@ -789,7 +749,7 @@ Future<bool> MesosContainerizerProcess::isolate( // NOTE: This is done is parallel and is not sequenced like prepare // or destroy because we assume there are no dependencies in // isolation. - list<Future<Nothing> > futures; + list<Future<Nothing>> futures; foreach (const Owned<Isolator>& isolator, isolators) { futures.push_back(isolator->isolate(containerId, _pid)); } @@ -857,7 +817,7 @@ Future<Nothing> MesosContainerizerProcess::update( resources.put(containerId, _resources); // Update each isolator. - list<Future<Nothing> > futures; + list<Future<Nothing>> futures; foreach (const Owned<Isolator>& isolator, isolators) { futures.push_back(isolator->update(containerId, _resources)); } @@ -874,7 +834,7 @@ Future<Nothing> MesosContainerizerProcess::update( Future<ResourceStatistics> _usage( const ContainerID& containerId, const Option<Resources>& resources, - const list<Future<ResourceStatistics> >& statistics) + const list<Future<ResourceStatistics>>& statistics) { ResourceStatistics result; @@ -916,7 +876,7 @@ Future<ResourceStatistics> MesosContainerizerProcess::usage( return Failure("Unknown container: " + stringify(containerId)); } - list<Future<ResourceStatistics> > futures; + list<Future<ResourceStatistics>> futures; foreach (const Owned<Isolator>& isolator, isolators) { futures.push_back(isolator->usage(containerId)); } @@ -1023,7 +983,7 @@ static T reversed(const T& t) void MesosContainerizerProcess::__destroy( const ContainerID& containerId, - const Future<Option<int > >& status) + const Future<Option<int>>& status) { // We clean up each isolator in the reverse order they were // prepared (see comment in prepare()). @@ -1147,7 +1107,7 @@ void MesosContainerizerProcess::limited( } -Future<hashset<ContainerID> > MesosContainerizerProcess::containers() +Future<hashset<ContainerID>> MesosContainerizerProcess::containers() { return promises.keys(); } http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/slave/containerizer/mesos/containerizer.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/mesos/containerizer.hpp b/src/slave/containerizer/mesos/containerizer.hpp index ab3bb6f..3baea31 100644 --- a/src/slave/containerizer/mesos/containerizer.hpp +++ b/src/slave/containerizer/mesos/containerizer.hpp @@ -45,7 +45,7 @@ public: const Flags& flags, bool local, const process::Owned<Launcher>& launcher, - const std::vector<process::Owned<Isolator> >& isolators); + const std::vector<process::Owned<Isolator>>& isolators); virtual ~MesosContainerizer(); @@ -83,7 +83,7 @@ public: virtual void destroy(const ContainerID& containerId); - virtual process::Future<hashset<ContainerID> > containers(); + virtual process::Future<hashset<ContainerID>> containers(); private: MesosContainerizerProcess* process; @@ -98,7 +98,7 @@ public: const Flags& _flags, bool _local, const process::Owned<Launcher>& _launcher, - const std::vector<process::Owned<Isolator> >& _isolators) + const std::vector<process::Owned<Isolator>>& _isolators) : flags(_flags), local(_local), launcher(_launcher), @@ -140,7 +140,7 @@ public: void destroy(const ContainerID& containerId); - process::Future<hashset<ContainerID> > containers(); + process::Future<hashset<ContainerID>> containers(); private: process::Future<Nothing> _recover( @@ -149,7 +149,7 @@ private: process::Future<Nothing> __recover( const std::list<state::RunState>& recovered); - process::Future<std::list<Option<CommandInfo> > > prepare( + process::Future<std::list<Option<CommandInfo>>> prepare( const ContainerID& containerId, const ExecutorInfo& executorInfo, const std::string& directory, @@ -169,7 +169,7 @@ private: const SlaveID& slaveId, const process::PID<Slave>& slavePid, bool checkpoint, - const std::list<Option<CommandInfo> >& scripts); + const std::list<Option<CommandInfo>>& scripts); process::Future<bool> isolate( const ContainerID& containerId, @@ -187,13 +187,13 @@ private: // Continues '_destroy()' once we get the exit status of the executor. void __destroy( const ContainerID& containerId, - const process::Future<Option<int > >& status); + const process::Future<Option<int>>& status); // Continues (and completes) '__destroy()' once all isolators have completed // cleanup. void ___destroy( const ContainerID& containerId, - const process::Future<Option<int> >& status, + const process::Future<Option<int>>& status, const process::Future<std::list<process::Future<Nothing>>>& cleanups); // Call back for when an isolator limits a container and impacts the @@ -209,17 +209,17 @@ private: const Flags flags; const bool local; const process::Owned<Launcher> launcher; - const std::vector<process::Owned<Isolator> > isolators; + const std::vector<process::Owned<Isolator>> isolators; // TODO(idownes): Consider putting these per-container variables into a // struct. // Promises for futures returned from wait(). hashmap<ContainerID, - process::Owned<process::Promise<containerizer::Termination> > > promises; + process::Owned<process::Promise<containerizer::Termination>>> promises; // We need to keep track of the future exit status for each executor because // we'll only get a single notification when the executor exits. - hashmap<ContainerID, process::Future<Option<int> > > statuses; + hashmap<ContainerID, process::Future<Option<int>>> statuses; // We keep track of any limitations received from each isolator so we can // determine the cause of an executor termination. http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/tests/containerizer_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/containerizer_tests.cpp b/src/tests/containerizer_tests.cpp index 2c90d2f..abe8074 100644 --- a/src/tests/containerizer_tests.cpp +++ b/src/tests/containerizer_tests.cpp @@ -47,211 +47,6 @@ using std::map; using std::string; using std::vector; -namespace mesos { -namespace internal { -namespace slave { - -// Forward declaration. -map<string, string> fetcherEnvironment( - const CommandInfo& commandInfo, - const string& directory, - const Option<string>& user, - const Flags& flags); - -} // namespace slave { -} // namespace internal { -} // namespace mesos { - -class MesosContainerizerProcessTest : public ::testing::Test {}; - - -TEST_F(MesosContainerizerProcessTest, Simple) -{ - CommandInfo commandInfo; - CommandInfo::URI uri; - uri.set_value("hdfs:///uri"); - uri.set_executable(false); - commandInfo.add_uris()->MergeFrom(uri); - - string directory = "/tmp/directory"; - Option<string> user = "user"; - - Flags flags; - flags.frameworks_home = "/tmp/frameworks"; - flags.hadoop_home = "/tmp/hadoop"; - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, user, flags); - EXPECT_EQ(5u, environment.size()); - EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); - EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); - EXPECT_EQ(user.get(), environment["MESOS_USER"]); - EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); - EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); -} - - -TEST_F(MesosContainerizerProcessTest, MultipleURIs) -{ - CommandInfo commandInfo; - CommandInfo::URI uri; - uri.set_value("hdfs:///uri1"); - uri.set_executable(false); - commandInfo.add_uris()->MergeFrom(uri); - uri.set_value("hdfs:///uri2"); - uri.set_executable(true); - commandInfo.add_uris()->MergeFrom(uri); - - string directory = "/tmp/directory"; - Option<string> user("user"); - - Flags flags; - flags.frameworks_home = "/tmp/frameworks"; - flags.hadoop_home = "/tmp/hadoop"; - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, user, flags); - - EXPECT_EQ(5u, environment.size()); - EXPECT_EQ( - "hdfs:///uri1+0X hdfs:///uri2+1X", environment["MESOS_EXECUTOR_URIS"]); - EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); - EXPECT_EQ(user.get(), environment["MESOS_USER"]); - EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); - EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); -} - - -TEST_F(MesosContainerizerProcessTest, NoUser) -{ - CommandInfo commandInfo; - CommandInfo::URI uri; - uri.set_value("hdfs:///uri"); - uri.set_executable(false); - commandInfo.add_uris()->MergeFrom(uri); - - string directory = "/tmp/directory"; - - Flags flags; - flags.frameworks_home = "/tmp/frameworks"; - flags.hadoop_home = "/tmp/hadoop"; - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, None(), flags); - - EXPECT_EQ(4u, environment.size()); - EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); - EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); - EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); - EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); -} - - -TEST_F(MesosContainerizerProcessTest, EmptyHadoop) -{ - CommandInfo commandInfo; - CommandInfo::URI uri; - uri.set_value("hdfs:///uri"); - uri.set_executable(false); - commandInfo.add_uris()->MergeFrom(uri); - - string directory = "/tmp/directory"; - Option<string> user = "user"; - - Flags flags; - flags.frameworks_home = "/tmp/frameworks"; - flags.hadoop_home = ""; - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, user, flags); - - EXPECT_EQ(4u, environment.size()); - EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); - EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); - EXPECT_EQ(user.get(), environment["MESOS_USER"]); - EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); -} - - -TEST_F(MesosContainerizerProcessTest, NoHadoop) -{ - CommandInfo commandInfo; - CommandInfo::URI uri; - uri.set_value("hdfs:///uri"); - uri.set_executable(false); - commandInfo.add_uris()->MergeFrom(uri); - - string directory = "/tmp/directory"; - Option<string> user = "user"; - - Flags flags; - flags.frameworks_home = "/tmp/frameworks"; - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, user, flags); - - EXPECT_EQ(4u, environment.size()); - EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); - EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); - EXPECT_EQ(user.get(), environment["MESOS_USER"]); - EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); -} - - -TEST_F(MesosContainerizerProcessTest, NoExtract) -{ - CommandInfo commandInfo; - CommandInfo::URI uri; - uri.set_value("hdfs:///uri"); - uri.set_executable(false); - uri.set_extract(false); - commandInfo.add_uris()->MergeFrom(uri); - - string directory = "/tmp/directory"; - Option<string> user = "user"; - - Flags flags; - flags.frameworks_home = "/tmp/frameworks"; - flags.hadoop_home = "/tmp/hadoop"; - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, user, flags); - EXPECT_EQ(5u, environment.size()); - EXPECT_EQ("hdfs:///uri+0N", environment["MESOS_EXECUTOR_URIS"]); - EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); - EXPECT_EQ(user.get(), environment["MESOS_USER"]); - EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); - EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); -} - - -TEST_F(MesosContainerizerProcessTest, NoExtractExecutable) -{ - CommandInfo commandInfo; - CommandInfo::URI uri; - uri.set_value("hdfs:///uri"); - uri.set_executable(true); - uri.set_extract(false); - commandInfo.add_uris()->MergeFrom(uri); - - string directory = "/tmp/directory"; - Option<string> user = "user"; - - Flags flags; - flags.frameworks_home = "/tmp/frameworks"; - flags.hadoop_home = "/tmp/hadoop"; - - map<string, string> environment = - fetcherEnvironment(commandInfo, directory, user, flags); - EXPECT_EQ(5u, environment.size()); - EXPECT_EQ("hdfs:///uri+1N", environment["MESOS_EXECUTOR_URIS"]); - EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); - EXPECT_EQ(user.get(), environment["MESOS_USER"]); - EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); - EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); -} - - class MesosContainerizerIsolatorPreparationTest : public tests::TemporaryDirectoryTest { http://git-wip-us.apache.org/repos/asf/mesos/blob/fae5a602/src/tests/fetcher_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/fetcher_tests.cpp b/src/tests/fetcher_tests.cpp index e026e87..a01eec1 100644 --- a/src/tests/fetcher_tests.cpp +++ b/src/tests/fetcher_tests.cpp @@ -18,8 +18,7 @@ #include <unistd.h> -#include <gmock/gmock.h> - +#include <map> #include <string> #include <process/future.hpp> @@ -35,12 +34,16 @@ #include <stout/strings.hpp> #include <stout/try.hpp> +#include "slave/containerizer/fetcher.hpp" +#include "slave/flags.hpp" + #include "tests/environment.hpp" #include "tests/flags.hpp" #include "tests/utils.hpp" using namespace mesos; using namespace mesos::internal; +using namespace mesos::internal::slave; using namespace mesos::internal::tests; using namespace process; @@ -50,6 +53,198 @@ using process::Future; using std::string; using std::map; + +class FetcherEnvironmentTest : public ::testing::Test {}; + + +TEST_F(FetcherEnvironmentTest, Simple) +{ + CommandInfo commandInfo; + CommandInfo::URI uri; + uri.set_value("hdfs:///uri"); + uri.set_executable(false); + commandInfo.add_uris()->MergeFrom(uri); + + string directory = "/tmp/directory"; + Option<string> user = "user"; + + slave::Flags flags; + flags.frameworks_home = "/tmp/frameworks"; + flags.hadoop_home = "/tmp/hadoop"; + + map<string, string> environment = + fetcher::environment(commandInfo, directory, user, flags); + + EXPECT_EQ(5u, environment.size()); + EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); + EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); + EXPECT_EQ(user.get(), environment["MESOS_USER"]); + EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); + EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); +} + + +TEST_F(FetcherEnvironmentTest, MultipleURIs) +{ + CommandInfo commandInfo; + CommandInfo::URI uri; + uri.set_value("hdfs:///uri1"); + uri.set_executable(false); + commandInfo.add_uris()->MergeFrom(uri); + uri.set_value("hdfs:///uri2"); + uri.set_executable(true); + commandInfo.add_uris()->MergeFrom(uri); + + string directory = "/tmp/directory"; + Option<string> user("user"); + + slave::Flags flags; + flags.frameworks_home = "/tmp/frameworks"; + flags.hadoop_home = "/tmp/hadoop"; + + map<string, string> environment = + fetcher::environment(commandInfo, directory, user, flags); + + EXPECT_EQ(5u, environment.size()); + EXPECT_EQ( + "hdfs:///uri1+0X hdfs:///uri2+1X", environment["MESOS_EXECUTOR_URIS"]); + EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); + EXPECT_EQ(user.get(), environment["MESOS_USER"]); + EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); + EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); +} + + +TEST_F(FetcherEnvironmentTest, NoUser) +{ + CommandInfo commandInfo; + CommandInfo::URI uri; + uri.set_value("hdfs:///uri"); + uri.set_executable(false); + commandInfo.add_uris()->MergeFrom(uri); + + string directory = "/tmp/directory"; + + slave::Flags flags; + flags.frameworks_home = "/tmp/frameworks"; + flags.hadoop_home = "/tmp/hadoop"; + + map<string, string> environment = + fetcher::environment(commandInfo, directory, None(), flags); + + EXPECT_EQ(4u, environment.size()); + EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); + EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); + EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); + EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); +} + + +TEST_F(FetcherEnvironmentTest, EmptyHadoop) +{ + CommandInfo commandInfo; + CommandInfo::URI uri; + uri.set_value("hdfs:///uri"); + uri.set_executable(false); + commandInfo.add_uris()->MergeFrom(uri); + + string directory = "/tmp/directory"; + Option<string> user = "user"; + + slave::Flags flags; + flags.frameworks_home = "/tmp/frameworks"; + flags.hadoop_home = ""; + + map<string, string> environment = + fetcher::environment(commandInfo, directory, user, flags); + + EXPECT_EQ(4u, environment.size()); + EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); + EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); + EXPECT_EQ(user.get(), environment["MESOS_USER"]); + EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); +} + + +TEST_F(FetcherEnvironmentTest, NoHadoop) +{ + CommandInfo commandInfo; + CommandInfo::URI uri; + uri.set_value("hdfs:///uri"); + uri.set_executable(false); + commandInfo.add_uris()->MergeFrom(uri); + + string directory = "/tmp/directory"; + Option<string> user = "user"; + + slave::Flags flags; + flags.frameworks_home = "/tmp/frameworks"; + + map<string, string> environment = + fetcher::environment(commandInfo, directory, user, flags); + + EXPECT_EQ(4u, environment.size()); + EXPECT_EQ("hdfs:///uri+0X", environment["MESOS_EXECUTOR_URIS"]); + EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); + EXPECT_EQ(user.get(), environment["MESOS_USER"]); + EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); +} + + +TEST_F(FetcherEnvironmentTest, NoExtractNoExecutable) +{ + CommandInfo commandInfo; + CommandInfo::URI uri; + uri.set_value("hdfs:///uri"); + uri.set_executable(false); + uri.set_extract(false); + commandInfo.add_uris()->MergeFrom(uri); + + string directory = "/tmp/directory"; + Option<string> user = "user"; + + slave::Flags flags; + flags.frameworks_home = "/tmp/frameworks"; + flags.hadoop_home = "/tmp/hadoop"; + + map<string, string> environment = + fetcher::environment(commandInfo, directory, user, flags); + EXPECT_EQ(5u, environment.size()); + EXPECT_EQ("hdfs:///uri+0N", environment["MESOS_EXECUTOR_URIS"]); + EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); + EXPECT_EQ(user.get(), environment["MESOS_USER"]); + EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); + EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); +} + + +TEST_F(FetcherEnvironmentTest, NoExtractExecutable) +{ + CommandInfo commandInfo; + CommandInfo::URI uri; + uri.set_value("hdfs:///uri"); + uri.set_executable(true); + uri.set_extract(false); + commandInfo.add_uris()->MergeFrom(uri); + + string directory = "/tmp/directory"; + Option<string> user = "user"; + + slave::Flags flags; + flags.frameworks_home = "/tmp/frameworks"; + flags.hadoop_home = "/tmp/hadoop"; + + map<string, string> environment = + fetcher::environment(commandInfo, directory, user, flags); + EXPECT_EQ(5u, environment.size()); + EXPECT_EQ("hdfs:///uri+1N", environment["MESOS_EXECUTOR_URIS"]); + EXPECT_EQ(directory, environment["MESOS_WORK_DIRECTORY"]); + EXPECT_EQ(user.get(), environment["MESOS_USER"]); + EXPECT_EQ(flags.frameworks_home, environment["MESOS_FRAMEWORKS_HOME"]); + EXPECT_EQ(flags.hadoop_home, environment["HADOOP_HOME"]); +} + + class FetcherTest : public TemporaryDirectoryTest {}; @@ -74,7 +269,7 @@ TEST_F(FetcherTest, FileURI) env); ASSERT_SOME(fetcherProcess); - Future<Option<int> > status = fetcherProcess.get().status(); + Future<Option<int>> status = fetcherProcess.get().status(); AWAIT_READY(status); ASSERT_SOME(status.get()); @@ -105,7 +300,7 @@ TEST_F(FetcherTest, FilePath) env); ASSERT_SOME(fetcherProcess); - Future<Option<int> > status = fetcherProcess.get().status(); + Future<Option<int>> status = fetcherProcess.get().status(); AWAIT_READY(status); ASSERT_SOME(status.get()); @@ -153,7 +348,7 @@ TEST_F(FetcherTest, OSNetUriTest) env); ASSERT_SOME(fetcherProcess); - Future<Option<int> > status = fetcherProcess.get().status(); + Future<Option<int>> status = fetcherProcess.get().status(); AWAIT_READY(status); ASSERT_SOME(status.get()); @@ -184,7 +379,7 @@ TEST_F(FetcherTest, FileLocalhostURI) env); ASSERT_SOME(fetcherProcess); - Future<Option<int> > status = fetcherProcess.get().status(); + Future<Option<int>> status = fetcherProcess.get().status(); AWAIT_READY(status); ASSERT_SOME(status.get()); @@ -192,3 +387,150 @@ TEST_F(FetcherTest, FileLocalhostURI) EXPECT_EQ(0, status.get().get()); EXPECT_TRUE(os::exists(localFile)); } + + +TEST_F(FetcherTest, NoExtractNotExecutable) +{ + // First construct a temporary file that can be fetched. + Try<string> path = os::mktemp(); + + ASSERT_SOME(path); + + CommandInfo commandInfo; + CommandInfo::URI* uri = commandInfo.add_uris(); + uri->set_value(path.get()); + uri->set_executable(false); + uri->set_extract(false); + + Option<int> stdout = None(); + Option<int> stderr = None(); + + // Redirect mesos-fetcher output if running the tests verbosely. + if (tests::flags.verbose) { + stdout = STDOUT_FILENO; + stderr = STDERR_FILENO; + } + + slave::Flags flags; + flags.launcher_dir = path::join(tests::flags.build_dir, "src"); + + Future<Option<int>> run = + fetcher::run(commandInfo, os::getcwd(), None(), flags, stdout, stderr); + + AWAIT_READY(run); + EXPECT_SOME_EQ(0, run.get()); + + Try<string> basename = os::basename(path.get()); + + ASSERT_SOME(basename); + + Try<os::Permissions> permissions = os::permissions(basename.get()); + + ASSERT_SOME(permissions); + EXPECT_FALSE(permissions.get().owner.x); + EXPECT_FALSE(permissions.get().group.x); + EXPECT_FALSE(permissions.get().others.x); + + ASSERT_SOME(os::rm(path.get())); +} + + +TEST_F(FetcherTest, NoExtractExecutable) +{ + // First construct a temporary file that can be fetched. + Try<string> path = os::mktemp(); + + ASSERT_SOME(path); + + CommandInfo commandInfo; + CommandInfo::URI* uri = commandInfo.add_uris(); + uri->set_value(path.get()); + uri->set_executable(true); + uri->set_extract(false); + + Option<int> stdout = None(); + Option<int> stderr = None(); + + // Redirect mesos-fetcher output if running the tests verbosely. + if (tests::flags.verbose) { + stdout = STDOUT_FILENO; + stderr = STDERR_FILENO; + } + + slave::Flags flags; + flags.launcher_dir = path::join(tests::flags.build_dir, "src"); + + Future<Option<int>> run = + fetcher::run(commandInfo, os::getcwd(), None(), flags, stdout, stderr); + + AWAIT_READY(run); + EXPECT_SOME_EQ(0, run.get()); + + Try<string> basename = os::basename(path.get()); + + ASSERT_SOME(basename); + + Try<os::Permissions> permissions = os::permissions(basename.get()); + + ASSERT_SOME(permissions); + EXPECT_TRUE(permissions.get().owner.x); + EXPECT_TRUE(permissions.get().group.x); + EXPECT_TRUE(permissions.get().others.x); + + ASSERT_SOME(os::rm(path.get())); +} + + +TEST_F(FetcherTest, ExtractNotExecutable) +{ + // First construct a temporary file that can be fetched and archive + // with tar gzip. + Try<string> path = os::mktemp(); + + ASSERT_SOME(path); + + ASSERT_SOME(os::write(path.get(), "hello world")); + + // TODO(benh): Update os::tar so that we can capture or ignore + // stdout/stderr output. + + ASSERT_SOME(os::tar(path.get(), path.get() + ".tar.gz")); + + CommandInfo commandInfo; + CommandInfo::URI* uri = commandInfo.add_uris(); + uri->set_value(path.get() + ".tar.gz"); + uri->set_executable(false); + uri->set_extract(true); + + Option<int> stdout = None(); + Option<int> stderr = None(); + + // Redirect mesos-fetcher output if running the tests verbosely. + if (tests::flags.verbose) { + stdout = STDOUT_FILENO; + stderr = STDERR_FILENO; + } + + slave::Flags flags; + flags.launcher_dir = path::join(tests::flags.build_dir, "src"); + + Future<Option<int>> run = + fetcher::run(commandInfo, os::getcwd(), None(), flags, stdout, stderr); + + AWAIT_READY(run); + EXPECT_SOME_EQ(0, run.get()); + + ASSERT_TRUE(os::exists(path::join(".", path.get()))); + + ASSERT_SOME_EQ("hello world", os::read(path::join(".", path.get()))); + + Try<os::Permissions> permissions = + os::permissions(path::join(".", path.get())); + + ASSERT_SOME(permissions); + EXPECT_FALSE(permissions.get().owner.x); + EXPECT_FALSE(permissions.get().group.x); + EXPECT_FALSE(permissions.get().others.x); + + ASSERT_SOME(os::rm(path.get())); +}
