http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/port_mapping_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/port_mapping_tests.cpp b/src/tests/port_mapping_tests.cpp
deleted file mode 100644
index 45ef97a..0000000
--- a/src/tests/port_mapping_tests.cpp
+++ /dev/null
@@ -1,2296 +0,0 @@
-/**
- * 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 <gmock/gmock.h>
-
-#include <iostream>
-#include <string>
-#include <vector>
-
-#include <process/future.hpp>
-#include <process/io.hpp>
-#include <process/reap.hpp>
-#include <process/subprocess.hpp>
-
-#include <stout/bytes.hpp>
-#include <stout/gtest.hpp>
-#include <stout/ip.hpp>
-#include <stout/json.hpp>
-#include <stout/mac.hpp>
-#include <stout/net.hpp>
-#include <stout/stopwatch.hpp>
-
-#include <stout/os/stat.hpp>
-#include <stout/os/exists.hpp>
-
-#include "linux/fs.hpp"
-
-#include "linux/routing/utils.hpp"
-
-#include "linux/routing/filter/ip.hpp"
-
-#include "linux/routing/link/link.hpp"
-
-#include "linux/routing/queueing/ingress.hpp"
-
-#include "master/master.hpp"
-
-#include "mesos/mesos.hpp"
-
-#include "slave/flags.hpp"
-#include "slave/slave.hpp"
-
-#include "slave/containerizer/isolators/network/port_mapping.hpp"
-
-#include "slave/containerizer/fetcher.hpp"
-#include "slave/containerizer/launcher.hpp"
-#include "slave/containerizer/linux_launcher.hpp"
-
-#include "slave/containerizer/mesos/containerizer.hpp"
-#include "slave/containerizer/mesos/launch.hpp"
-
-#include "tests/flags.hpp"
-#include "tests/mesos.hpp"
-#include "tests/utils.hpp"
-
-using namespace mesos::internal::slave;
-
-using namespace process;
-
-using namespace routing;
-using namespace routing::filter;
-using namespace routing::queueing;
-
-using mesos::internal::master::Master;
-
-using mesos::slave::Isolator;
-
-using std::list;
-using std::ostringstream;
-using std::set;
-using std::string;
-using std::vector;
-
-using testing::_;
-using testing::Eq;
-using testing::Return;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-// An old glibc might not have this symbol.
-#ifndef MNT_DETACH
-#define MNT_DETACH 2
-#endif
-
-
-// Each test container works with a common specification of 2 CPUs,
-// 1GB of memory and 1GB of disk space, which experience has shown
-// to be sufficient to not encounter resource starvation issues when
-// running the test suite.
-const char* const containerCPU = "cpus:2";
-const char* const containerMemory = "mem:1024";
-const char* const containerDisk = "disk:1024";
-
-// We configure ephemeral and persistent port ranges outside the
-// default linux ip_local_port_range [32768-61000] in order to reduce
-// the probability of a conflict which could result in spurious
-// results (positive or negative) from these tests.
-const char* const ephemeralPorts = "ephemeral_ports:[30001-30999]";
-const char* const persistentPorts = "ports:[31000-32000]";
-
-// To keep things simple, we used fixed port ranges for our containers
-// in these tests rather than try to dynamically track port usage.
-// Note that container ports must be contained in the persistent port
-// range.
-const char* const container1Ports = "ports:[31000-31499]";
-const char* const container2Ports = "ports:[31500-32000]";
-
-// We define a validPort in the container1 assigned range which can
-// therefore accept incoming traffic.
-const int validPort = 31001;
-
-// We also define a port outside the persistent port range; containers
-// connecting to this port will never receive incoming traffic.
-const int invalidPort = 32502;
-
-
-static void cleanup(const string& eth0, const string& lo)
-{
- // Clean up the ingress qdisc on eth0 and lo if exists.
- Try<bool> hostEth0ExistsQdisc = ingress::exists(eth0);
- ASSERT_SOME(hostEth0ExistsQdisc);
-
- if (hostEth0ExistsQdisc.get()) {
- ASSERT_SOME_TRUE(ingress::remove(eth0));
- }
-
- Try<bool> hostLoExistsQdisc = ingress::exists(lo);
- ASSERT_SOME(hostLoExistsQdisc);
-
- if (hostLoExistsQdisc.get()) {
- ASSERT_SOME_TRUE(ingress::remove(lo));
- }
-
- // Clean up all 'veth' devices if exist.
- Try<set<string> > links = net::links();
- ASSERT_SOME(links);
-
- foreach (const string& name, links.get()) {
- if (strings::startsWith(name, slave::PORT_MAPPING_VETH_PREFIX())) {
- ASSERT_SOME_TRUE(link::remove(name));
- }
- }
-
- Try<list<string> > entries = os::ls(slave::PORT_MAPPING_BIND_MOUNT_ROOT());
- ASSERT_SOME(entries);
-
- foreach (const string& file, entries.get()) {
- string target = path::join(slave::PORT_MAPPING_BIND_MOUNT_ROOT(), file);
-
- // NOTE: Here, we ignore the unmount errors because previous tests
- // may have created the file and died before mounting.
- if (!os::stat::islink(target)) {
- mesos::internal::fs::unmount(target, MNT_DETACH);
- }
-
- // Remove the network namespace handle and the corresponding
- // symlinks. The removal here is best effort.
- os::rm(target);
- }
-}
-
-
-class PortMappingIsolatorTest : public TemporaryDirectoryTest
-{
-public:
- static void SetUpTestCase()
- {
- ASSERT_SOME(routing::check())
- << "-------------------------------------------------------------\n"
- << "We cannot run any PortMappingIsolatorTests because your\n"
- << "libnl library is not new enough. You can either install a\n"
- << "new libnl library, or disable this test case\n"
- << "-------------------------------------------------------------";
-
- ASSERT_SOME_EQ(0, os::shell(NULL, "which nc"))
- << "-------------------------------------------------------------\n"
- << "We cannot run any PortMappingIsolatorTests because 'nc'\n"
- << "could not be found. You can either install 'nc', or disable\n"
- << "this test case\n"
- << "-------------------------------------------------------------";
-
- ASSERT_SOME_EQ(0, os::shell(NULL, "which arping"))
- << "-------------------------------------------------------------\n"
- << "We cannot run some PortMappingIsolatorTests because 'arping'\n"
- << "could not be found. You can either isntall 'arping', or\n"
- << "disable this test case\n"
- << "-------------------------------------------------------------";
- }
-
- PortMappingIsolatorTest() : hostIP(net::IP(INADDR_ANY)) {}
-
-protected:
- virtual void SetUp()
- {
- TemporaryDirectoryTest::SetUp();
-
- flags = CreateSlaveFlags();
-
- // Guess the name of the public interface.
- Result<string> _eth0 = link::eth0();
- ASSERT_SOME(_eth0) << "Failed to guess the name of the public interface";
-
- eth0 = _eth0.get();
-
- LOG(INFO) << "Using " << eth0 << " as the public interface";
-
- // Guess the name of the loopback interface.
- Result<string> _lo = link::lo();
- ASSERT_SOME(_lo) << "Failed to guess the name of the loopback interface";
-
- lo = _lo.get();
-
- LOG(INFO) << "Using " << lo << " as the loopback interface";
-
- // Clean up qdiscs and veth devices.
- cleanup(eth0, lo);
-
- // Get host IP address.
- Result<net::IPNetwork> hostIPNetwork =
- net::IPNetwork::fromLinkDevice(eth0, AF_INET);
-
- CHECK_SOME(hostIPNetwork)
- << "Failed to retrieve the host public IP network from " << eth0 << ": "
- << hostIPNetwork.error();
-
- hostIP = hostIPNetwork.get().address();
-
- // Get all the external name servers for tests that need to talk
- // to an external host, e.g., ping, DNS.
- Try<string> read = os::read("/etc/resolv.conf");
- CHECK_SOME(read);
-
- foreach (const string& line, strings::split(read.get(), "\n")) {
- if (!strings::startsWith(line, "nameserver")) {
- continue;
- }
-
- vector<string> tokens = strings::split(line, " ");
- ASSERT_EQ(2u, tokens.size()) << "Unexpected format in
'/etc/resolv.conf'";
- if (tokens[1] != "127.0.0.1") {
- nameServers.push_back(tokens[1]);
- }
- }
-
- container1Ready = path::join(os::getcwd(), "container1_ready");
- container2Ready = path::join(os::getcwd(), "container2_ready");
- trafficViaLoopback = path::join(os::getcwd(), "traffic_via_loopback");
- trafficViaPublic = path::join(os::getcwd(), "traffic_via_public");
- exitStatus = path::join(os::getcwd(), "exit_status");
- }
-
- virtual void TearDown()
- {
- cleanup(eth0, lo);
- TemporaryDirectoryTest::TearDown();
- }
-
- slave::Flags CreateSlaveFlags()
- {
- slave::Flags flags;
-
- flags.launcher_dir = path::join(tests::flags.build_dir, "src");
-
- flags.resources = strings::join(";", vector<string>({
- containerCPU,
- containerMemory,
- containerDisk,
- ephemeralPorts,
- persistentPorts }));
-
- // NOTE: '16' should be enough for all our tests.
- flags.ephemeral_ports_per_container = 16;
-
- flags.isolation = "network/port_mapping";
-
- return flags;
- }
-
- Try<pid_t> launchHelper(
- Launcher* launcher,
- int pipes[2],
- const ContainerID& containerId,
- const string& command,
- const Option<CommandInfo>& preparation)
- {
- CommandInfo commandInfo;
- commandInfo.set_value(command);
-
- // The flags to pass to the helper process.
- MesosContainerizerLaunch::Flags launchFlags;
-
- launchFlags.command = JSON::Protobuf(commandInfo);
- launchFlags.directory = os::getcwd();
-
- CHECK_SOME(os::user());
- launchFlags.user = os::user().get();
-
- launchFlags.pipe_read = pipes[0];
- launchFlags.pipe_write = pipes[1];
-
- JSON::Object commands;
- JSON::Array array;
- array.values.push_back(JSON::Protobuf(preparation.get()));
- commands.values["commands"] = array;
-
- launchFlags.commands = commands;
-
- vector<string> argv(2);
- argv[0] = MESOS_CONTAINERIZER;
- argv[1] = MesosContainerizerLaunch::NAME;
-
- Try<pid_t> pid = launcher->fork(
- containerId,
- path::join(flags.launcher_dir, MESOS_CONTAINERIZER),
- argv,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- launchFlags,
- None(),
- None());
-
- return pid;
- }
-
- Result<ResourceStatistics> statisticsHelper(
- pid_t pid,
- bool enable_summary,
- bool enable_details)
- {
- // Retrieve the socket information from inside the container.
- PortMappingStatistics statistics;
- statistics.flags.pid = pid;
- statistics.flags.eth0_name = eth0;
- statistics.flags.enable_socket_statistics_summary = enable_summary;
- statistics.flags.enable_socket_statistics_details = enable_details;
-
- vector<string> argv(2);
- argv[0] = "mesos-network-helper";
- argv[1] = PortMappingStatistics::NAME;
-
- // We don't need STDIN; we need STDOUT for the result; we leave
- // STDERR as is to log to slave process.
- Try<Subprocess> s = subprocess(
- path::join(flags.launcher_dir, "mesos-network-helper"),
- argv,
- Subprocess::PATH("/dev/null"),
- Subprocess::PIPE(),
- Subprocess::FD(STDERR_FILENO),
- statistics.flags);
-
- CHECK_SOME(s);
-
- Future<Option<int> > status = s.get().status();
- AWAIT_EXPECT_READY(status);
- EXPECT_SOME_EQ(0, status.get());
-
- Future<string> out = io::read(s.get().out().get());
- AWAIT_EXPECT_READY(out);
-
- Try<JSON::Object> object = JSON::parse<JSON::Object>(out.get());
- CHECK_SOME(object);
-
- return ::protobuf::parse<ResourceStatistics>(object.get());
- }
-
- slave::Flags flags;
-
- // Name of the host eth0 and lo.
- string eth0;
- string lo;
-
- // Host public IP network.
- net::IP hostIP;
-
- // All the external name servers as read from /etc/resolv.conf.
- vector<string> nameServers;
-
- // Some auxiliary files for the tests.
- string container1Ready;
- string container2Ready;
- string trafficViaLoopback;
- string trafficViaPublic;
- string exitStatus;
-};
-
-
-// Wait up to timeout seconds for a file to be created. If timeout is
-// zero, then wait indefinitely. Return true if file exists.
-//
-// TODO(pbrett): Consider generalizing this function and moving it to
-// a common header.
-static bool waitForFileCreation(
- const string& path,
- const Duration& duration = Seconds(60))
-{
- Stopwatch timer;
- timer.start();
- while (!os::exists(path)) {
- if ((duration > Duration::zero()) && (timer.elapsed() > duration))
- break;
- os::sleep(Milliseconds(50));
- }
- return os::exists(path);
-}
-
-
-// This test uses two containers: one listens to 'validPort' and
-// 'invalidPort' and writes data received to files; the other
-// container attempts to connect to the previous container using
-// 'validPort' and 'invalidPort'. Verify that only the connection
-// through 'validPort' is successful by confirming that the expected
-// data has been written to its output file.
-TEST_F(PortMappingIsolatorTest, ROOT_NC_ContainerToContainerTCP)
-{
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId1;
- containerId1.set_value("container1");
-
- // Use a relative temporary directory so it gets cleaned up
- // automatically with the test.
- Try<string> dir1 = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
- ASSERT_SOME(dir1);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId1,
- executorInfo,
- dir1.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
-
- // Listen to 'localhost' and 'port'.
- command1 << "nc -l localhost " << validPort << " > " << trafficViaLoopback
- << "& ";
-
- // Listen to 'public ip' and 'port'.
- command1 << "nc -l " << hostIP << " " << validPort << " > "
- << trafficViaPublic << "& ";
-
- // Listen to 'invalidPort'. This should not receive any data.
- command1 << "nc -l " << invalidPort << " | tee " << trafficViaLoopback << " "
- << trafficViaPublic << "& ";
-
- // Touch the guard file.
- command1 << "touch " << container1Ready;
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId1,
- command1.str(),
- preparation1.get());
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int> > status1 = process::reap(pid.get());
-
- // Continue in the parent.
- ::close(pipes[0]);
-
- // Isolate the forked child.
- AWAIT_READY(isolator.get()->isolate(containerId1, pid.get()));
-
- // Now signal the child to continue.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to start.
- ASSERT_TRUE(waitForFileCreation(container1Ready));
-
- ContainerID containerId2;
- containerId2.set_value("container2");
-
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container2Ports).get());
-
- // Use a relative temporary directory so it gets cleaned up
- // automatically with the test.
- Try<string> dir2 = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
- ASSERT_SOME(dir2);
-
- Future<Option<CommandInfo> > preparation2 =
- isolator.get()->prepare(
- containerId2,
- executorInfo,
- dir2.get(),
- None(),
- None());
-
- AWAIT_READY(preparation2);
- ASSERT_SOME(preparation2.get());
-
- ostringstream command2;
-
- // Send to 'localhost' and 'port'.
- command2 << "printf hello1 | nc localhost " << validPort << ";";
- // Send to 'localhost' and 'invalidPort'. This should fail.
- command2 << "printf hello2 | nc localhost " << invalidPort << ";";
- // Send to 'public IP' and 'port'.
- command2 << "printf hello3 | nc " << hostIP << " " << validPort << ";";
- // Send to 'public IP' and 'invalidPort'. This should fail.
- command2 << "printf hello4 | nc " << hostIP << " " << invalidPort << ";";
- // Touch the guard file.
- command2 << "touch " << container2Ready;
-
- ASSERT_NE(-1, ::pipe(pipes));
-
- pid = launchHelper(
- launcher.get(),
- pipes,
- containerId2,
- command2.str(),
- preparation2.get());
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int> > status2 = process::reap(pid.get());
-
- // Continue in the parent.
- ::close(pipes[0]);
-
- // Isolate the forked child.
- AWAIT_READY(isolator.get()->isolate(containerId2, pid.get()));
-
- // Now signal the child to continue.
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to start.
- ASSERT_TRUE(waitForFileCreation(container2Ready));
-
- // Wait for the command to complete.
- AWAIT_READY(status1);
- AWAIT_READY(status2);
-
- EXPECT_SOME_EQ("hello1", os::read(trafficViaLoopback));
- EXPECT_SOME_EQ("hello3", os::read(trafficViaPublic));
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId1));
- AWAIT_READY(launcher.get()->destroy(containerId2));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId1));
- AWAIT_READY(isolator.get()->cleanup(containerId2));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// The same container-to-container test but with UDP.
-TEST_F(PortMappingIsolatorTest, ROOT_NC_ContainerToContainerUDP)
-{
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId1;
- containerId1.set_value("container1");
-
- // Use a relative temporary directory so it gets cleaned up
- // automatically with the test.
- Try<string> dir1 = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
- ASSERT_SOME(dir1);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId1,
- executorInfo,
- dir1.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
-
- // Listen to 'localhost' and 'port'.
- command1 << "nc -u -l localhost " << validPort << " > " << trafficViaLoopback
- << "& ";
-
- // Listen to 'public ip' and 'port'.
- command1 << "nc -u -l " << hostIP << " " << validPort << " > "
- << trafficViaPublic << "& ";
-
- // Listen to 'invalidPort'. This should not receive anything.
- command1 << "nc -u -l " << invalidPort << " | tee " << trafficViaLoopback
- << " " << trafficViaPublic << "& ";
-
- // Touch the guard file.
- command1 << "touch " << container1Ready;
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId1,
- command1.str(),
- preparation1.get());
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int> > status1 = process::reap(pid.get());
-
- // Continue in the parent.
- ::close(pipes[0]);
-
- // Isolate the forked child.
- AWAIT_READY(isolator.get()->isolate(containerId1, pid.get()));
-
- // Now signal the child to continue.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to start.
- ASSERT_TRUE(waitForFileCreation(container1Ready));
-
- ContainerID containerId2;
- containerId2.set_value("container2");
-
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container2Ports).get());
-
- // Use a relative temporary directory so it gets cleaned up
- // automatically with the test.
- Try<string> dir2 = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
- ASSERT_SOME(dir2);
-
- Future<Option<CommandInfo> > preparation2 =
- isolator.get()->prepare(
- containerId2,
- executorInfo,
- dir2.get(),
- None(),
- None());
-
- AWAIT_READY(preparation2);
- ASSERT_SOME(preparation2.get());
-
- ostringstream command2;
-
- // Send to 'localhost' and 'port'.
- command2 << "printf hello1 | nc -w1 -u localhost " << validPort << ";";
- // Send to 'localhost' and 'invalidPort'. No data should be sent.
- command2 << "printf hello2 | nc -w1 -u localhost " << invalidPort << ";";
- // Send to 'public IP' and 'port'.
- command2 << "printf hello3 | nc -w1 -u " << hostIP << " " << validPort <<
";";
- // Send to 'public IP' and 'invalidPort'. No data should be sent.
- command2 << "printf hello4 | nc -w1 -u " << hostIP << " " << invalidPort
- << ";";
- // Touch the guard file.
- command2 << "touch " << container2Ready;
-
- ASSERT_NE(-1, ::pipe(pipes));
-
- pid = launchHelper(
- launcher.get(),
- pipes,
- containerId2,
- command2.str(),
- preparation2.get());
-
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int> > status2 = process::reap(pid.get());
-
- // Continue in the parent.
- ::close(pipes[0]);
-
- // Isolate the forked child.
- AWAIT_READY(isolator.get()->isolate(containerId2, pid.get()));
-
- // Now signal the child to continue.
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to start.
- ASSERT_TRUE(waitForFileCreation(container2Ready));
-
- // Wait for the command to complete.
- AWAIT_READY(status1);
- AWAIT_READY(status2);
-
- EXPECT_SOME_EQ("hello1", os::read(trafficViaLoopback));
- EXPECT_SOME_EQ("hello3", os::read(trafficViaPublic));
-
- AWAIT_READY(launcher.get()->destroy(containerId1));
- AWAIT_READY(launcher.get()->destroy(containerId2));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId1));
- AWAIT_READY(isolator.get()->cleanup(containerId2));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test the scenario where a UDP server is in a container while host
-// tries to establish a UDP connection.
-TEST_F(PortMappingIsolatorTest, ROOT_NC_HostToContainerUDP)
-{
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // 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);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
-
- // Listen to 'localhost' and 'Port'.
- command1 << "nc -u -l localhost " << validPort << " > " << trafficViaLoopback
- << "&";
-
- // Listen to 'public IP' and 'Port'.
- command1 << "nc -u -l " << hostIP << " " << validPort << " > "
- << trafficViaPublic << "&";
-
- // Listen to 'public IP' and 'invalidPort'. This should not receive anything.
- command1 << "nc -u -l " << invalidPort << " | tee " << trafficViaLoopback
- << " " << trafficViaPublic << "&";
-
- // Touch the guard file.
- command1 << "touch " << container1Ready;
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command1.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to start.
- ASSERT_TRUE(waitForFileCreation(container1Ready));
-
- // Send to 'localhost' and 'port'.
- ostringstream command2;
- command2 << "printf hello1 | nc -w1 -u localhost " << validPort;
- ASSERT_SOME_EQ(0, os::shell(NULL, command2.str().c_str()));
-
- // Send to 'localhost' and 'invalidPort'. The command should return
- // successfully because UDP is stateless but no data could be sent.
- ostringstream command3;
- command3 << "printf hello2 | nc -w1 -u localhost " << invalidPort;
- ASSERT_SOME_EQ(0, os::shell(NULL, command3.str().c_str()));
-
- // Send to 'public IP' and 'port'.
- ostringstream command4;
- command4 << "printf hello3 | nc -w1 -u " << hostIP << " " << validPort;
- ASSERT_SOME_EQ(0, os::shell(NULL, command4.str().c_str()));
-
- // Send to 'public IP' and 'invalidPort'. The command should return
- // successfully because UDP is stateless but no data could be sent.
- ostringstream command5;
- command5 << "printf hello4 | nc -w1 -u " << hostIP << " " << invalidPort;
- ASSERT_SOME_EQ(0, os::shell(NULL, command5.str().c_str()));
-
- EXPECT_SOME_EQ("hello1", os::read(trafficViaLoopback));
- EXPECT_SOME_EQ("hello3", os::read(trafficViaPublic));
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test the scenario where a TCP server is in a container while host
-// tries to establish a TCP connection.
-TEST_F(PortMappingIsolatorTest, ROOT_NC_HostToContainerTCP)
-{
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // 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);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
-
- // Listen to 'localhost' and 'Port'.
- command1 << "nc -l localhost " << validPort << " > " << trafficViaLoopback
- << "&";
-
- // Listen to 'public IP' and 'Port'.
- command1 << "nc -l " << hostIP << " " << validPort << " > "
- << trafficViaPublic << "&";
-
- // Listen to 'public IP' and 'invalidPort'. This should fail.
- command1 << "nc -l " << invalidPort << " | tee " << trafficViaLoopback << " "
- << trafficViaPublic << "&";
-
- // Touch the guard file.
- command1 << "touch " << container1Ready;
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command1.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to start.
- ASSERT_TRUE(waitForFileCreation(container1Ready));
-
- // Send to 'localhost' and 'port'.
- ostringstream command2;
- command2 << "printf hello1 | nc localhost " << validPort;
- ASSERT_SOME_EQ(0, os::shell(NULL, command2.str().c_str()));
-
- // Send to 'localhost' and 'invalidPort'. This should fail because TCP
- // connection couldn't be established..
- ostringstream command3;
- command3 << "printf hello2 | nc localhost " << invalidPort;
- ASSERT_SOME_EQ(256, os::shell(NULL, command3.str().c_str()));
-
- // Send to 'public IP' and 'port'.
- ostringstream command4;
- command4 << "printf hello3 | nc " << hostIP << " " << validPort;
- ASSERT_SOME_EQ(0, os::shell(NULL, command4.str().c_str()));
-
- // Send to 'public IP' and 'invalidPort'. This should fail because TCP
- // connection couldn't be established.
- ostringstream command5;
- command5 << "printf hello4 | nc " << hostIP << " " << invalidPort;
- ASSERT_SOME_EQ(256, os::shell(NULL, command5.str().c_str()));
-
- EXPECT_SOME_EQ("hello1", os::read(trafficViaLoopback));
- EXPECT_SOME_EQ("hello3", os::read(trafficViaPublic));
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test the scenario where a container issues ICMP requests to
-// external hosts.
-TEST_F(PortMappingIsolatorTest, ROOT_ContainerICMPExternal)
-{
- // TODO(chzhcn): Even though this is unlikely, consider a better
- // way to get external servers.
- ASSERT_FALSE(nameServers.empty())
- << "-------------------------------------------------------------\n"
- << "We cannot run some PortMappingIsolatorTests because we could\n"
- << "not find any external name servers in /etc/resolv.conf.\n"
- << "-------------------------------------------------------------";
-
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // 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);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
- for (unsigned int i = 0; i < nameServers.size(); i++) {
- const string& IP = nameServers[i];
- command1 << "ping -c1 " << IP;
- if (i + 1 < nameServers.size()) {
- command1 << " && ";
- }
- }
- command1 << "; printf $? > " << exitStatus << "; sync";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command1.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to complete.
- AWAIT_READY(status);
-
- EXPECT_SOME_EQ("0", os::read(exitStatus));
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test the scenario where a container issues ICMP requests to itself.
-TEST_F(PortMappingIsolatorTest, ROOT_ContainerICMPInternal)
-{
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // 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);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
- command1 << "ping -c1 127.0.0.1 && ping -c1 " << hostIP
- << "; printf $? > " << exitStatus << "; sync";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command1.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to complete.
- AWAIT_READY(status);
-
- EXPECT_SOME_EQ("0", os::read(exitStatus));
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test the scenario where a container issues ARP requests to
-// external hosts.
-TEST_F(PortMappingIsolatorTest, ROOT_ContainerARPExternal)
-{
- // TODO(chzhcn): Even though this is unlikely, consider a better
- // way to get external servers.
- ASSERT_FALSE(nameServers.empty())
- << "-------------------------------------------------------------\n"
- << "We cannot run some PortMappingIsolatorTests because we could\n"
- << "not find any external name servers in /etc/resolv.conf.\n"
- << "-------------------------------------------------------------";
-
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // 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);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
- for (unsigned int i = 0; i < nameServers.size(); i++) {
- const string& IP = nameServers[i];
- // Time out after 1s and terminate upon receiving the first reply.
- command1 << "arping -f -w1 " << IP << " -I " << eth0;
- if (i + 1 < nameServers.size()) {
- command1 << " && ";
- }
- }
- command1 << "; printf $? > " << exitStatus << "; sync";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command1.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to complete.
- AWAIT_READY(status);
-
- EXPECT_SOME_EQ("0", os::read(exitStatus));
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test DNS connectivity.
-TEST_F(PortMappingIsolatorTest, ROOT_DNS)
-{
- // TODO(chzhcn): Even though this is unlikely, consider a better
- // way to get external servers.
- ASSERT_FALSE(nameServers.empty())
- << "-------------------------------------------------------------\n"
- << "We cannot run some PortMappingIsolatorTests because we could\n"
- << "not find any external name servers in /etc/resolv.conf.\n"
- << "-------------------------------------------------------------";
-
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // 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);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
- for (unsigned int i = 0; i < nameServers.size(); i++) {
- const string& IP = nameServers[i];
- command1 << "host " << IP;
- if (i + 1 < nameServers.size()) {
- command1 << " && ";
- }
- }
- command1 << "; printf $? > " << exitStatus << "; sync";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command1.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to complete.
- AWAIT_READY(status);
-
- EXPECT_SOME_EQ("0", os::read(exitStatus));
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test the scenario where a container has run out of ephemeral ports
-// to use.
-TEST_F(PortMappingIsolatorTest, ROOT_TooManyContainers)
-{
- // Increase the ephemeral ports per container so that we dont have
- // enough ephemeral ports to launch a second container.
- flags.ephemeral_ports_per_container = 512;
-
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId1;
- containerId1.set_value("container1");
-
- // Use a relative temporary directory so it gets cleaned up
- // automatically with the test.
- Try<string> dir1 = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
- ASSERT_SOME(dir1);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId1,
- executorInfo,
- dir1.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- ostringstream command1;
- command1 << "sleep 1000";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId1,
- command1.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int> > status1 = process::reap(pid.get());
-
- // Continue in the parent.
- ::close(pipes[0]);
-
- // Isolate the forked child.
- AWAIT_READY(isolator.get()->isolate(containerId1, pid.get()));
-
- // Now signal the child to continue.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- ContainerID containerId2;
- containerId2.set_value("container2");
-
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container2Ports).get());
-
- // Use a relative temporary directory so it gets cleaned up
- // automatically with the test.
- Try<string> dir2 = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
- ASSERT_SOME(dir2);
-
- Future<Option<CommandInfo> > preparation2 =
- isolator.get()->prepare(
- containerId2,
- executorInfo,
- dir2.get(),
- None(),
- None());
-
- AWAIT_FAILED(preparation2);
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId1));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId1));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-// Test the scenario where PortMappingIsolator uses a very small
-// egress rate limit.
-TEST_F(PortMappingIsolatorTest, ROOT_NC_SmallEgressLimit)
-{
- // Note that the underlying rate limiting mechanism usually has a
- // small allowance for burst. Empirically, as least 10x of the rate
- // limit amount of data is required to make sure the burst is an
- // insignificant factor of the transmission time.
-
- // To-be-tested egress rate limit, in Bytes/s.
- const Bytes rate = 2000;
- // Size of the data to send, in Bytes.
- const Bytes size = 20480;
-
- // Use a very small egress limit.
- flags.egress_rate_limit_per_container = rate;
-
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Open an nc server on the host side. Note that 'invalidPort' is in
- // neither 'ports' nor 'ephemeral_ports', which makes it a good port
- // to use on the host.
- ostringstream command1;
- command1 << "nc -l localhost " << invalidPort << " > /devnull";
- Try<Subprocess> s = subprocess(command1.str().c_str());
- CHECK_SOME(s);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // 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);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- // Fill 'size' bytes of data. The actual content does not matter.
- string data(size.bytes(), 'a');
-
- ostringstream command2;
- const string transmissionTime = path::join(os::getcwd(),
"transmission_time");
-
- command2 << "echo 'Sending " << size.bytes()
- << " bytes of data under egress rate limit " << rate.bytes()
- << "Bytes/s...';";
-
- command2 << "{ time -p echo " << data << " | nc localhost "
- << invalidPort << " ; } 2> " << transmissionTime << " && ";
-
- // Touch the guard file.
- command2 << "touch " << container1Ready;
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command2.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int> > reap = 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Wait for the command to finish.
- ASSERT_TRUE(waitForFileCreation(container1Ready));
-
- Try<string> read = os::read(transmissionTime);
- CHECK_SOME(read);
-
- // Get the real elapsed time from `time` output. Sample output:
- // real 12.37
- // user 0.00
- // sys 0.00
- vector<string> lines = strings::split(strings::trim(read.get()), "\n");
- ASSERT_EQ(3u, lines.size());
-
- vector<string> split = strings::split(lines[0], " ");
- ASSERT_EQ(2u, split.size());
-
- Try<float> time = numify<float>(split[1]);
- ASSERT_SOME(time);
- ASSERT_GT(time.get(), (size.bytes() / rate.bytes()));
-
- // Make sure the nc server exits normally.
- Future<Option<int> > status = s.get().status();
- AWAIT_READY(status);
- EXPECT_SOME_EQ(0, status.get());
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-bool HasTCPSocketsCount(const ResourceStatistics& statistics)
-{
- return statistics.has_net_tcp_active_connections() &&
- statistics.has_net_tcp_time_wait_connections();
-}
-
-
-bool HasTCPSocketsRTT(const ResourceStatistics& statistics)
-{
- // We either have all of the following metrics or we have nothing.
- if (statistics.has_net_tcp_rtt_microsecs_p50() &&
- statistics.has_net_tcp_rtt_microsecs_p90() &&
- statistics.has_net_tcp_rtt_microsecs_p95() &&
- statistics.has_net_tcp_rtt_microsecs_p99()) {
- return true;
- } else {
- return false;
- }
-}
-
-
-// Test that RTT can be returned properly from usage(). This test is
-// very similar to SmallEgressLimitTest in its setup.
-TEST_F(PortMappingIsolatorTest, ROOT_NC_PortMappingStatistics)
-{
- // To-be-tested egress rate limit, in Bytes/s.
- const Bytes rate = 2000;
- // Size of the data to send, in Bytes.
- const Bytes size = 20480;
-
- // Use a very small egress limit.
- flags.egress_rate_limit_per_container = rate;
- flags.network_enable_socket_statistics_summary = true;
- flags.network_enable_socket_statistics_details = true;
-
- Try<Isolator*> isolator = PortMappingIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Open an nc server on the host side. Note that 'invalidPort' is
- // in neither 'ports' nor 'ephemeral_ports', which makes it a good
- // port to use on the host. We use this host's public IP because
- // connections to the localhost IP are filtered out when retrieving
- // the RTT information inside containers.
- ostringstream command1;
- command1 << "nc -l " << hostIP << " " << invalidPort << " > /devnull";
- Try<Subprocess> s = subprocess(command1.str().c_str());
- CHECK_SOME(s);
-
- // Set the executor's resources.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse(container1Ports).get());
-
- ContainerID containerId;
- containerId.set_value("container1");
-
- // Use a relative temporary directory so it gets cleaned up
- // automatically with the test.
- Try<string> dir1 = os::mkdtemp(path::join(os::getcwd(), "XXXXXX"));
- ASSERT_SOME(dir1);
-
- Future<Option<CommandInfo> > preparation1 =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- dir1.get(),
- None(),
- None());
-
- AWAIT_READY(preparation1);
- ASSERT_SOME(preparation1.get());
-
- // Fill 'size' bytes of data. The actual content does not matter.
- string data(size.bytes(), 'a');
-
- ostringstream command2;
- const string transmissionTime = path::join(os::getcwd(),
"transmission_time");
-
- command2 << "echo 'Sending " << size.bytes()
- << " bytes of data under egress rate limit " << rate.bytes()
- << "Bytes/s...';";
-
- command2 << "{ time -p echo " << data << " | nc " << hostIP << " "
- << invalidPort << " ; } 2> " << transmissionTime << " && ";
-
- // Touch the guard file.
- command2 << "touch " << container1Ready;
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- Try<pid_t> pid = launchHelper(
- launcher.get(),
- pipes,
- containerId,
- command2.str(),
- preparation1.get());
-
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int> > reap = 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.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
- ::close(pipes[1]);
-
- // Test that RTT can be returned while transmission is going. It is
- // possible that the first few statistics returned don't have a RTT
- // value because it takes a few round-trips to actually establish a
- // tcp connection and start sending data. Nevertheless, we should
- // see a meaningful result well within seconds.
- Duration waited = Duration::zero();
- do {
- os::sleep(Milliseconds(200));
- waited += Milliseconds(200);
-
- // Do an end-to-end test by calling `usage`.
- Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
- AWAIT_READY(usage);
-
- if (usage.get().has_net_tcp_rtt_microsecs_p50() &&
- usage.get().has_net_tcp_active_connections()) {
- EXPECT_GT(usage.get().net_tcp_active_connections(), 0);
- break;
- }
- } while (waited < Seconds(5));
- ASSERT_LT(waited, Seconds(5));
-
- // While the connection is still active, try out different flag
- // combinations.
- Result<ResourceStatistics> statistics =
- statisticsHelper(pid.get(), true, true);
- ASSERT_SOME(statistics);
- EXPECT_TRUE(HasTCPSocketsCount(statistics.get()));
- EXPECT_TRUE(HasTCPSocketsRTT(statistics.get()));
-
- statistics = statisticsHelper(pid.get(), true, false);
- ASSERT_SOME(statistics);
- EXPECT_TRUE(HasTCPSocketsCount(statistics.get()));
- EXPECT_FALSE(HasTCPSocketsRTT(statistics.get()));
-
- statistics = statisticsHelper(pid.get(), false, true);
- ASSERT_SOME(statistics);
- EXPECT_FALSE(HasTCPSocketsCount(statistics.get()));
- EXPECT_TRUE(HasTCPSocketsRTT(statistics.get()));
-
- statistics = statisticsHelper(pid.get(), false, false);
- ASSERT_SOME(statistics);
- EXPECT_FALSE(HasTCPSocketsCount(statistics.get()));
- EXPECT_FALSE(HasTCPSocketsRTT(statistics.get()));
-
- // Wait for the command to finish.
- ASSERT_TRUE(waitForFileCreation(container1Ready));
-
- // Make sure the nc server exits normally.
- Future<Option<int> > status = s.get().status();
- AWAIT_READY(status);
- EXPECT_SOME_EQ(0, status.get());
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-
-
-class PortMappingMesosTest : public ContainerizerTest<MesosContainerizer>
-{
-public:
- virtual void SetUp()
- {
- ContainerizerTest<MesosContainerizer>::SetUp();
-
- // Guess the name of the public interface.
- Result<string> _eth0 = link::eth0();
- ASSERT_SOME(_eth0) << "Failed to guess the name of the public interface";
-
- eth0 = _eth0.get();
-
- LOG(INFO) << "Using " << eth0 << " as the public interface";
-
- // Guess the name of the loopback interface.
- Result<string> _lo = link::lo();
- ASSERT_SOME(_lo) << "Failed to guess the name of the loopback interface";
-
- lo = _lo.get();
-
- LOG(INFO) << "Using " << lo << " as the loopback interface";
-
- cleanup(eth0, lo);
- }
-
- virtual void TearDown()
- {
- cleanup(eth0, lo);
-
- ContainerizerTest<MesosContainerizer>::TearDown();
- }
-
- Fetcher fetcher;
-
- // Name of the host eth0 and lo.
- string eth0;
- string lo;
-};
-
-
-// Test the scenario where the network isolator is asked to recover
-// both types of containers: containers that were previously managed
-// by network isolator, and containers that weren't.
-TEST_F(PortMappingMesosTest, CGROUPS_ROOT_RecoverMixedContainers)
-{
- master::Flags masterFlags = CreateMasterFlags();
-
- Try<PID<Master>> master = StartMaster(masterFlags);
- ASSERT_SOME(master);
-
- // Start the first slave without the network isolator.
- slave::Flags slaveFlags = CreateSlaveFlags();
-
- // NOTE: This is to make sure that we use the linux launcher which
- // is consistent with the launchers we use for other containerizers
- // we create in this test. Also, this will bypass MESOS-2554.
- slaveFlags.isolation = "cgroups/cpu,cgroups/mem";
-
- Try<MesosContainerizer*> containerizer1 =
- MesosContainerizer::create(slaveFlags, true, &fetcher);
-
- ASSERT_SOME(containerizer1);
-
- Try<PID<Slave> > slave = StartSlave(containerizer1.get(), slaveFlags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
-
- // Enable checkpointing for the framework.
- FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
- frameworkInfo.set_checkpoint(true);
-
- MesosSchedulerDriver driver(
- &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
-
- EXPECT_CALL(sched, registered(_, _, _));
-
- Filters filters;
- filters.set_refuse_seconds(0);
-
- // NOTE: We set filter explicitly here so that the resources will
- // not be filtered for 5 seconds (by default).
- Future<vector<Offer> > offers1;
- EXPECT_CALL(sched, resourceOffers(_, _))
- .WillOnce(FutureArg<1>(&offers1))
- .WillRepeatedly(DeclineOffers(filters)); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(offers1);
- EXPECT_NE(0u, offers1.get().size());
-
- Offer offer1 = offers1.get()[0];
-
- // Start a long running task without using the network isolator.
- TaskInfo task1 = createTask(
- offer1.slave_id(),
- Resources::parse("cpus:1;mem:512").get(),
- "sleep 1000");
-
- EXPECT_CALL(sched, statusUpdate(_, _));
-
- Future<Nothing> _statusUpdateAcknowledgement1 =
- FUTURE_DISPATCH(_, &Slave::_statusUpdateAcknowledgement);
-
- driver.launchTasks(offers1.get()[0].id(), {task1}, filters);
-
- // Wait for the ACK to be checkpointed.
- AWAIT_READY(_statusUpdateAcknowledgement1);
-
- Stop(slave.get());
- delete containerizer1.get();
-
- Future<Nothing> _recover1 = FUTURE_DISPATCH(_, &Slave::_recover);
-
- Future<SlaveReregisteredMessage> slaveReregisteredMessage1 =
- FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _);
-
- Future<vector<Offer> > offers2;
- EXPECT_CALL(sched, resourceOffers(_, _))
- .WillOnce(FutureArg<1>(&offers2))
- .WillRepeatedly(DeclineOffers(filters)); // Ignore subsequent offers.
-
- // Restart the slave with the network isolator.
- slaveFlags.isolation += ",network/port_mapping";
-
- Try<MesosContainerizer*> containerizer2 =
- MesosContainerizer::create(slaveFlags, true, &fetcher);
-
- ASSERT_SOME(containerizer2);
-
- slave = StartSlave(containerizer2.get(), slaveFlags);
- ASSERT_SOME(slave);
-
- Clock::pause();
-
- AWAIT_READY(_recover1);
-
- Clock::settle(); // Wait for slave to schedule reregister timeout.
- Clock::advance(EXECUTOR_REREGISTER_TIMEOUT);
-
- AWAIT_READY(slaveReregisteredMessage1);
-
- Clock::settle(); // Make sure an allocation is scheduled.
- Clock::advance(masterFlags.allocation_interval);
-
- Clock::resume();
-
- AWAIT_READY(offers2);
- EXPECT_NE(0u, offers2.get().size());
-
- Offer offer2 = offers2.get()[0];
-
- // Start a long running task using the network isolator.
- TaskInfo task2 = createTask(offer2, "sleep 1000");
-
- EXPECT_CALL(sched, statusUpdate(_, _));
-
- Future<Nothing> _statusUpdateAcknowledgement2 =
- FUTURE_DISPATCH(_, &Slave::_statusUpdateAcknowledgement);
-
- driver.launchTasks(offers2.get()[0].id(), {task2});
-
- // Wait for the ACK to be checkpointed.
- AWAIT_READY(_statusUpdateAcknowledgement2);
-
- Stop(slave.get());
- delete containerizer2.get();
-
- Future<Nothing> _recover2 = FUTURE_DISPATCH(_, &Slave::_recover);
-
- Future<SlaveReregisteredMessage> slaveReregisteredMessage2 =
- FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _);
-
- // Restart the slave with the network isolator. This is to verify
- // the slave recovery case where one task is running with the
- // network isolator and another task is running without it.
- Try<MesosContainerizer*> containerizer3 =
- MesosContainerizer::create(slaveFlags, true, &fetcher);
-
- ASSERT_SOME(containerizer3);
-
- slave = StartSlave(containerizer3.get(), slaveFlags);
- ASSERT_SOME(slave);
-
- Clock::pause();
-
- AWAIT_READY(_recover2);
-
- Clock::settle(); // Wait for slave to schedule reregister timeout.
- Clock::advance(EXECUTOR_REREGISTER_TIMEOUT);
-
- AWAIT_READY(slaveReregisteredMessage2);
-
- Clock::resume();
-
- // Ensure that both containers (with and without network isolation)
- // were recovered.
- Future<hashset<ContainerID>> containers = containerizer3.get()->containers();
- AWAIT_READY(containers);
- EXPECT_EQ(2u, containers.get().size());
-
- foreach (const ContainerID& containerId, containers.get()) {
- // Do some basic checks to make sure the network isolator can
- // handle mixed types of containers correctly.
- Future<ResourceStatistics> usage =
containerizer3.get()->usage(containerId);
- AWAIT_READY(usage);
-
- // TODO(chzhcn): Write a more thorough test for update.
- }
-
- driver.stop();
- driver.join();
-
- Shutdown();
- delete containerizer3.get();
-}
-
-
-// Test that all configurations (tc filters etc) is cleaned up for an
-// orphaned container using the network isolator.
-TEST_F(PortMappingMesosTest, CGROUPS_ROOT_CleanUpOrphan)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- // NOTE: We add 'cgroups/cpu,cgroups/mem' to bypass MESOS-2554.
- flags.isolation = "cgroups/cpu,cgroups/mem,network/port_mapping";
-
- Try<PID<Slave> > slave = StartSlave(flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
-
- // Enable checkpointing for the framework.
- FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
- frameworkInfo.set_checkpoint(true);
-
- MesosSchedulerDriver driver(
- &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(_, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(_, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(DeclineOffers()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(offers);
- EXPECT_NE(0u, offers.get().size());
-
- // Start a long running task using network islator.
- TaskInfo task = createTask(offers.get()[0], "sleep 1000");
-
- EXPECT_CALL(sched, statusUpdate(_, _));
-
- Future<Nothing> _statusUpdateAcknowledgement =
- FUTURE_DISPATCH(_, &Slave::_statusUpdateAcknowledgement);
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- // Wait for the ACK to be checkpointed.
- AWAIT_READY(_statusUpdateAcknowledgement);
-
- Stop(slave.get());
-
- // Wipe the slave meta directory so that the slave will treat the
- // above running task as an orphan.
- ASSERT_SOME(os::rmdir(paths::getMetaRootDir(flags.work_dir)));
-
- Future<SlaveRegisteredMessage> slaveRegisteredMessage =
- FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
-
- Future<Nothing> orphansDestroyed =
- FUTURE_DISPATCH(_, &MesosContainerizerProcess::___recover);
-
- // Restart the slave.
- slave = StartSlave(flags);
- ASSERT_SOME(slave);
-
- AWAIT_READY(slaveRegisteredMessage);
-
- AWAIT_READY(orphansDestroyed);
-
- // Expect that qdiscs still exist on eth0 and lo but with no filters.
- Try<bool> hostEth0ExistsQdisc = ingress::exists(eth0);
- EXPECT_SOME_TRUE(hostEth0ExistsQdisc);
-
- Try<bool> hostLoExistsQdisc = ingress::exists(lo);
- EXPECT_SOME_TRUE(hostLoExistsQdisc);
-
- Result<vector<ip::Classifier> > classifiers =
- ip::classifiers(eth0, ingress::HANDLE);
-
- EXPECT_SOME(classifiers);
- EXPECT_EQ(0u, classifiers.get().size());
-
- classifiers = ip::classifiers(lo, ingress::HANDLE);
- EXPECT_SOME(classifiers);
- EXPECT_EQ(0u, classifiers.get().size());
-
- // Expect no 'veth' devices.
- Try<set<string> > links = net::links();
- ASSERT_SOME(links);
- foreach (const string& name, links.get()) {
- EXPECT_FALSE(strings::startsWith(name, slave::PORT_MAPPING_VETH_PREFIX()));
- }
-
- // Expect no files in bind mount directory.
- Try<list<string> > files = os::ls(slave::PORT_MAPPING_BIND_MOUNT_ROOT());
- ASSERT_SOME(files);
- EXPECT_EQ(0u, files.get().size());
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-// This test verfies the creation and destroy of the network namespace
-// handle symlink. The symlink was introduced in 0.23.0.
-TEST_F(PortMappingMesosTest, ROOT_NetworkNamespaceHandleSymlink)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
- flags.isolation = "network/port_mapping";
-
- Try<MesosContainerizer*> containerizer =
- MesosContainerizer::create(flags, true, &fetcher);
-
- ASSERT_SOME(containerizer);
-
- Try<PID<Slave>> slave = StartSlave(containerizer.get(), flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
-
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- EXPECT_CALL(sched, registered(_, _, _));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(_, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(DeclineOffers()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(offers);
- EXPECT_NE(0u, offers.get().size());
-
- // Start a long running task using network islator.
- TaskInfo task = createTask(offers.get()[0], "sleep 1000");
-
- Future<TaskStatus> status1;
- Future<TaskStatus> status2;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&status1))
- .WillOnce(FutureArg<1>(&status2))
- .WillRepeatedly(Return()); // Ignore subsequent updates.
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY(status1);
- EXPECT_EQ(task.task_id(), status1.get().task_id());
- EXPECT_EQ(TASK_RUNNING, status1.get().state());
-
- Future<hashset<ContainerID>> containers = containerizer.get()->containers();
- AWAIT_READY(containers);
- ASSERT_EQ(1u, containers.get().size());
-
- ContainerID containerId = *(containers.get().begin());
-
- const string symlink = path::join(
- slave::PORT_MAPPING_BIND_MOUNT_SYMLINK_ROOT(),
- stringify(containerId));
-
- EXPECT_TRUE(os::exists(symlink));
- EXPECT_TRUE(os::stat::islink(symlink));
-
- Future<containerizer::Termination> termination =
- containerizer.get()->wait(containerId);
-
- driver.killTask(task.task_id());
-
- AWAIT_READY(status2);
- EXPECT_EQ(task.task_id(), status2.get().task_id());
- EXPECT_EQ(TASK_KILLED, status2.get().state());
-
- AWAIT_READY(termination);
- EXPECT_FALSE(os::exists(symlink));
-
- driver.stop();
- driver.join();
-
- Shutdown();
-
- delete containerizer.get();
-}
-
-
-// This test verfies that the isolator is able to recover a mix of
-// known and unkonwn orphans. This is used to capture the regression
-// described in MESOS-2914.
-TEST_F(PortMappingMesosTest, CGROUPS_ROOT_RecoverMixedKnownAndUnKnownOrphans)
-{
- Try<PID<Master>> master = StartMaster(CreateMasterFlags());
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
- flags.isolation = "network/port_mapping";
-
- Try<MesosContainerizer*> containerizer =
- MesosContainerizer::create(flags, true, &fetcher);
-
- ASSERT_SOME(containerizer);
-
- Try<PID<Slave> > slave = StartSlave(containerizer.get(), flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
-
- FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
- frameworkInfo.set_checkpoint(true);
-
- MesosSchedulerDriver driver(
- &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
-
- EXPECT_CALL(sched, registered(_, _, _));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(_, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(DeclineOffers()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(offers);
- EXPECT_NE(0u, offers.get().size());
-
- Offer offer = offers.get()[0];
-
- TaskInfo task1 = createTask(
- offer.slave_id(),
- Resources::parse("cpus:1;mem:64").get(),
- "sleep 1000");
-
- TaskInfo task2 = createTask(
- offer.slave_id(),
- Resources::parse("cpus:1;mem:64").get(),
- "sleep 1000");
-
- Future<TaskStatus> status1;
- Future<TaskStatus> status2;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&status1))
- .WillOnce(FutureArg<1>(&status2))
- .WillRepeatedly(Return()); // Ignore subsequent updates.
-
- driver.launchTasks(offers.get()[0].id(), {task1, task2});
-
- AWAIT_READY(status1);
- ASSERT_EQ(TASK_RUNNING, status1.get().state());
-
- AWAIT_READY(status2);
- ASSERT_EQ(TASK_RUNNING, status2.get().state());
-
- // Obtain the container IDs.
- Future<hashset<ContainerID>> containers = containerizer.get()->containers();
- AWAIT_READY(containers);
- ASSERT_EQ(2u, containers.get().size());
-
- Stop(slave.get());
- delete containerizer.get();
-
- // Wipe the slave meta directory so that the slave will treat the
- // above running tasks as orphans.
- ASSERT_SOME(os::rmdir(paths::getMetaRootDir(flags.work_dir)));
-
- // Remove the network namespace symlink for one container so that it
- // becomes an unknown orphan.
- const ContainerID containerId = *(containers.get().begin());
- const string symlink = path::join(
- slave::PORT_MAPPING_BIND_MOUNT_SYMLINK_ROOT(),
- stringify(containerId));
-
- ASSERT_TRUE(os::exists(symlink));
- ASSERT_TRUE(os::stat::islink(symlink));
- ASSERT_SOME(os::rm(symlink));
-
- Future<SlaveRegisteredMessage> slaveRegisteredMessage =
- FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
-
- Future<Nothing> knownOrphansDestroyed =
- FUTURE_DISPATCH(_, &MesosContainerizerProcess::___recover);
-
- // Restart the slave.
- slave = StartSlave(flags);
- ASSERT_SOME(slave);
-
- AWAIT_READY(slaveRegisteredMessage);
- AWAIT_READY(knownOrphansDestroyed);
-
- // We settle the clock here to ensure that the processing of
- // 'MesosContainerizerProcess::___destroy()' is complete and the
- // metric is updated.
- Clock::pause();
- Clock::settle();
- Clock::resume();
-
- JSON::Object metrics = Metrics();
- EXPECT_EQ(
- 0u,
- metrics.values["containerizer/mesos/container_destroy_errors"]);
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {