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__