Move Linux namespace functions into linux/. Also moved and updated tests.
A following commit will remove this code from stout. Review: https://reviews.apache.org/r/27091 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/57447a72 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/57447a72 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/57447a72 Branch: refs/heads/master Commit: 57447a729c0ee6c0b12a5f1e89034f42284e2e42 Parents: 2b8ad0b Author: Ian Downes <[email protected]> Authored: Thu Oct 23 18:14:58 2014 -0700 Committer: Ian Downes <[email protected]> Committed: Tue Oct 28 11:44:07 2014 -0700 ---------------------------------------------------------------------- src/Makefile.am | 7 + src/linux/ns.hpp | 203 +++++++++++++++++++ .../isolators/network/port_mapping.cpp | 8 +- src/tests/ns_tests.cpp | 156 ++++++++++++++ src/tests/setns_test_helper.cpp | 70 +++++++ src/tests/setns_test_helper.hpp | 38 ++++ 6 files changed, 478 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/57447a72/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index f177d87..a1549c2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -406,6 +406,7 @@ libmesos_no_3rdparty_la_SOURCES += \ hdfs/hdfs.hpp \ linux/cgroups.hpp \ linux/fs.hpp \ + linux/ns.hpp \ linux/perf.hpp \ local/flags.hpp \ local/local.hpp \ @@ -1150,6 +1151,11 @@ load_generator_framework_SOURCES = examples/load_generator_framework.cpp load_generator_framework_CPPFLAGS = $(MESOS_CPPFLAGS) load_generator_framework_LDADD = libmesos.la +check_PROGRAMS += setns-test-helper +setns_test_helper_SOURCES = tests/setns_test_helper.cpp +setns_test_helper_CPPFLAGS = $(MESOS_CPPFLAGS) +setns_test_helper_LDADD = libmesos.la + check_PROGRAMS += mesos-tests # Library containing an example module. @@ -1233,6 +1239,7 @@ if OS_LINUX mesos_tests_SOURCES += tests/cgroups_isolator_tests.cpp mesos_tests_SOURCES += tests/cgroups_tests.cpp mesos_tests_SOURCES += tests/fs_tests.cpp + mesos_tests_SOURCES += tests/ns_tests.cpp mesos_tests_SOURCES += tests/perf_tests.cpp endif http://git-wip-us.apache.org/repos/asf/mesos/blob/57447a72/src/linux/ns.hpp ---------------------------------------------------------------------- diff --git a/src/linux/ns.hpp b/src/linux/ns.hpp new file mode 100644 index 0000000..53c95a4 --- /dev/null +++ b/src/linux/ns.hpp @@ -0,0 +1,203 @@ +/** + * Licensed 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 __LINUX_NS_HPP__ +#define __LINUX_NS_HPP__ + +// This file contains Linux-only OS utilities. +#ifndef __linux__ +#error "linux/ns.hpp is only available on Linux systems." +#endif + +#include <sched.h> +#include <unistd.h> + +#include <sys/syscall.h> + +#include <set> +#include <string> + +#include <stout/error.hpp> +#include <stout/hashmap.hpp> +#include <stout/nothing.hpp> +#include <stout/os.hpp> +#include <stout/path.hpp> +#include <stout/proc.hpp> +#include <stout/stringify.hpp> +#include <stout/try.hpp> + +#include <stout/os/exists.hpp> +#include <stout/os/ls.hpp> + +namespace ns { + +// Returns all the supported namespaces by the kernel. +inline std::set<std::string> namespaces() +{ + std::set<std::string> result; + Try<std::list<std::string> > entries = os::ls("/proc/self/ns"); + if (entries.isSome()) { + foreach (const std::string& entry, entries.get()) { + result.insert(entry); + } + } + return result; +} + + +// Returns the nstype (e.g., CLONE_NEWNET, CLONE_NEWNS, etc.) for the +// given namespace which will be used when calling ::setns. +inline Try<int> nstype(const std::string& ns) +{ + hashmap<std::string, int> nstypes; + +#ifdef CLONE_NEWNS + nstypes["mnt"] = CLONE_NEWNS; +#else + nstypes["mnt"] = 0x00020000; +#endif + +#ifdef CLONE_NEWUTS + nstypes["uts"] = CLONE_NEWUTS; +#else + nstypes["uts"] = 0x04000000; +#endif + +#ifdef CLONE_NEWIPC + nstypes["ipc"] = CLONE_NEWIPC; +#else + nstypes["ipc"] = 0x08000000; +#endif + +#ifdef CLONE_NEWNET + nstypes["net"] = CLONE_NEWNET; +#else + nstypes["net"] = 0x40000000; +#endif + +#ifdef CLONE_NEWUSER + nstypes["user"] = CLONE_NEWUSER; +#else + nstypes["user"] = 0x10000000; +#endif + +#ifdef CLONE_NEWPID + nstypes["pid"] = CLONE_NEWPID; +#else + nstypes["pid"] = 0x20000000; +#endif + + if (!nstypes.contains(ns)) { + return Error("Unknown namespace '" + ns + "'"); + } + + return nstypes[ns]; +} + + +// Re-associate the calling process with the specified namespace. The +// path refers to one of the corresponding namespace entries in the +// /proc/[pid]/ns/ directory (or bind mounted elsewhere). We do not +// allow a process with multiple threads to call this function because +// it will lead to some weird situations where different threads of a +// process are in different namespaces. +inline Try<Nothing> setns(const std::string& path, const std::string& ns) +{ + // Return error if there're multiple threads in the calling process. + Try<std::set<pid_t> > threads = proc::threads(::getpid()); + if (threads.isError()) { + return Error( + "Failed to get the threads of the current process: " + + threads.error()); + } else if (threads.get().size() > 1) { + return Error("Multiple threads exist in the current process"); + } + + if (ns::namespaces().count(ns) == 0) { + return Error("Namespace '" + ns + "' is not supported"); + } + + // Currently, we don't support pid namespace as its semantics is + // different from other namespaces (instead of re-associating the + // calling thread, it re-associates the *children* of the calling + // thread with the specified namespace). + if (ns == "pid") { + return Error("Pid namespace is not supported"); + } + +#ifdef O_CLOEXEC + Try<int> fd = os::open(path, O_RDONLY | O_CLOEXEC); +#else + Try<int> fd = os::open(path, O_RDONLY); +#endif + + if (fd.isError()) { + return Error("Failed to open '" + path + "': " + fd.error()); + } + +#ifndef O_CLOEXEC + Try<Nothing> cloexec = os::cloexec(fd.get()); + if (cloexec.isError()) { + os::close(fd.get()); + return Error("Failed to cloexec: " + cloexec.error()); + } +#endif + + Try<int> nstype = ns::nstype(ns); + if (nstype.isError()) { + return Error(nstype.error()); + } + +#ifdef SYS_setns + int ret = ::syscall(SYS_setns, fd.get(), nstype.get()); +#elif __x86_64__ + // A workaround for those hosts that have an old glibc (older than + // 2.14) but have a new kernel. The magic number '308' here is the + // syscall number for 'setns' on x86_64 architecture. + int ret = ::syscall(308, fd.get(), nstype.get()); +#else +#error "setns is not available" +#endif + + if (ret == -1) { + // Save the errno as it might be overwritten by 'os::close' below. + ErrnoError error; + os::close(fd.get()); + return error; + } + + os::close(fd.get()); + return Nothing(); +} + + +// Re-associate the calling process with the specified namespace. The +// pid specifies the process whose namespace we will associate. +inline Try<Nothing> setns(pid_t pid, const std::string& ns) +{ + if (!os::exists(pid)) { + return Error("Pid " + stringify(pid) + " does not exist"); + } + + std::string path = path::join("/proc", stringify(pid), "ns", ns); + if (!os::exists(path)) { + return Error("Namespace '" + ns + "' is not supported"); + } + + return ns::setns(path, ns); +} + +} // namespace ns { + +#endif // __LINUX_NS_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/57447a72/src/slave/containerizer/isolators/network/port_mapping.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/isolators/network/port_mapping.cpp b/src/slave/containerizer/isolators/network/port_mapping.cpp index 1234d8e..14fae1f 100644 --- a/src/slave/containerizer/isolators/network/port_mapping.cpp +++ b/src/slave/containerizer/isolators/network/port_mapping.cpp @@ -45,11 +45,11 @@ #include <stout/stringify.hpp> #include <stout/os/exists.hpp> -#include <stout/os/setns.hpp> #include "common/status_utils.hpp" #include "linux/fs.hpp" +#include "linux/ns.hpp" #include "linux/routing/route.hpp" #include "linux/routing/utils.hpp" @@ -435,7 +435,7 @@ int PortMappingUpdate::execute() } // Enter the network namespace. - Try<Nothing> setns = os::setns(flags.pid.get(), "net"); + Try<Nothing> setns = ns::setns(flags.pid.get(), "net"); if (setns.isError()) { cerr << "Failed to enter the network namespace of pid " << flags.pid.get() << ": " << setns.error() << endl; @@ -504,7 +504,7 @@ int PortMappingStatistics::execute() } // Enter the network namespace. - Try<Nothing> setns = os::setns(flags.pid.get(), "net"); + Try<Nothing> setns = ns::setns(flags.pid.get(), "net"); if (setns.isError()) { // This could happen if the executor exits before this function is // invoked. We do not log here to avoid spurious logging. @@ -707,7 +707,7 @@ Try<Isolator*> PortMappingIsolatorProcess::create(const Flags& flags) // Verify that the network namespace is available by checking the // existence of the network namespace handle of the current process. - if (os::namespaces().count("net") == 0) { + if (ns::namespaces().count("net") == 0) { return Error( "Using network isolator requires network namespace. " "Make sure your kernel is newer than 3.4"); http://git-wip-us.apache.org/repos/asf/mesos/blob/57447a72/src/tests/ns_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/ns_tests.cpp b/src/tests/ns_tests.cpp new file mode 100644 index 0000000..c4cf9ab --- /dev/null +++ b/src/tests/ns_tests.cpp @@ -0,0 +1,156 @@ +/** + * 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/ns_tests.hpp" + + +using namespace mesos::internal; + +using namespace process; + +using std::list; +using std::set; +using std::string; +using std::vector; + + +// 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("test"); + + 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(0, 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)); +} http://git-wip-us.apache.org/repos/asf/mesos/blob/57447a72/src/tests/setns_test_helper.cpp ---------------------------------------------------------------------- diff --git a/src/tests/setns_test_helper.cpp b/src/tests/setns_test_helper.cpp new file mode 100644 index 0000000..eb8746b --- /dev/null +++ b/src/tests/setns_test_helper.cpp @@ -0,0 +1,70 @@ +/** + * 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 <stout/foreach.hpp> +#include <stout/none.hpp> +#include <stout/subcommand.hpp> +#include <stout/try.hpp> + +#include "linux/ns.hpp" + +#include "tests/setns_test_helper.hpp" + +#include <set> +#include <string> + +using std::set; +using std::string; + +const string SetnsTestHelper::NAME="test"; + +int SetnsTestHelper::execute() +{ + // Get all the available namespaces. + set<string> namespaces = ns::namespaces(); + + // Note: /proc has not been remounted so we can look up pid 1's + // namespaces, even if we're in a separate pid namespace. + foreach (const string& ns, namespaces) { + // ns::setns() does not (currently) support pid namespaces so this + // should return an error. + if (ns == "pid") { + Try<Nothing> setns = ns::setns(1, ns); + if (!setns.isError()) { + return 1; + } + } else { + Try<Nothing> setns = ns::setns(1, ns); + if (!setns.isSome()) { + return 1; + } + } + } + + return 0; +} + + +int main(int argc, char** argv) +{ + return Subcommand::dispatch( + None(), + argc, + argv, + new SetnsTestHelper()); +} http://git-wip-us.apache.org/repos/asf/mesos/blob/57447a72/src/tests/setns_test_helper.hpp ---------------------------------------------------------------------- diff --git a/src/tests/setns_test_helper.hpp b/src/tests/setns_test_helper.hpp new file mode 100644 index 0000000..c6bec95 --- /dev/null +++ b/src/tests/setns_test_helper.hpp @@ -0,0 +1,38 @@ +/** + * 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 __SETNS_TEST_HELPER_HPP__ +#define __SETNS_TEST_HELPER_HPP__ + +#include <string> + +#include <stout/flags.hpp> +#include <stout/subcommand.hpp> + +class SetnsTestHelper : public Subcommand +{ +public: + static const std::string NAME; + + SetnsTestHelper() : Subcommand(NAME) {} + +protected: + virtual int execute(); +}; + +#endif // __SETNS_TEST_HELPER_HPP__
