// This helper allocates memory and prevents the compiler from
// optimizing that allocation away by locking the allocated pages.
static Try<void*> allocateRSS(const Bytes& size)
{
#ifndef __APPLE__
  // Make sure that all pages that are going to be mapped into the
  // address space of this process become unevictable. This is needed
  // for testing cgroups OOM killer.
  if (mlockall(MCL_FUTURE) != 0) {
    return ErrnoError("Failed to make pages to be mapped unevictable");
  }
#endif

  void* rss = nullptr;
  if (posix_memalign(&rss, os::pagesize(), size.bytes()) != 0) {
    return ErrnoError("Failed to increase RSS memory, posix_memalign");
  }

  // Use memset to map pages into the memory space of this process.
  memset(rss, 1, size.bytes());

#ifdef __APPLE__
  // Locking a page makes it unevictable in the kernel. This is needed
  // for testing cgroups OOM killer.
  // NOTE: We use 'mlock' here because 'mlockall' is left
  // unimplemented on OS X.
  if (mlock(rss, size.bytes()) != 0) {
    return ErrnoError("Failed to make mapped pages unevictable");
  }
#endif

  return rss;
}
Beispiel #2
0
static Try<Nothing> increasePageCache(const vector<string>& tokens)
{
  const Bytes UNIT = Megabytes(1);

  if (tokens.size() < 2) {
    return Error("Expect at least one argument");
  }

  Try<Bytes> size = Bytes::parse(tokens[1]);
  if (size.isError()) {
    return Error("The first argument '" + tokens[1] + "' is not a byte size");
  }

  // TODO(chzhcn): Currently, we assume the current working directory
  // is a temporary directory and will be cleaned up when the test
  // finishes. Since the child process will inherit the current
  // working directory from the parent process, that means the test
  // that uses this helper probably needs to inherit from
  // TemporaryDirectoryTest. Consider relaxing this constraint.
  Try<string> path = os::mktemp(path::join(os::getcwd(), "XXXXXX"));
  if (path.isError()) {
    return Error("Failed to create a temporary file: " + path.error());
  }

  Try<int> fd = os::open(path.get(), O_WRONLY);
  if (fd.isError()) {
    return Error("Failed to open file: " + fd.error());
  }

  // NOTE: We are doing round-down here to calculate the number of
  // writes to do.
  for (uint64_t i = 0; i < size.get().bytes() / UNIT.bytes(); i++) {
    // Write UNIT size to disk at a time. The content isn't important.
    Try<Nothing> write = os::write(fd.get(), string(UNIT.bytes(), 'a'));
    if (write.isError()) {
      os::close(fd.get());
      return Error("Failed to write file: " + write.error());
    }

    // Use fsync to make sure data is written to disk.
    if (fsync(fd.get()) == -1) {
      // Save the error message because os::close below might
      // overwrite the errno.
      const string message = os::strerror(errno);

      os::close(fd.get());
      return Error("Failed to fsync: " + message);
    }
  }

  os::close(fd.get());
  return Nothing();
}
Beispiel #3
0
// This function should be async-signal-safe but it isn't: at least
// posix_memalign, mlock, memset and perror are not safe.
int consumeMemory(const Bytes& _size, const Duration& duration, int pipes[2])
{
  // In child process
  ::close(pipes[1]);

  int buf;
  // Wait until the parent signals us to continue.
  while (::read(pipes[0], &buf, sizeof(buf)) == -1 && errno == EINTR);
  ::close(pipes[0]);

  size_t size = static_cast<size_t>(_size.bytes());
  void* buffer = NULL;

  if (posix_memalign(&buffer, getpagesize(), size) != 0) {
    perror("Failed to allocate page-aligned memory, posix_memalign");
    abort();
  }

  // We use mlock and memset here to make sure that the memory
  // actually gets paged in and thus accounted for.
  if (mlock(buffer, size) != 0) {
    perror("Failed to lock memory, mlock");
    abort();
  }

  if (memset(buffer, 1, size) != buffer) {
    perror("Failed to fill memory, memset");
    abort();
  }

  os::sleep(duration);

  return 0;
}
Beispiel #4
0
// Tests whether a file's size is reported by os::stat::size as expected.
// Tests all four combinations of following a link or not and of a file
// or a link as argument. Also tests that an error is returned for a
// non-existing file.
TEST_F(OsTest, Size)
{
  const string& file = path::join(os::getcwd(), UUID::random().toString());

  const Bytes size = 1053;

  ASSERT_SOME(os::write(file, string(size.bytes(), 'X')));

  // The reported file size should be the same whether following links
  // or not, given that the input parameter is not a link.
  EXPECT_SOME_EQ(size, os::stat::size(file, os::stat::FOLLOW_SYMLINK));
  EXPECT_SOME_EQ(size, os::stat::size(file, os::stat::DO_NOT_FOLLOW_SYMLINK));

  EXPECT_ERROR(os::stat::size("aFileThatDoesNotExist"));

  const string& link = path::join(os::getcwd(), UUID::random().toString());

  ASSERT_SOME(fs::symlink(file, link));

  // Following links we expect the file's size, not the link's.
  EXPECT_SOME_EQ(size, os::stat::size(link, os::stat::FOLLOW_SYMLINK));

  // Not following links, we expect the string length of the linked path.
  EXPECT_SOME_EQ(Bytes(file.size()),
                 os::stat::size(link, os::stat::DO_NOT_FOLLOW_SYMLINK));
}
Beispiel #5
0
TYPED_TEST(MemIsolatorTest, MemUsage)
{
  slave::Flags flags;

  Try<Isolator*> isolator = TypeParam::create(flags);
  CHECK_SOME(isolator);

  ExecutorInfo executorInfo;
  executorInfo.mutable_resources()->CopyFrom(
      Resources::parse("mem:1024").get());

  ContainerID containerId;
  containerId.set_value(UUID::random().toString());

  // Use a relative temporary directory so it gets cleaned up
  // automatically with the test.
  Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
  ASSERT_SOME(dir);

  ContainerConfig containerConfig;
  containerConfig.mutable_executor_info()->CopyFrom(executorInfo);
  containerConfig.set_directory(dir.get());

  AWAIT_READY(isolator.get()->prepare(
      containerId,
      containerConfig));

  MemoryTestHelper helper;
  ASSERT_SOME(helper.spawn());
  ASSERT_SOME(helper.pid());

  // Set up the reaper to wait on the subprocess.
  Future<Option<int>> status = process::reap(helper.pid().get());

  // Isolate the subprocess.
  AWAIT_READY(isolator.get()->isolate(containerId, helper.pid().get()));

  const Bytes allocation = Megabytes(128);
  EXPECT_SOME(helper.increaseRSS(allocation));

  Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
  AWAIT_READY(usage);

  EXPECT_GE(usage.get().mem_rss_bytes(), allocation.bytes());

  // Ensure the process is killed.
  helper.cleanup();

  // Make sure the subprocess was reaped.
  AWAIT_READY(status);

  // Let the isolator clean up.
  AWAIT_READY(isolator.get()->cleanup(containerId));

  delete isolator.get();
}
Beispiel #6
0
  static Option<Error> validateSize(const Bytes& value)
  {
    if (value.bytes() < static_cast<uint64_t>(os::pagesize())) {
      return Error(
          "Expected --max_stdout_size and --max_stderr_size of "
          "at least " + stringify(os::pagesize()) + " bytes");
    }

    return None();
  }
Beispiel #7
0
TYPED_TEST(MemIsolatorTest, MemUsage)
{
  Flags flags;

  Try<Isolator*> isolator = TypeParam::create(flags);
  CHECK_SOME(isolator);

  // A PosixLauncher is sufficient even when testing a cgroups isolator.
  Try<Launcher*> launcher = PosixLauncher::create(flags);

  ExecutorInfo executorInfo;
  executorInfo.mutable_resources()->CopyFrom(
      Resources::parse("mem:1024").get());

  ContainerID containerId;
  containerId.set_value("memory_usage");

  AWAIT_READY(isolator.get()->prepare(containerId, executorInfo));

  int pipes[2];
  ASSERT_NE(-1, ::pipe(pipes));

  lambda::function<int()> inChild = lambda::bind(
      &consumeMemory,
      Megabytes(256),
      Seconds(10),
      pipes);

  Try<pid_t> pid = launcher.get()->fork(containerId, inChild);
  ASSERT_SOME(pid);

  // Set up the reaper to wait on the forked child.
  Future<Option<int> > status = process::reap(pid.get());

  // Continue in the parent.
  ::close(pipes[0]);

  // Isolate the forked child.
  AWAIT_READY(isolator.get()->isolate(containerId, pid.get()));

  // Now signal the child to continue.
  int buf;
  ASSERT_LT(0, ::write(pipes[1], &buf, sizeof(buf)));
  ::close(pipes[1]);

  // Wait up to 5 seconds for the child process to consume 256 MB of memory;
  ResourceStatistics statistics;
  Bytes threshold = Megabytes(256);
  Duration waited = Duration::zero();
  do {
    Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
    AWAIT_READY(usage);

    statistics = usage.get();

    // If we meet our usage expectations, we're done!
    if (statistics.mem_rss_bytes() >= threshold.bytes()) {
      break;
    }

    os::sleep(Seconds(1));
    waited += Seconds(1);
  } while (waited < Seconds(5));

  EXPECT_LE(threshold.bytes(), statistics.mem_rss_bytes());

  // Ensure all processes are killed.
  AWAIT_READY(launcher.get()->destroy(containerId));

  // Make sure the child was reaped.
  AWAIT_READY(status);

  // Let the isolator clean up.
  AWAIT_READY(isolator.get()->cleanup(containerId));

  delete isolator.get();
  delete launcher.get();
}
Beispiel #8
0
TYPED_TEST(MemIsolatorTest, MemUsage)
{
    slave::Flags flags;

    Try<Isolator*> isolator = TypeParam::create(flags);
    CHECK_SOME(isolator);

    // A PosixLauncher is sufficient even when testing a cgroups isolator.
    Try<Launcher*> launcher = PosixLauncher::create(flags);

    ExecutorInfo executorInfo;
    executorInfo.mutable_resources()->CopyFrom(
        Resources::parse("mem:1024").get());

    ContainerID containerId;
    containerId.set_value("memory_usage");

    // Use a relative temporary directory so it gets cleaned up
    // automatically with the test.
    Try<string> dir = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
    ASSERT_SOME(dir);

    AWAIT_READY(
        isolator.get()->prepare(containerId, executorInfo, dir.get(), None()));

    int pipes[2];
    ASSERT_NE(-1, ::pipe(pipes));

    Try<pid_t> pid = launcher.get()->fork(
                         containerId,
                         "/bin/sh",
                         vector<string>(),
                         Subprocess::FD(STDIN_FILENO),
                         Subprocess::FD(STDOUT_FILENO),
                         Subprocess::FD(STDERR_FILENO),
                         None(),
                         None(),
                         lambda::bind(&consumeMemory, Megabytes(256), Seconds(10), pipes));

    ASSERT_SOME(pid);

    // Set up the reaper to wait on the forked child.
    Future<Option<int> > status = process::reap(pid.get());

    // Continue in the parent.
    ASSERT_SOME(os::close(pipes[0]));

    // Isolate the forked child.
    AWAIT_READY(isolator.get()->isolate(containerId, pid.get()));

    // Now signal the child to continue.
    char dummy;
    ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));

    ASSERT_SOME(os::close(pipes[1]));

    // Wait up to 5 seconds for the child process to consume 256 MB of memory;
    ResourceStatistics statistics;
    Bytes threshold = Megabytes(256);
    Duration waited = Duration::zero();
    do {
        Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
        AWAIT_READY(usage);

        statistics = usage.get();

        // If we meet our usage expectations, we're done!
        if (statistics.mem_rss_bytes() >= threshold.bytes()) {
            break;
        }

        os::sleep(Seconds(1));
        waited += Seconds(1);
    } while (waited < Seconds(5));

    EXPECT_LE(threshold.bytes(), statistics.mem_rss_bytes());

    // Ensure all processes are killed.
    AWAIT_READY(launcher.get()->destroy(containerId));

    // Make sure the child was reaped.
    AWAIT_READY(status);

    // Let the isolator clean up.
    AWAIT_READY(isolator.get()->cleanup(containerId));

    delete isolator.get();
    delete launcher.get();
}