blob: 9c14f3acbc19631b2f5cac4dc7cd9caba8527712 [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 <unistd.h>
#include <string>
#include <process/future.hpp>
#include <process/owned.hpp>
#include <stout/gtest.hpp>
#include <stout/os.hpp>
#include <stout/path.hpp>
#include <stout/stringify.hpp>
#include <stout/strings.hpp>
#include <stout/uuid.hpp>
#include <mesos/mesos.hpp>
#include <mesos/slave/containerizer.hpp>
#ifdef __linux__
#include "linux/ns.hpp"
#endif
#include "slave/containerizer/fetcher.hpp"
#include "slave/containerizer/mesos/containerizer.hpp"
#include "tests/mesos.hpp"
using std::string;
using process::Future;
using process::Owned;
using mesos::internal::slave::Containerizer;
using mesos::internal::slave::Fetcher;
using mesos::internal::slave::MesosContainerizer;
using mesos::slave::ContainerTermination;
namespace mesos {
namespace internal {
namespace tests {
#ifdef __linux__
class NamespacesIsolatorTest : public MesosTest
{
public:
void SetUp() override
{
MesosTest::SetUp();
directory = os::getcwd(); // We're inside a temporary sandbox.
containerId.set_value(id::UUID::random().toString());
}
Try<Owned<MesosContainerizer>> createContainerizer(
const string& isolation,
const Option<bool>& disallowSharingAgentPidNamespace = None())
{
slave::Flags flags = CreateSlaveFlags();
flags.isolation = isolation;
if (disallowSharingAgentPidNamespace.isSome()) {
flags.disallow_sharing_agent_pid_namespace =
disallowSharingAgentPidNamespace.get();
}
fetcher.reset(new Fetcher(flags));
Try<MesosContainerizer*> _containerizer =
MesosContainerizer::create(flags, false, fetcher.get());
if (_containerizer.isError()) {
return Error(_containerizer.error());
}
return Owned<MesosContainerizer>(_containerizer.get());
}
// Read a uint64_t value from the given path.
Try<uint64_t> readValue(const string& path)
{
Try<string> value = os::read(path);
if (value.isError()) {
return Error("Failed to read '" + path + "': " + value.error());
}
return numify<uint64_t>(strings::trim(value.get()));
}
string directory;
Owned<Fetcher> fetcher;
ContainerID containerId;
};
TEST_F(NamespacesIsolatorTest, ROOT_PidNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/pid");
ASSERT_SOME(containerizer);
// Write the command's pid namespace inode and init name to files.
const string command =
"stat -Lc %i /proc/1/ns/pid > ns && (cat /proc/1/cmdline > init)";
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait on the container.
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
// Check the executor exited correctly.
EXPECT_TRUE(wait->get().has_status());
EXPECT_EQ(0, wait->get().status());
// Check that the command was run in a different pid namespace.
Result<ino_t> testPidNamespace = ns::getns(::getpid(), "pid");
ASSERT_SOME(testPidNamespace);
Try<string> containerPidNamespace = os::read(path::join(directory, "ns"));
ASSERT_SOME(containerPidNamespace);
EXPECT_NE(stringify(testPidNamespace.get()),
strings::trim(containerPidNamespace.get()));
// Check that the word 'mesos' is the part of the name for the
// container's 'init' process. This verifies that /proc has been
// correctly mounted for the container.
Try<string> init = os::read(path::join(directory, "init"));
ASSERT_SOME(init);
EXPECT_TRUE(strings::contains(init.get(), "mesos"));
}
// This test verifies a top-level container can share pid namespace
// with the agent when the field `share_pid_namespace` is set as
// true in `ContainerInfo.linux_info`. Please note that the agent flag
// `--disallow_sharing_agent_pid_namespace` is set to
// false by default, that means top-level container is allowed to share
// pid namespace with agent.
TEST_F(NamespacesIsolatorTest, ROOT_SharePidNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/pid");
ASSERT_SOME(containerizer);
// Write the command's pid namespace inode to file.
const string command = "stat -Lc %i /proc/1/ns/pid > ns";
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_share_pid_namespace(true);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait on the container.
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
// Check the executor exited correctly.
EXPECT_TRUE(wait->get().has_status());
EXPECT_EQ(0, wait->get().status());
// Check that the command was run in the same pid namespace.
Result<ino_t> testPidNamespace = ns::getns(::getpid(), "pid");
ASSERT_SOME(testPidNamespace);
Try<string> containerPidNamespace = os::read(path::join(directory, "ns"));
ASSERT_SOME(containerPidNamespace);
EXPECT_EQ(stringify(testPidNamespace.get()),
strings::trim(containerPidNamespace.get()));
}
// This test verifies launching a top-level container to share
// pid namespace with agent will fail when the agent flag
// `--disallow_sharing_agent_pid_namespace` is set to true.
TEST_F(NamespacesIsolatorTest, ROOT_SharePidNamespaceWhenDisallow)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("filesystem/linux,namespaces/pid", true);
ASSERT_SOME(containerizer);
const string command = "sleep 1000";
mesos::slave::ContainerConfig containerConfig = createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory);
ContainerInfo* container = containerConfig.mutable_container_info();
container->set_type(ContainerInfo::MESOS);
container->mutable_linux_info()->set_share_pid_namespace(true);
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
containerConfig,
std::map<string, string>(),
None());
AWAIT_FAILED(launch);
}
// The IPC namespace has its own copy of the svipc(7) tunables. We verify
// that we are correctly entering the IPC namespace by verifying that we
// can set shmmax some different value than that of the host namespace.
TEST_F(NamespacesIsolatorTest, ROOT_IPCNamespace)
{
Try<Owned<MesosContainerizer>> containerizer =
createContainerizer("namespaces/ipc");
ASSERT_SOME(containerizer);
// Value we will set the child namespace shmmax to.
uint64_t shmmaxValue = static_cast<uint64_t>(::getpid());
Try<uint64_t> hostShmmax = readValue("/proc/sys/kernel/shmmax");
ASSERT_SOME(hostShmmax);
// Verify that the host namespace shmmax is different.
ASSERT_NE(hostShmmax.get(), shmmaxValue);
const string command =
"stat -Lc %i /proc/self/ns/ipc > ns;"
"echo " + stringify(shmmaxValue) + " > /proc/sys/kernel/shmmax;"
"cp /proc/sys/kernel/shmmax shmmax";
process::Future<Containerizer::LaunchResult> launch =
containerizer.get()->launch(
containerId,
createContainerConfig(
None(),
createExecutorInfo("executor", command),
directory),
std::map<string, string>(),
None());
AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
// Wait on the container.
Future<Option<ContainerTermination>> wait =
containerizer.get()->wait(containerId);
AWAIT_READY(wait);
ASSERT_SOME(wait.get());
// Check the executor exited correctly.
EXPECT_TRUE(wait->get().has_status());
EXPECT_EQ(0, wait->get().status());
// Check that the command was run in a different IPC namespace.
Result<ino_t> testIPCNamespace = ns::getns(::getpid(), "ipc");
ASSERT_SOME(testIPCNamespace);
Try<string> containerIPCNamespace = os::read(path::join(directory, "ns"));
ASSERT_SOME(containerIPCNamespace);
EXPECT_NE(stringify(testIPCNamespace.get()),
strings::trim(containerIPCNamespace.get()));
// Check that we modified the IPC shmmax of the namespace, not the host.
Try<uint64_t> childShmmax = readValue("shmmax");
ASSERT_SOME(childShmmax);
// Verify that we didn't modify shmmax in the host namespace.
ASSERT_EQ(hostShmmax.get(), readValue("/proc/sys/kernel/shmmax").get());
EXPECT_NE(hostShmmax.get(), childShmmax.get());
EXPECT_EQ(shmmaxValue, childShmmax.get());
}
#endif // __linux__
} // namespace tests {
} // namespace internal {
} // namespace mesos {