You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by bm...@apache.org on 2016/03/29 04:01:08 UTC

[1/2] mesos git commit: Added tests for cgroups devices subsystem.

Repository: mesos
Updated Branches:
  refs/heads/master 4c81f29b4 -> 50b904033


Added tests for cgroups devices subsystem.

Review: https://reviews.apache.org/r/44975/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/50b90403
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/50b90403
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/50b90403

Branch: refs/heads/master
Commit: 50b904033ce495ad6e1375c17ec3faf2bea77091
Parents: e716189
Author: Abhishek Dasgupta <a1...@linux.vnet.ibm.com>
Authored: Mon Mar 28 18:26:35 2016 -0700
Committer: Benjamin Mahler <bm...@apache.org>
Committed: Mon Mar 28 19:00:37 2016 -0700

----------------------------------------------------------------------
 src/tests/containerizer/cgroups_tests.cpp | 142 +++++++++++++++++++++++++
 1 file changed, 142 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/50b90403/src/tests/containerizer/cgroups_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/cgroups_tests.cpp b/src/tests/containerizer/cgroups_tests.cpp
index b29ce09..ecd83c7 100644
--- a/src/tests/containerizer/cgroups_tests.cpp
+++ b/src/tests/containerizer/cgroups_tests.cpp
@@ -218,6 +218,15 @@ public:
 };
 
 
+class CgroupsAnyHierarchyDevicesTest
+  : public CgroupsAnyHierarchyTest
+{
+public:
+  CgroupsAnyHierarchyDevicesTest()
+    : CgroupsAnyHierarchyTest("devices") {}
+};
+
+
 TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Enabled)
 {
   EXPECT_SOME_TRUE(cgroups::enabled(""));
@@ -1268,6 +1277,139 @@ TEST_F(CgroupsAnyHierarchyWithCpuAcctMemoryTest, ROOT_CGROUPS_CpuAcctsStats)
   AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
 }
 
+
+// TODO(klueska): Ideally we would call this test CgroupsDevicesTest,
+// but currently we filter tests on the keyword 'Cgroup' and only run
+// them if we have root access. However, this test doesn't require
+// root access since it's just testing the parsing aspects of the
+// cgroups devices whitelist. If we ever modify this filter to be less
+// restrictive, we should rename this test accordingly.
+TEST(DevicesTest, Parse)
+{
+  EXPECT_ERROR(cgroups::devices::Entry::parse(""));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("x"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b x"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b *:*"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b *:* x"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b *:* rwmx"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b *:* rwm x"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b x:x rwm"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b *:x rwm"));
+  EXPECT_ERROR(cgroups::devices::Entry::parse("b x:* rwm"));
+
+  Try<cgroups::devices::Entry> entry =
+    cgroups::devices::Entry::parse("a");
+  EXPECT_EQ("a *:* rwm", stringify(entry.get()));
+
+  // Note that if the first character is "a", the rest
+  // of the input is ignored, even if it is invalid!
+  entry = cgroups::devices::Entry::parse("a x x");
+  EXPECT_EQ("a *:* rwm", stringify(entry.get()));
+
+  entry = cgroups::devices::Entry::parse("b *:* rwm");
+  EXPECT_EQ("b *:* rwm", stringify(entry.get()));
+
+  entry = cgroups::devices::Entry::parse("b *:* r");
+  EXPECT_EQ("b *:* r", stringify(entry.get()));
+
+  entry = cgroups::devices::Entry::parse("b *:* w");
+  EXPECT_EQ("b *:* w", stringify(entry.get()));
+
+  entry = cgroups::devices::Entry::parse("b *:* m");
+  EXPECT_EQ("b *:* m", stringify(entry.get()));
+
+  entry = cgroups::devices::Entry::parse("b 1:* rwm");
+  EXPECT_EQ("b 1:* rwm", stringify(entry.get()));
+
+  entry = cgroups::devices::Entry::parse("b *:3 rwm");
+  EXPECT_EQ("b *:3 rwm", stringify(entry.get()));
+
+  entry = cgroups::devices::Entry::parse("b 1:3 rwm");
+  EXPECT_EQ("b 1:3 rwm", stringify(entry.get()));
+}
+
+
+TEST_F(CgroupsAnyHierarchyDevicesTest, ROOT_CGROUPS_Devices)
+{
+  // Create a cgroup in the devices hierarchy.
+  string hierarchy = path::join(baseHierarchy, "devices");
+  ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+  // Assign ourselves to the cgroup.
+  CHECK_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid()));
+
+  // When a devices cgroup is first created, its whitelist inherits
+  // all devices from its parent's whitelist (i.e., "a *:* rwm" by
+  // default). In theory, we should be able to add and remove devices
+  // from the whitelist by writing to the respective `devices.allow`
+  // and `devices.deny` files associated with the cgroup. However, the
+  // semantics of the whitelist are such that writing to the deny file
+  // will only remove entries in the whitelist that are explicitly
+  // listed in there (i.e., denying "b 1:3 rwm" when the whitelist
+  // only contains "a *:* rwm" will not modify the whitelist because
+  // "b 1:3 rwm" is not explicitly listed).  Although the whitelist
+  // doesn't change, access to the device is still denied as expected
+  // (there is just no way of querying the system to detect it).
+  // Because of this, we first deny access to all devices and
+  // selectively add some back in so we can control the entries in the
+  // whitelist explicitly.
+  Try<cgroups::devices::Entry> entry =
+    cgroups::devices::Entry::parse("a *:* rwm");
+
+  ASSERT_SOME(entry);
+
+  Try<Nothing> deny = cgroups::devices::deny(
+      hierarchy,
+      TEST_CGROUPS_ROOT,
+      entry.get());
+
+  EXPECT_SOME(deny);
+
+  // Verify that there are no entries in the whitelist.
+  Try<vector<cgroups::devices::Entry>> whitelist =
+      cgroups::devices::list(hierarchy, TEST_CGROUPS_ROOT);
+
+  ASSERT_SOME(whitelist);
+
+  EXPECT_TRUE(whitelist->empty());
+
+  // Verify that we can't open /dev/null.
+  Try<int> fd = os::open("/dev/null", O_RDWR);
+  EXPECT_ERROR(fd);
+
+  // Add /dev/null to the list of allowed devices.
+  entry = cgroups::devices::Entry::parse("c 1:3 rwm");
+
+  ASSERT_SOME(entry);
+
+  Try<Nothing> allow = cgroups::devices::allow(
+      hierarchy,
+      TEST_CGROUPS_ROOT,
+      entry.get());
+
+  EXPECT_SOME(allow);
+
+  // Verify that /dev/null is in the whitelist.
+  whitelist = cgroups::devices::list(hierarchy, TEST_CGROUPS_ROOT);
+
+  ASSERT_SOME(whitelist);
+
+  ASSERT_EQ(1u, whitelist->size());
+
+  EXPECT_EQ(entry.get(), whitelist.get()[0]);
+
+  // Verify that we can now open and write to /dev/null.
+  fd = os::open("/dev/null", O_WRONLY);
+  ASSERT_SOME(fd);
+
+  Try<Nothing> write = os::write(fd.get(), "nonsense");
+  EXPECT_SOME(write);
+
+  // Move ourselves to the root cgroup.
+  CHECK_SOME(cgroups::assign(hierarchy, "", ::getpid()));
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {


[2/2] mesos git commit: Added devices subsystem support in cgroups library.

Posted by bm...@apache.org.
Added devices subsystem support in cgroups library.

Review: https://reviews.apache.org/r/44974/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/e7161896
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/e7161896
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/e7161896

Branch: refs/heads/master
Commit: e7161896782f5e5ddc5d32e332b090b7bae833ae
Parents: 4c81f29
Author: Abhishek Dasgupta <a1...@linux.vnet.ibm.com>
Authored: Mon Mar 28 17:34:09 2016 -0700
Committer: Benjamin Mahler <bm...@apache.org>
Committed: Mon Mar 28 19:00:37 2016 -0700

----------------------------------------------------------------------
 src/linux/cgroups.cpp | 249 +++++++++++++++++++++++++++++++++++++++++++++
 src/linux/cgroups.hpp |  79 ++++++++++++++
 2 files changed, 328 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/e7161896/src/linux/cgroups.cpp
----------------------------------------------------------------------
diff --git a/src/linux/cgroups.cpp b/src/linux/cgroups.cpp
index df18ed4..fd3df55 100644
--- a/src/linux/cgroups.cpp
+++ b/src/linux/cgroups.cpp
@@ -2418,6 +2418,255 @@ Future<uint64_t> Counter::value() const
 } // namespace memory {
 
 
+namespace devices {
+
+ostream& operator<<(ostream& stream, const Entry::Selector& selector)
+{
+  switch (selector.type) {
+    case Entry::Selector::Type::ALL:
+      stream << "a";
+      break;
+    case Entry::Selector::Type::BLOCK:
+      stream << "b";
+      break;
+    case Entry::Selector::Type::CHARACTER:
+      stream << "c";
+      break;
+    // We omit the default case because we assume -Wswitch
+    // will trigger a compile-time error if a case is missed.
+  }
+
+  stream << " ";
+
+  if (selector.major.isSome()) {
+    stream << stringify(selector.major.get());
+  } else {
+    stream << "*";
+  }
+
+  stream << ":";
+
+  if (selector.minor.isSome()) {
+    stream << stringify(selector.minor.get());
+  } else {
+    stream << "*";
+  }
+
+  return stream;
+}
+
+
+ostream& operator<<(ostream& stream, const Entry::Access& access)
+{
+  if (access.read) {
+    stream << "r";
+  }
+  if (access.write) {
+    stream << "w";
+  }
+  if (access.mknod) {
+    stream << "m";
+  }
+
+  return stream;
+}
+
+
+ostream& operator<<(ostream& stream, const Entry& ListEntry)
+{
+  return stream << ListEntry.selector << " " << ListEntry.access;
+}
+
+
+bool operator==(const Entry::Selector& left, const Entry::Selector& right)
+{
+  return left.type == right.type &&
+         left.minor == right.minor &&
+         left.major == right.major;
+}
+
+
+bool operator==(const Entry::Access& left, const Entry::Access& right)
+{
+  return left.read == right.read &&
+         left.write == right.write &&
+         left.mknod == right.mknod;
+}
+
+
+bool operator==(const Entry& left, const Entry& right)
+{
+  return left.selector == right.selector &&
+         left.access == right.access;
+}
+
+
+Try<Entry> Entry::parse(const string& s)
+{
+  vector<string> tokens = strings::tokenize(s, " ");
+
+  if (tokens.empty()) {
+    return Error("Invalid format");
+  }
+
+  Entry entry;
+
+  // Parse the device type.
+  // By default, when "a" is set as the device type
+  // all other fields in the device entry are ignored!
+  // (i.e. "a" implies a *:* rwm").
+  if (tokens[0] == "a") {
+    entry.selector.type = Selector::Type::ALL;
+    entry.selector.major = None();
+    entry.selector.minor = None();
+    entry.access.read = true;
+    entry.access.write = true;
+    entry.access.mknod = true;
+    return entry;
+  }
+
+  if (tokens.size() != 3) {
+    return Error("Invalid format");
+  }
+
+  // Other than for the "a" device, a correctly formed entry must
+  // contain exactly 3 space-separated tokens of the form:
+  // "{b,c} <major>:<minor> [r][w][m]".
+  if (tokens[0] == "b") {
+    entry.selector.type = Selector::Type::BLOCK;
+  } else if (tokens[0] == "c") {
+    entry.selector.type = Selector::Type::CHARACTER;
+  } else {
+    return Error("Invalid format");
+  }
+
+  // Parse the device major/minor numbers.
+  vector<string> deviceNumbers = strings::tokenize(tokens[1], ":");
+
+  if (deviceNumbers.size() != 2) {
+    return Error("Invalid format");
+  }
+
+  // The device major/minor numbers must either be "*"
+  // or an unsigned integer. A value of None() means "*".
+  entry.selector.major = None();
+  entry.selector.minor = None();
+
+  if (deviceNumbers[0] != "*") {
+    Try<dev_t> major = numify<dev_t>(deviceNumbers[0]);
+
+    if (major.isError()) {
+      return Error("Invalid format");
+    }
+
+    entry.selector.major = major.get();
+  }
+
+  if (deviceNumbers[1] != "*") {
+    Try<dev_t> minor = numify<dev_t>(deviceNumbers[1]);
+
+    if (minor.isError()) {
+      return Error("Invalid format");
+    }
+
+    entry.selector.minor = minor.get();
+  }
+
+  // Parse the access bits.
+  string permissions = tokens[2];
+
+  if (permissions.size() > 3) {
+    return Error("Invalid format");
+  }
+
+  entry.access.read = false;
+  entry.access.write = false;
+  entry.access.mknod = false;
+
+  foreach (char permission, permissions) {
+    if (permission == 'r') {
+      entry.access.read = true;
+    } else if (permission == 'w') {
+      entry.access.write = true;
+    } else if (permission == 'm') {
+      entry.access.mknod = true;
+    } else {
+      return Error("Invalid format");
+    }
+  }
+
+  return entry;
+}
+
+
+Try<vector<Entry>> list(
+    const string& hierarchy,
+    const string& cgroup)
+{
+  Try<string> read = cgroups::read(hierarchy, cgroup, "devices.list");
+
+  if (read.isError()) {
+    return Error("Failed to read from 'devices.list': " + read.error());
+  }
+
+  vector<Entry> entries;
+
+  foreach (const string& s, strings::tokenize(read.get(), "\n")) {
+    Try<Entry> entry = Entry::parse(s);
+
+    if (entry.isError()) {
+      return Error("Failed to parse device entry '" + s + "'"
+                   " from 'devices.list': " + entry.error());
+    }
+
+    entries.push_back(entry.get());
+  }
+
+  return entries;
+}
+
+
+Try<Nothing> allow(
+    const string& hierarchy,
+    const string& cgroup,
+    const Entry& entry)
+{
+  Try<Nothing> write = cgroups::write(
+     hierarchy,
+     cgroup,
+     "devices.allow",
+     stringify(entry));
+
+  if (write.isError()) {
+    return Error("Failed to write to 'devices.allow': " + write.error());
+  }
+
+  return Nothing();
+}
+
+
+Try<Nothing> deny(
+    const string& hierarchy,
+    const string& cgroup,
+    const Entry& entry)
+{
+  Try<Nothing> write = cgroups::write(
+     hierarchy,
+     cgroup,
+     "devices.deny",
+     stringify(entry));
+
+  if (write.isError()) {
+    return Error("Failed to write to 'devices.deny': " + write.error());
+  }
+
+  return Nothing();
+}
+
+
+} // namespace devices {
+
+
 namespace freezer {
 
 Future<Nothing> freeze(

http://git-wip-us.apache.org/repos/asf/mesos/blob/e7161896/src/linux/cgroups.hpp
----------------------------------------------------------------------
diff --git a/src/linux/cgroups.hpp b/src/linux/cgroups.hpp
index 51ccefd..c040962 100644
--- a/src/linux/cgroups.hpp
+++ b/src/linux/cgroups.hpp
@@ -622,6 +622,85 @@ private:
 } // namespace memory {
 
 
+// Device controls.
+namespace devices {
+
+struct Entry
+{
+  static Try<Entry> parse(const std::string& s);
+
+  struct Selector
+  {
+    enum Type
+    {
+      ALL,
+      BLOCK,
+      CHARACTER,
+    };
+
+    Type type;
+    Option<dev_t> major; // Matches all `major` numbers if None.
+    Option<dev_t> minor; // Matches all `minor` numbers if None.
+  };
+
+  struct Access
+  {
+    bool read;
+    bool write;
+    bool mknod;
+  };
+
+  Selector selector;
+  Access access;
+};
+
+
+std::ostream& operator<<(
+    std::ostream& stream,
+    const Entry::Selector& selector);
+
+std::ostream& operator<<(
+    std::ostream& stream,
+    const Entry::Access& access);
+
+std::ostream& operator<<(
+    std::ostream& stream,
+    const Entry& ListEntry);
+
+
+bool operator==(
+    const Entry::Selector& left,
+    const Entry::Selector& right);
+
+bool operator==(
+    const Entry::Access& left,
+    const Entry::Access& right);
+
+bool operator==(
+    const Entry& left,
+    const Entry& right);
+
+
+// Returns the entries within devices.list.
+Try<std::vector<Entry>> list(
+    const std::string& hierarchy,
+    const std::string& cgroup);
+
+// Writes the provided `entry` into devices.allow.
+Try<Nothing> allow(
+    const std::string& hierarchy,
+    const std::string& cgroup,
+    const Entry& entry);
+
+// Writes the provided `entry` into devices.deny.
+Try<Nothing> deny(
+    const std::string& hierarchy,
+    const std::string& cgroup,
+    const Entry& entry);
+
+} // namespace devices {
+
+
 // Freezer controls.
 // The freezer can be in one of three states:
 // 1. THAWED   : No process in the cgroup is frozen.