Future<int> CheckerProcess::commandCheck( const check::Command& cmd, const runtime::Plain& plain) { const CommandInfo& command = cmd.info; map<string, string> environment = os::environment(); foreach (const Environment::Variable& variable, command.environment().variables()) { environment[variable.name()] = variable.value(); } // Launch the subprocess. Try<Subprocess> s = Error("Not launched"); if (command.shell()) { // Use the shell variant. VLOG(1) << "Launching " << name << " '" << command.value() << "'" << " for task '" << taskId << "'"; s = process::subprocess( command.value(), Subprocess::PATH(os::DEV_NULL), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), environment, getCustomCloneFunc(plain)); } else { // Use the exec variant. vector<string> argv( std::begin(command.arguments()), std::end(command.arguments())); VLOG(1) << "Launching " << name << " [" << command.value() << ", " << strings::join(", ", argv) << "] for task '" << taskId << "'"; s = process::subprocess( command.value(), argv, Subprocess::PATH(os::DEV_NULL), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), nullptr, environment, getCustomCloneFunc(plain)); } if (s.isError()) { return Failure("Failed to create subprocess: " + s.error()); } // TODO(alexr): Use lambda named captures for // these cached values once it is available. const pid_t commandPid = s->pid(); const string _name = name; const Duration timeout = checkTimeout; const TaskID _taskId = taskId; return s->status() .after( timeout, [timeout, commandPid, _name, _taskId](Future<Option<int>> future) { future.discard(); if (commandPid != -1) { // Cleanup the external command process. VLOG(1) << "Killing the " << _name << " process '" << commandPid << "' for task '" << _taskId << "'"; os::killtree(commandPid, SIGKILL); } return Failure("Command timed out after " + stringify(timeout)); }) .then([](const Option<int>& exitCode) -> Future<int> { if (exitCode.isNone()) { return Failure("Failed to reap the command process"); } return exitCode.get(); }); }
Future<Nothing> HealthCheckerProcess::_tcpHealthCheck() { CHECK_EQ(HealthCheck::TCP, check.type()); CHECK(check.has_tcp()); // TCP_CHECK_COMMAND should be reachable. CHECK(os::exists(launcherDir)); const HealthCheck::TCPCheckInfo& tcp = check.tcp(); VLOG(1) << "Launching TCP health check at port '" << tcp.port() << "'"; const string tcpConnectPath = path::join(launcherDir, TCP_CHECK_COMMAND); const vector<string> tcpConnectArguments = { tcpConnectPath, "--ip=" + DEFAULT_DOMAIN, "--port=" + stringify(tcp.port()) }; Try<Subprocess> s = subprocess( tcpConnectPath, tcpConnectArguments, Subprocess::PATH("/dev/null"), Subprocess::PIPE(), Subprocess::PIPE(), nullptr, None(), clone); if (s.isError()) { return Failure( "Failed to create the " + string(TCP_CHECK_COMMAND) + " subprocess: " + s.error()); } pid_t tcpConnectPid = s->pid(); Duration timeout = Seconds(static_cast<int64_t>(check.timeout_seconds())); return await( s->status(), process::io::read(s->out().get()), process::io::read(s->err().get())) .after(timeout, [timeout, tcpConnectPid](Future<tuple<Future<Option<int>>, Future<string>, Future<string>>> future) { future.discard(); if (tcpConnectPid != -1) { // Cleanup the TCP_CHECK_COMMAND process. VLOG(1) << "Killing the TCP health check process " << tcpConnectPid; os::killtree(tcpConnectPid, SIGKILL); } return Failure( string(TCP_CHECK_COMMAND) + " has not returned after " + stringify(timeout) + "; aborting"); }) .then(defer(self(), &Self::__tcpHealthCheck, lambda::_1)); }
Future<Nothing> HealthCheckerProcess::_commandHealthCheck() { CHECK_EQ(HealthCheck::COMMAND, check.type()); CHECK(check.has_command()); const CommandInfo& command = check.command(); map<string, string> environment = os::environment(); foreach (const Environment::Variable& variable, command.environment().variables()) { environment[variable.name()] = variable.value(); } // Launch the subprocess. Try<Subprocess> external = Error("Not launched"); if (command.shell()) { // Use the shell variant. VLOG(1) << "Launching command health check '" << command.value() << "'"; external = subprocess( command.value(), Subprocess::PATH("/dev/null"), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), environment, clone); } else { // Use the exec variant. vector<string> argv; foreach (const string& arg, command.arguments()) { argv.push_back(arg); } VLOG(1) << "Launching command health check [" << command.value() << ", " << strings::join(", ", argv) << "]"; external = subprocess( command.value(), argv, Subprocess::PATH("/dev/null"), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), nullptr, environment, clone); } if (external.isError()) { return Failure("Failed to create subprocess: " + external.error()); } pid_t commandPid = external->pid(); Duration timeout = Seconds(static_cast<int64_t>(check.timeout_seconds())); return external->status() .after(timeout, [timeout, commandPid](Future<Option<int>> future) { future.discard(); if (commandPid != -1) { // Cleanup the external command process. VLOG(1) << "Killing the command health check process " << commandPid; os::killtree(commandPid, SIGKILL); } return Failure( "Command has not returned after " + stringify(timeout) + "; aborting"); }) .then([](const Option<int>& status) -> Future<Nothing> { if (status.isNone()) { return Failure("Failed to reap the command process"); } int statusCode = status.get(); if (statusCode != 0) { return Failure("Command returned " + WSTRINGIFY(statusCode)); } return Nothing(); }); }
Future<Nothing> HealthCheckerProcess::_httpHealthCheck() { CHECK_EQ(HealthCheck::HTTP, check.type()); CHECK(check.has_http()); const HealthCheck::HTTPCheckInfo& http = check.http(); const string scheme = http.has_scheme() ? http.scheme() : DEFAULT_HTTP_SCHEME; const string path = http.has_path() ? http.path() : ""; const string url = scheme + "://" + DEFAULT_DOMAIN + ":" + stringify(http.port()) + path; VLOG(1) << "Launching HTTP health check '" << url << "'"; const vector<string> argv = { HTTP_CHECK_COMMAND, "-s", // Don't show progress meter or error messages. "-S", // Makes curl show an error message if it fails. "-L", // Follows HTTP 3xx redirects. "-k", // Ignores SSL validation when scheme is https. "-w", "%{http_code}", // Displays HTTP response code on stdout. "-o", "/dev/null", // Ignores output. url }; Try<Subprocess> s = subprocess( HTTP_CHECK_COMMAND, argv, Subprocess::PATH("/dev/null"), Subprocess::PIPE(), Subprocess::PIPE(), nullptr, None(), clone); if (s.isError()) { return Failure( "Failed to create the " + string(HTTP_CHECK_COMMAND) + " subprocess: " + s.error()); } pid_t curlPid = s->pid(); Duration timeout = Seconds(static_cast<int64_t>(check.timeout_seconds())); return await( s->status(), process::io::read(s->out().get()), process::io::read(s->err().get())) .after(timeout, [timeout, curlPid](Future<tuple<Future<Option<int>>, Future<string>, Future<string>>> future) { future.discard(); if (curlPid != -1) { // Cleanup the HTTP_CHECK_COMMAND process. VLOG(1) << "Killing the HTTP health check process " << curlPid; os::killtree(curlPid, SIGKILL); } return Failure( string(HTTP_CHECK_COMMAND) + " has not returned after " + stringify(timeout) + "; aborting"); }) .then(defer(self(), &Self::__httpHealthCheck, lambda::_1)); }
pid_t launchTaskPosix( const CommandInfo& command, const string& launcherDir, const Environment& environment, const Option<string>& user, const Option<string>& rootfs, const Option<string>& sandboxDirectory, const Option<string>& workingDirectory, const Option<CapabilityInfo>& capabilities) { // Prepare the flags to pass to the launch process. MesosContainerizerLaunch::Flags launchFlags; ContainerLaunchInfo launchInfo; launchInfo.mutable_command()->CopyFrom(command); if (rootfs.isSome()) { // The command executor is responsible for chrooting into the // root filesystem and changing the user before exec-ing the // user process. #ifdef __linux__ if (geteuid() != 0) { ABORT("The command executor requires root with rootfs"); } // Ensure that mount namespace of the executor is not affected by // changes in its task's namespace induced by calling `pivot_root` // as part of the task setup in mesos-containerizer binary. launchFlags.unshare_namespace_mnt = true; #else ABORT("Not expecting root volume with non-linux platform"); #endif // __linux__ launchInfo.set_rootfs(rootfs.get()); CHECK_SOME(sandboxDirectory); launchInfo.set_working_directory(workingDirectory.isSome() ? workingDirectory.get() : sandboxDirectory.get()); // TODO(jieyu): If the task has a rootfs, the executor itself will // be running as root. Its sandbox is owned by root as well. In // order for the task to be able to access to its sandbox, we need // to make sure the owner of the sandbox is 'user'. However, this // is still a workaround. The owner of the files downloaded by the // fetcher is still not correct (i.e., root). if (user.isSome()) { // NOTE: We only chown the sandbox directory (non-recursively). Try<Nothing> chown = os::chown(user.get(), os::getcwd(), false); if (chown.isError()) { ABORT("Failed to chown sandbox to user " + user.get() + ": " + chown.error()); } } } launchInfo.mutable_environment()->CopyFrom(environment); if (user.isSome()) { launchInfo.set_user(user.get()); } if (capabilities.isSome()) { launchInfo.mutable_capabilities()->CopyFrom(capabilities.get()); } launchFlags.launch_info = JSON::protobuf(launchInfo); string commandString = strings::format( "%s %s %s", path::join(launcherDir, MESOS_CONTAINERIZER), MesosContainerizerLaunch::NAME, stringify(launchFlags)).get(); // Fork the child using launcher. vector<string> argv(2); argv[0] = MESOS_CONTAINERIZER; argv[1] = MesosContainerizerLaunch::NAME; Try<Subprocess> s = subprocess( path::join(launcherDir, MESOS_CONTAINERIZER), argv, Subprocess::FD(STDIN_FILENO), Subprocess::FD(STDOUT_FILENO), Subprocess::FD(STDERR_FILENO), &launchFlags, None(), None(), {}, {Subprocess::ChildHook::SETSID()}); if (s.isError()) { ABORT("Failed to launch '" + commandString + "': " + s.error()); } cout << commandString << endl; return s->pid(); }