You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by id...@apache.org on 2014/10/28 20:23:38 UTC
[2/8] git commit: Move Linux namespace functions into linux/.
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 <id...@twitter.com>
Authored: Thu Oct 23 18:14:58 2014 -0700
Committer: Ian Downes <id...@twitter.com>
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__