blob: 400ce937e8c53482d78eeeece91c8d9515a626ea [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 <sys/wait.h>
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <list>
#include <set>
#include <vector>
#include <gtest/gtest.h>
#include <stout/gtest.hpp>
#include <stout/lambda.hpp>
#include <stout/os.hpp>
#include <process/gtest.hpp>
#include <process/subprocess.hpp>
#include "linux/ns.hpp"
#include "tests/flags.hpp"
#include "tests/setns_test_helper.hpp"
using namespace process;
using std::list;
using std::set;
using std::string;
using std::vector;
namespace mesos {
namespace internal {
namespace tests {
// Helper for cloneChild() which expects an int(void*).
static int cloneChildHelper(void* _func)
{
const lambda::function<int()>* func =
static_cast<const lambda::function<int()>*> (_func);
return (*func)();
}
static pid_t cloneChild(
int flags,
const lambda::function<int()>& func)
{
// 8 MiB stack for child.
static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
return ::clone(
cloneChildHelper,
&stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
flags | SIGCHLD,
(void*) &func);
}
// Test that a child in different namespace(s) can setns back to the
// root namespace. We must fork a child to test this because setns
// doesn't support multi-threaded processes (which gtest is).
TEST(NsTest, ROOT_setns)
{
// Clone then exec the setns-test-helper into a new namespace for
// each available namespace.
set<string> namespaces = ns::namespaces();
ASSERT_FALSE(namespaces.empty());
int flags = 0;
foreach (const string& ns, namespaces) {
Try<int> nstype = ns::nstype(ns);
ASSERT_SOME(nstype);
flags |= nstype.get();
}
vector<string> argv;
argv.push_back("setns-test-helper");
argv.push_back(SetnsTestHelper::NAME);
Try<Subprocess> s = subprocess(
path::join(tests::flags.build_dir, "src", "setns-test-helper"),
argv,
Subprocess::FD(STDIN_FILENO),
Subprocess::FD(STDOUT_FILENO),
Subprocess::FD(STDERR_FILENO),
None(),
None(),
None(),
lambda::bind(&cloneChild, flags, lambda::_1));
// Continue in parent.
ASSERT_SOME(s);
// The child should exit 0.
Future<Option<int>> status = s.get().status();
AWAIT_READY(status);
ASSERT_SOME(status.get());
EXPECT_TRUE(WIFEXITED(status.get().get()));
EXPECT_EQ(0, status.get().get());
}
static void* childThread(void* arg)
{
// Newly created threads have PTHREAD_CANCEL_ENABLE and
// PTHREAD_CANCEL_DEFERRED so they can be cancelled.
while (true) { os::sleep(Seconds(1)); }
return NULL;
}
// Test that setns correctly refuses to re-associate to a namespace if
// the caller is multi-threaded.
TEST(NsTest, ROOT_setnsMultipleThreads)
{
set<string> namespaces = ns::namespaces();
EXPECT_LT(0u, namespaces.size());
// Do not allow multi-threaded environment.
pthread_t pthread;
ASSERT_EQ(0, pthread_create(&pthread, NULL, childThread, NULL));
foreach (const string& ns, namespaces) {
EXPECT_ERROR(ns::setns(::getpid(), ns));
}
// Terminate the threads.
EXPECT_EQ(0, pthread_cancel(pthread));
EXPECT_EQ(0, pthread_join(pthread, NULL));
}
// Use a different child function for clone because it requires
// int(*)(void*).
static int childGetns(void* arg)
{
// Sleep until killed.
while (true) { sleep(1); }
ABORT("Error, child should be killed before reaching here");
}
// Test that we can get the namespace inodes for a forked child.
TEST(NsTest, ROOT_getns)
{
set<string> namespaces = ns::namespaces();
// ns::setns() does not support "pid".
namespaces.erase("pid");
// Use the first other namespace available.
ASSERT_FALSE(namespaces.empty());
string ns = *(namespaces.begin());
ASSERT_SOME(ns::getns(::getpid(), ns));
Try<int> nstype = ns::nstype(ns);
ASSERT_SOME(nstype);
// 8 MiB stack for child.
static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
pid_t pid = clone(
childGetns,
&stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
SIGCHLD | nstype.get(),
NULL);
ASSERT_NE(-1, pid);
// Continue in parent.
Try<ino_t> nsParent = ns::getns(::getpid(), ns);
ASSERT_SOME(nsParent);
Try<ino_t> nsChild = ns::getns(pid, ns);
ASSERT_SOME(nsChild);
// Child should be in a different namespace.
EXPECT_NE(nsParent.get(), nsChild.get());
// Kill the child process.
ASSERT_NE(-1, ::kill(pid, SIGKILL));
// Wait for the child process.
int status;
EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
ASSERT_TRUE(WIFSIGNALED(status));
EXPECT_EQ(SIGKILL, WTERMSIG(status));
}
static int childDestroy(void* arg)
{
// Fork a bunch of children.
::fork();
::fork();
::fork();
// Parent and all children sleep.
while (true) { sleep(1); }
ABORT("Error, child should be killed before reaching here");
}
// Test we can destroy a pid namespace, i.e., kill all processes.
TEST(NsTest, ROOT_destroy)
{
set<string> namespaces = ns::namespaces();
if (namespaces.count("pid") == 0) {
// Pid namespace is not available.
return;
}
Try<int> nstype = ns::nstype("pid");
ASSERT_SOME(nstype);
// 8 MiB stack for child.
static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
pid_t pid = clone(
childDestroy,
&stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
SIGCHLD | nstype.get(),
NULL);
ASSERT_NE(-1, pid);
Future<Option<int>> status = process::reap(pid);
// Ensure the child is in a different pid namespace.
Try<ino_t> childNs = ns::getns(pid, "pid");
ASSERT_SOME(childNs);
Try<ino_t> ourNs = ns::getns(::getpid(), "pid");
ASSERT_SOME(ourNs);
ASSERT_NE(ourNs.get(), childNs.get());
// Kill the child.
AWAIT_READY(ns::pid::destroy(childNs.get()));
AWAIT_READY(status);
ASSERT_SOME(status.get());
ASSERT_TRUE(WIFSIGNALED(status.get().get()));
EXPECT_EQ(SIGKILL, WTERMSIG(status.get().get()));
// Finally, verify that no processes are in the child's pid
// namespace, i.e., destroy() also killed all descendants.
Try<set<pid_t>> pids = os::pids();
ASSERT_SOME(pids);
foreach (pid_t pid, pids.get()) {
Try<ino_t> otherNs = ns::getns(pid, "pid");
// pid may have exited since getting the snapshot of pids so
// ignore any error.
if (otherNs.isSome()) {
ASSERT_SOME_NE(childNs.get(), otherNs);
}
}
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {