blob: 2c90d2fc18a3268c55b6dfe98699bfb36d093983 [file] [log] [blame]
/**
* 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 <map>
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <process/future.hpp>
#include <process/owned.hpp>
#include <mesos/mesos.hpp>
#include "slave/flags.hpp"
#include "slave/containerizer/isolator.hpp"
#include "slave/containerizer/launcher.hpp"
#include "slave/containerizer/mesos/containerizer.hpp"
#include "tests/flags.hpp"
#include "tests/isolator.hpp"
#include "tests/mesos.hpp"
#include "tests/utils.hpp"
using namespace mesos;
using namespace mesos::internal;
using namespace mesos::internal::slave;
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
{
public:
// Construct a MesosContainerizer with TestIsolator(s) which use the provided
// 'prepare' command(s).
Try<MesosContainerizer*> CreateContainerizer(
const vector<Option<CommandInfo> >& prepares)
{
vector<Owned<Isolator> > isolators;
foreach (const Option<CommandInfo>& prepare, prepares) {
Try<Isolator*> isolator = tests::TestIsolatorProcess::create(prepare);
if (isolator.isError()) {
return Error(isolator.error());
}
isolators.push_back(Owned<Isolator>(isolator.get()));
}
slave::Flags flags;
flags.launcher_dir = path::join(tests::flags.build_dir, "src");
Try<Launcher*> launcher = PosixLauncher::create(flags);
if (launcher.isError()) {
return Error(launcher.error());
}
return new MesosContainerizer(
flags,
false,
Owned<Launcher>(launcher.get()),
isolators);
}
Try<MesosContainerizer*> CreateContainerizer(
const Option<CommandInfo>& prepare)
{
vector<Option<CommandInfo> > prepares;
prepares.push_back(prepare);
return CreateContainerizer(prepares);
}
};
// The isolator has a prepare command that succeeds.
TEST_F(MesosContainerizerIsolatorPreparationTest, ScriptSucceeds)
{
string directory = os::getcwd(); // We're inside a temporary sandbox.
string file = path::join(directory, "child.script.executed");
Try<MesosContainerizer*> containerizer = CreateContainerizer(
CREATE_COMMAND_INFO("touch " + file));
CHECK_SOME(containerizer);
ContainerID containerId;
containerId.set_value("test_container");
process::Future<bool> launch = containerizer.get()->launch(
containerId,
CREATE_EXECUTOR_INFO("executor", "exit 0"),
directory,
None(),
SlaveID(),
process::PID<Slave>(),
false);
// Wait until the launch completes.
AWAIT_READY(launch);
// Wait for the child (preparation script + executor) to complete.
process::Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the child exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
// Check the preparation script actually ran.
EXPECT_TRUE(os::exists(file));
// Destroy the container.
containerizer.get()->destroy(containerId);
delete containerizer.get();
}
// The isolator has a prepare command that fails.
TEST_F(MesosContainerizerIsolatorPreparationTest, ScriptFails)
{
string directory = os::getcwd(); // We're inside a temporary sandbox.
string file = path::join(directory, "child.script.executed");
Try<MesosContainerizer*> containerizer = CreateContainerizer(
CREATE_COMMAND_INFO("touch " + file + " && exit 1"));
CHECK_SOME(containerizer);
ContainerID containerId;
containerId.set_value("test_container");
Future<bool> launch = containerizer.get()->launch(
containerId,
CREATE_EXECUTOR_INFO("executor", "exit 0"),
directory,
None(),
SlaveID(),
process::PID<Slave>(),
false);
// Wait until the launch completes.
AWAIT_READY(launch);
// Wait for the child (preparation script + executor) to complete.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the child failed to exit correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_NE(0, wait.get().status());
// Check the preparation script actually ran.
EXPECT_TRUE(os::exists(file));
// Destroy the container.
containerizer.get()->destroy(containerId);
delete containerizer.get();
}
// There are two isolators, one with a prepare command that succeeds and
// another that fails. The execution order is not defined but the launch should
// fail from the failing prepare command.
TEST_F(MesosContainerizerIsolatorPreparationTest, MultipleScripts)
{
string directory = os::getcwd(); // We're inside a temporary sandbox.
string file1 = path::join(directory, "child.script.executed.1");
string file2 = path::join(directory, "child.script.executed.2");
vector<Option<CommandInfo> > prepares;
// This isolator prepare command one will succeed if called first, otherwise
// it won't get run.
prepares.push_back(CREATE_COMMAND_INFO("touch " + file1 + " && exit 0"));
// This will fail, either first or after the successful command.
prepares.push_back(CREATE_COMMAND_INFO("touch " + file2 + " && exit 1"));
Try<MesosContainerizer*> containerizer = CreateContainerizer(prepares);
CHECK_SOME(containerizer);
ContainerID containerId;
containerId.set_value("test_container");
Future<bool> launch = containerizer.get()->launch(
containerId,
CREATE_EXECUTOR_INFO("executor", "exit 0"),
directory,
None(),
SlaveID(),
process::PID<Slave>(),
false);
// Wait until the launch completes.
AWAIT_READY(launch);
// Wait for the child (preparation script(s) + executor) to complete.
Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the child failed to exit correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_NE(0, wait.get().status());
// Check the failing preparation script has actually ran.
EXPECT_TRUE(os::exists(file2));
// Destroy the container.
containerizer.get()->destroy(containerId);
delete containerizer.get();
}
class MesosContainerizerExecuteTest : public tests::TemporaryDirectoryTest {};
TEST_F(MesosContainerizerExecuteTest, IoRedirection)
{
string directory = os::getcwd(); // We're inside a temporary sandbox.
slave::Flags flags;
flags.launcher_dir = path::join(tests::flags.build_dir, "src");
// Use local=false so std{err,out} are redirected to files.
Try<MesosContainerizer*> containerizer =
MesosContainerizer::create(flags, false);
ASSERT_SOME(containerizer);
ContainerID containerId;
containerId.set_value("test_container");
string errMsg = "this is stderr";
string outMsg = "this is stdout";
string command =
"(echo '" + errMsg + "' 1>&2) && echo '" + outMsg + "'";
process::Future<bool> launch = containerizer.get()->launch(
containerId,
CREATE_EXECUTOR_INFO("executor", command),
directory,
None(),
SlaveID(),
process::PID<Slave>(),
false);
// Wait for the launch to complete.
AWAIT_READY(launch);
// Wait on the container.
process::Future<containerizer::Termination> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
// Check the executor exited correctly.
EXPECT_TRUE(wait.get().has_status());
EXPECT_EQ(0, wait.get().status());
// Check that std{err, out} was redirected.
EXPECT_SOME_EQ(errMsg + "\n", os::read(path::join(directory, "stderr")));
EXPECT_SOME_EQ(outMsg + "\n", os::read(path::join(directory, "stdout")));
delete containerizer.get();
}